feat(hosts): generate unique 8-digit asset ID
Add a Generate button to the host create dialog that fetches a random 8-digit asset ID from the new GET /hosts/generate-asset-id endpoint. The service retries against the unique index so the returned ID is guaranteed unused. Edit mode hides the button. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@ import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { z } from 'zod';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
import { Loader2, Sparkles } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { HostStack, HostState } from '@vector/shared';
|
||||
import {
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
SelectValue,
|
||||
Textarea,
|
||||
} from '@vector/ui';
|
||||
import { createHost, updateHost } from '../../lib/api/hosts.js';
|
||||
import { createHost, generateHostAssetId, updateHost } from '../../lib/api/hosts.js';
|
||||
import { ApiRequestError } from '../../lib/api/client.js';
|
||||
import { queryKeys } from '../../lib/queryKeys.js';
|
||||
import type { Host } from '../../lib/api/types.js';
|
||||
@@ -88,6 +88,15 @@ export function HostFormDialog({ open, onOpenChange, host }: HostFormDialogProps
|
||||
});
|
||||
}, [open, host, form]);
|
||||
|
||||
const generateMutation = useMutation({
|
||||
mutationFn: () => generateHostAssetId(),
|
||||
onSuccess: ({ assetId }) => {
|
||||
form.setValue('assetId', assetId, { shouldDirty: true, shouldValidate: true });
|
||||
},
|
||||
onError: (err) =>
|
||||
toast.error(err instanceof ApiRequestError ? err.body.message : 'Generate failed'),
|
||||
});
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async (values: Values) => {
|
||||
if (editing && host) {
|
||||
@@ -136,9 +145,27 @@ export function HostFormDialog({ open, onOpenChange, host }: HostFormDialogProps
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Asset ID</FormLabel>
|
||||
<FormControl>
|
||||
<Input autoFocus placeholder="e.g. ASSET-001" {...field} />
|
||||
</FormControl>
|
||||
<div className="flex gap-2">
|
||||
<FormControl>
|
||||
<Input autoFocus placeholder="e.g. ASSET-001" {...field} />
|
||||
</FormControl>
|
||||
{!editing && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => generateMutation.mutate()}
|
||||
disabled={generateMutation.isPending}
|
||||
title="Generate an unused 8-digit asset ID"
|
||||
>
|
||||
{generateMutation.isPending ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Sparkles className="h-4 w-4" />
|
||||
)}
|
||||
Generate
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
|
||||
@@ -42,3 +42,8 @@ export async function updateHost(id: string, input: UpdateHostRequest): Promise<
|
||||
export async function deleteHost(id: string): Promise<void> {
|
||||
await api.delete(`/hosts/${id}`);
|
||||
}
|
||||
|
||||
export async function generateHostAssetId(): Promise<{ assetId: string }> {
|
||||
const res = await api.get<{ assetId: string }>('/hosts/generate-asset-id');
|
||||
return res.data;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user