Add missing admin action files and navigation links
- Add 11 missing server action files: badges, bulk-users, catalog, catalog-bc, catalog-items, import-badges, import-furni, multi-account-detect, permissions, rooms, soundtracks - Add missing admin navigation links: tickets, sounds, translations, import, radio sub-pages - Add translation keys for all new navigation items
This commit is contained in:
1 parent
7e1ae17a3b
commit
41be6835bf
347 files changed
+28378
-6091
No files matched your search
@@ -0,0 +1,153 @@
|
||||
'use client'
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { updateUser } from '@/actions/users'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { useServerAction } from '@/hooks/use-server-action'
|
||||
import { type UpdateUserInput, updateUserSchema } from '@/lib/validators/user'
|
||||
|
||||
interface UserEditFormProps {
|
||||
user: {
|
||||
id: number
|
||||
username: string
|
||||
mail: string
|
||||
rank: number
|
||||
motto: string
|
||||
credits: number
|
||||
pixels: number
|
||||
diamonds: number
|
||||
duckets: number
|
||||
}
|
||||
/** Ranks loaded from the `permissions` table (id + human-readable name). */
|
||||
ranks?: Array<{ id: number; name: string }>
|
||||
}
|
||||
|
||||
// Fallback used if the page does not supply the DB-loaded rank list.
|
||||
const FALLBACK_RANKS: Array<{ id: number; name: string }> = [
|
||||
{ id: 1, name: 'User' },
|
||||
{ id: 2, name: 'Bronze' },
|
||||
{ id: 3, name: 'Silver' },
|
||||
{ id: 4, name: 'Gold' },
|
||||
{ id: 5, name: 'Moderator' },
|
||||
{ id: 6, name: 'Admin' },
|
||||
{ id: 7, name: 'Super Admin' },
|
||||
]
|
||||
|
||||
export function UserEditForm({ user, ranks }: UserEditFormProps) {
|
||||
const rankOptions = ranks && ranks.length > 0 ? ranks : FALLBACK_RANKS
|
||||
const { isPending, run } = useServerAction()
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<UpdateUserInput>({
|
||||
resolver: zodResolver(updateUserSchema),
|
||||
defaultValues: {
|
||||
username: user.username,
|
||||
mail: user.mail,
|
||||
rank: user.rank,
|
||||
motto: user.motto,
|
||||
credits: user.credits,
|
||||
pixels: user.pixels,
|
||||
diamonds: user.diamonds,
|
||||
duckets: user.duckets,
|
||||
},
|
||||
})
|
||||
|
||||
function onSubmit(data: UpdateUserInput) {
|
||||
run(() => updateUser({ id: user.id, ...data }), {
|
||||
successMessage: 'User updated successfully.',
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Edit User</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="username">Username</Label>
|
||||
<Input id="username" {...register('username')} />
|
||||
{errors.username && (
|
||||
<p className="text-sm text-destructive">{errors.username.message}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="mail">Email</Label>
|
||||
<Input id="mail" type="email" {...register('mail')} />
|
||||
{errors.mail && <p className="text-sm text-destructive">{errors.mail.message}</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="rank">Rank</Label>
|
||||
<Select
|
||||
defaultValue={String(user.rank)}
|
||||
onValueChange={(v) => setValue('rank', parseInt(v, 10))}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select rank" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{rankOptions.map((r) => (
|
||||
<SelectItem key={r.id} value={String(r.id)}>
|
||||
{r.id} — {r.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="motto">Motto</Label>
|
||||
<Input id="motto" {...register('motto')} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="credits">Credits</Label>
|
||||
<Input id="credits" type="number" {...register('credits', { valueAsNumber: true })} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="pixels">Pixels</Label>
|
||||
<Input id="pixels" type="number" {...register('pixels', { valueAsNumber: true })} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="diamonds">Diamonds</Label>
|
||||
<Input
|
||||
id="diamonds"
|
||||
type="number"
|
||||
{...register('diamonds', { valueAsNumber: true })}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="duckets">Duckets</Label>
|
||||
<Input id="duckets" type="number" {...register('duckets', { valueAsNumber: true })} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button type="submit" disabled={isPending}>
|
||||
{isPending ? 'Saving...' : 'Save Changes'}
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user