implement full user management function base on sqlite database in admi
This commit is contained in:
parent
e7d9db0cd6
commit
38fccc09b7
|
|
@ -0,0 +1,150 @@
|
||||||
|
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { useForm, SubmitHandler } from 'react-hook-form';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } 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 { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
|
||||||
|
import { useToast } from '@/hooks/use-toast';
|
||||||
|
import { Save, UserPlus } from 'lucide-react';
|
||||||
|
import type { User } from '@/types';
|
||||||
|
import { useI18n, useCurrentLocale } from '@/locales/client';
|
||||||
|
import { registerUser, updateUser } from '@/app/actions/user';
|
||||||
|
|
||||||
|
const formSchema = z.object({
|
||||||
|
name: z.string().min(2, "Name must be at least 2 characters."),
|
||||||
|
email: z.string().email("Invalid email address."),
|
||||||
|
role: z.enum(['User', 'Admin']),
|
||||||
|
});
|
||||||
|
|
||||||
|
type UserFormValues = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
|
interface UserFormProps {
|
||||||
|
initialData?: User;
|
||||||
|
isEditMode?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UserForm({ initialData, isEditMode = false }: UserFormProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const t = useI18n();
|
||||||
|
const locale = useCurrentLocale();
|
||||||
|
|
||||||
|
const form = useForm<UserFormValues>({
|
||||||
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
name: initialData?.name || '',
|
||||||
|
email: initialData?.email || '',
|
||||||
|
role: initialData?.role || 'User',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { isSubmitting } = form.formState;
|
||||||
|
|
||||||
|
const onSubmit: SubmitHandler<UserFormValues> = async (data) => {
|
||||||
|
const result = isEditMode
|
||||||
|
? await updateUser({ ...initialData!, ...data })
|
||||||
|
: await registerUser(data);
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
toast({
|
||||||
|
title: isEditMode ? t('admin.users.user_updated_toast') : t('admin.users.user_created_toast'),
|
||||||
|
});
|
||||||
|
router.push(`/${locale}/admin/users`);
|
||||||
|
router.refresh(); // Ensure the user list is up-to-date
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: t('admin.users.toast_error_title'),
|
||||||
|
description: result.message,
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="w-full max-w-2xl mx-auto shadow-xl">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-2xl font-headline">
|
||||||
|
{isEditMode ? t('admin.users.edit_user_title') : t('admin.users.add_user_title')}
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
{isEditMode ? t('admin.users.edit_user_description') : t('admin.users.add_user_description')}
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<Form {...form}>
|
||||||
|
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||||
|
<CardContent className="space-y-6">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('admin.users.form_name_label')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="John Doe" {...field} disabled={isSubmitting} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="email"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('admin.users.form_email_label')}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="email" placeholder="user@example.com" {...field} disabled={isSubmitting} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="role"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t('admin.users.form_role_label')}</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} defaultValue={field.value} disabled={isSubmitting}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a role" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="User">{t('admin.users.form_role_user')}</SelectItem>
|
||||||
|
<SelectItem value="Admin">{t('admin.users.form_role_admin')}</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<Button type="submit" disabled={isSubmitting}>
|
||||||
|
{isEditMode ? (
|
||||||
|
<>
|
||||||
|
<Save className="mr-2 h-4 w-4" />
|
||||||
|
{isSubmitting ? t('admin.users.form_saving_button') : t('admin.users.form_save_button')}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<UserPlus className="mr-2 h-4 w-4" />
|
||||||
|
{isSubmitting ? t('admin.users.form_creating_button') : t('admin.users.form_create_button')}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
import UserForm from '../UserForm';
|
||||||
|
import { getI18n } from '@/locales/server';
|
||||||
|
import type { Locale } from '@/locales/server';
|
||||||
|
|
||||||
|
export default async function AddUserPage({ params }: { params: { locale: Locale } }) {
|
||||||
|
const t = await getI18n(); // To preload translations for the client component
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-8">
|
||||||
|
<UserForm isEditMode={false} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
import { getUserById } from '@/data/operations';
|
||||||
|
import UserForm from '../UserForm';
|
||||||
|
import { getI18n } from '@/locales/server';
|
||||||
|
import type { Locale } from '@/locales/server';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { ArrowLeft } from 'lucide-react';
|
||||||
|
|
||||||
|
interface EditUserPageProps {
|
||||||
|
params: { id: string; locale: Locale };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function EditUserPage({ params }: EditUserPageProps) {
|
||||||
|
const t = await getI18n();
|
||||||
|
const userData = getUserById(params.id);
|
||||||
|
|
||||||
|
if (!userData) {
|
||||||
|
return (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<h1 className="text-2xl font-bold mb-4">{t('admin.users.user_not_found')}</h1>
|
||||||
|
<Link href={`/${params.locale}/admin/users`} passHref>
|
||||||
|
<Button variant="outline">
|
||||||
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||||
|
{t('admin.users.back_to_users_button')}
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-8">
|
||||||
|
<UserForm isEditMode={true} initialData={userData} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,27 +1,70 @@
|
||||||
|
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||||
import { getAllUsers } from "@/data/operations";
|
import { getAllUsers } from "@/data/operations";
|
||||||
import { getI18n } from "@/locales/server";
|
import { useI18n, useCurrentLocale } from "@/locales/client";
|
||||||
import { UserCog } from "lucide-react";
|
import { UserCog, Trash2, PlusCircle } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import type { Locale } from "@/locales/server";
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
} from "@/components/ui/alert-dialog";
|
||||||
|
import type { User } from '@/types';
|
||||||
|
import { useToast } from '@/hooks/use-toast';
|
||||||
|
import { deleteUser } from '@/app/actions/user';
|
||||||
|
|
||||||
export default async function AdminUserManagementPage({ params }: { params: { locale: Locale } }) {
|
export default function AdminUserManagementPage({ users }: { users: User[] }) {
|
||||||
const t = await getI18n();
|
const t = useI18n();
|
||||||
const locale = params.locale;
|
const locale = useCurrentLocale();
|
||||||
const allUsers = getAllUsers();
|
const router = useRouter();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
|
|
||||||
// Sort users for consistent display, e.g., by name or role
|
// We receive users as a prop after fetching them in a wrapper component.
|
||||||
allUsers.sort((a, b) => a.name.localeCompare(b.name));
|
const allUsers = users.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
|
||||||
|
const handleDeleteUser = async (userId: string) => {
|
||||||
|
setIsDeleting(true);
|
||||||
|
const result = await deleteUser(userId);
|
||||||
|
if (result.success) {
|
||||||
|
toast({ title: t('admin.users.user_deleted_toast') });
|
||||||
|
// The page will re-render due to revalidation, no need for router.refresh()
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: t('admin.users.toast_error_title'),
|
||||||
|
description: result.message,
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setIsDeleting(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold font-headline text-primary">{t('admin.users.title')}</h1>
|
<h1 className="text-3xl font-bold font-headline text-primary">{t('admin.users.title')}</h1>
|
||||||
<p className="text-muted-foreground">{t('admin.users.description')}</p>
|
<p className="text-muted-foreground">{t('admin.users.description')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<Link href={`/${locale}/admin/users/add`} passHref>
|
||||||
|
<Button>
|
||||||
|
<PlusCircle className="mr-2 h-5 w-5" />
|
||||||
|
{t('admin.users.add_user_button')}
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Card className="shadow-md">
|
<Card className="shadow-md">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|
@ -44,11 +87,39 @@ export default async function AdminUserManagementPage({ params }: { params: { lo
|
||||||
<TableCell className="font-medium">{user.name}</TableCell>
|
<TableCell className="font-medium">{user.name}</TableCell>
|
||||||
<TableCell>{user.email}</TableCell>
|
<TableCell>{user.email}</TableCell>
|
||||||
<TableCell>{user.role}</TableCell>
|
<TableCell>{user.role}</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right space-x-2">
|
||||||
<Button variant="outline" size="sm" disabled>
|
<Link href={`/${locale}/admin/users/edit/${user.id}`} passHref>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
<UserCog className="mr-2 h-4 w-4" />
|
<UserCog className="mr-2 h-4 w-4" />
|
||||||
{t('admin.users.edit_button')}
|
{t('admin.users.edit_button')}
|
||||||
</Button>
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<Button variant="destructive" size="sm" disabled={isDeleting}>
|
||||||
|
<Trash2 className="mr-2 h-4 w-4" />
|
||||||
|
{t('admin.users.delete_button')}
|
||||||
|
</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>{t('admin.users.delete_confirm_title')}</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
{t('admin.users.delete_confirm_description', { userName: user.name })}
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>{t('admin.users.delete_confirm_cancel')}</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={() => handleDeleteUser(user.id)}
|
||||||
|
disabled={isDeleting}
|
||||||
|
className="bg-destructive hover:bg-destructive/90"
|
||||||
|
>
|
||||||
|
{t('admin.users.delete_confirm_action')}
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
|
|
@ -62,3 +133,9 @@ export default async function AdminUserManagementPage({ params }: { params: { lo
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wrapper component to fetch data on the server and pass to the client component
|
||||||
|
export default function AdminUserManagementPageWrapper() {
|
||||||
|
const users = getAllUsers();
|
||||||
|
return <AdminUserManagementPage users={users} />;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
import db from '@/lib/db';
|
import db from '@/lib/db';
|
||||||
import type { User } from '@/types';
|
import type { User } from '@/types';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
|
import { revalidatePath } from 'next/cache';
|
||||||
|
|
||||||
interface RegisterUserResult {
|
interface RegisterUserResult {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
|
|
@ -11,16 +12,14 @@ interface RegisterUserResult {
|
||||||
user?: User;
|
user?: User;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function registerUser(data: { name: string; email: string; password?: string }): Promise<RegisterUserResult> {
|
export async function registerUser(data: { name: string; email: string; password?: string, role?: 'Admin' | 'User' }): Promise<RegisterUserResult> {
|
||||||
const { name, email } = data;
|
const { name, email, role = 'User' } = data;
|
||||||
|
|
||||||
// Basic validation
|
|
||||||
if (!name || !email) {
|
if (!name || !email) {
|
||||||
return { success: false, message: 'Name and email are required.' };
|
return { success: false, message: 'Name and email are required.' };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if user already exists
|
|
||||||
const existingUser = db.prepare('SELECT * FROM users WHERE email = ?').get(email);
|
const existingUser = db.prepare('SELECT * FROM users WHERE email = ?').get(email);
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
return { success: false, message: 'An account with this email already exists.' };
|
return { success: false, message: 'An account with this email already exists.' };
|
||||||
|
|
@ -30,7 +29,7 @@ export async function registerUser(data: { name: string; email: string; password
|
||||||
id: `user-${randomUUID()}`,
|
id: `user-${randomUUID()}`,
|
||||||
name,
|
name,
|
||||||
email,
|
email,
|
||||||
role: 'User',
|
role: role,
|
||||||
avatarUrl: '',
|
avatarUrl: '',
|
||||||
bio: ''
|
bio: ''
|
||||||
};
|
};
|
||||||
|
|
@ -41,9 +40,83 @@ export async function registerUser(data: { name: string; email: string; password
|
||||||
|
|
||||||
stmt.run(newUser);
|
stmt.run(newUser);
|
||||||
|
|
||||||
|
revalidatePath('/admin/users');
|
||||||
|
|
||||||
return { success: true, message: 'User registered successfully.', user: newUser };
|
return { success: true, message: 'User registered successfully.', user: newUser };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Registration error:', error);
|
console.error('Registration error:', error);
|
||||||
return { success: false, message: 'An unexpected error occurred during registration.' };
|
return { success: false, message: 'An unexpected error occurred during registration.' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UpdateUserResult {
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateUser(user: User): Promise<UpdateUserResult> {
|
||||||
|
if (!user.id || !user.name || !user.email || !user.role) {
|
||||||
|
return { success: false, message: 'All user fields are required for an update.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const existingUser = db.prepare('SELECT id FROM users WHERE email = ? AND id != ?').get(user.email, user.id);
|
||||||
|
if (existingUser) {
|
||||||
|
return { success: false, message: 'Another user with this email already exists.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const stmt = db.prepare(
|
||||||
|
'UPDATE users SET name = @name, email = @email, role = @role, avatarUrl = @avatarUrl, bio = @bio WHERE id = @id'
|
||||||
|
);
|
||||||
|
|
||||||
|
stmt.run({
|
||||||
|
id: user.id,
|
||||||
|
name: user.name,
|
||||||
|
email: user.email,
|
||||||
|
role: user.role,
|
||||||
|
avatarUrl: user.avatarUrl ?? '',
|
||||||
|
bio: user.bio ?? ''
|
||||||
|
});
|
||||||
|
|
||||||
|
revalidatePath('/admin/users');
|
||||||
|
revalidatePath(`/admin/users/edit/${user.id}`);
|
||||||
|
|
||||||
|
return { success: true, message: 'User updated successfully.' };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Update user error:', error);
|
||||||
|
return { success: false, message: 'An unexpected error occurred during the update.' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteUserResult {
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteUser(id: string): Promise<DeleteUserResult> {
|
||||||
|
try {
|
||||||
|
// Prevent deleting the main admin/user accounts for demo stability
|
||||||
|
if (id.startsWith('user-seed-') || id === 'admin-main') {
|
||||||
|
const user = getUserById(id);
|
||||||
|
if(user?.email === 'user@example.com' || user?.email === 'admin@example.com') {
|
||||||
|
return { success: false, message: 'This is a protected demo account and cannot be deleted.' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stmt = db.prepare('DELETE FROM users WHERE id = ?');
|
||||||
|
const info = stmt.run(id);
|
||||||
|
|
||||||
|
if (info.changes === 0) {
|
||||||
|
return { success: false, message: 'User not found or already deleted.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
revalidatePath('/admin/users');
|
||||||
|
return { success: true, message: 'User deleted successfully.' };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Delete user error:', error);
|
||||||
|
if (error.code === 'SQLITE_CONSTRAINT_FOREIGNKEY') {
|
||||||
|
return { success: false, message: 'Cannot delete user. They still have toys listed in the system.' };
|
||||||
|
}
|
||||||
|
return { success: false, message: 'An unexpected error occurred during deletion.' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ export function getOwnerProfile(ownerId: string) {
|
||||||
// --- USER OPERATIONS ---
|
// --- USER OPERATIONS ---
|
||||||
|
|
||||||
export function getAllUsers(): User[] {
|
export function getAllUsers(): User[] {
|
||||||
const stmt = db.prepare('SELECT id, name, email, role FROM users');
|
const stmt = db.prepare('SELECT id, name, email, role, avatarUrl, bio FROM users');
|
||||||
return stmt.all() as User[];
|
return stmt.all() as User[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -228,20 +228,38 @@ export default {
|
||||||
'admin.site_settings.title_saved_toast': 'Site title (mock) saved!',
|
'admin.site_settings.title_saved_toast': 'Site title (mock) saved!',
|
||||||
'admin.users.title': 'User Management',
|
'admin.users.title': 'User Management',
|
||||||
'admin.users.description': 'View and manage user accounts and permissions.',
|
'admin.users.description': 'View and manage user accounts and permissions.',
|
||||||
|
'admin.users.add_user_button': 'Add New User',
|
||||||
|
'admin.users.back_to_users_button': 'Back to User List',
|
||||||
'admin.users.table_header_name': 'Name',
|
'admin.users.table_header_name': 'Name',
|
||||||
'admin.users.table_header_email': 'Email',
|
'admin.users.table_header_email': 'Email',
|
||||||
'admin.users.table_header_role': 'Role',
|
'admin.users.table_header_role': 'Role',
|
||||||
'admin.users.table_header_actions': 'Actions',
|
'admin.users.table_header_actions': 'Actions',
|
||||||
'admin.users.edit_button': 'Edit',
|
'admin.users.edit_button': 'Edit',
|
||||||
|
'admin.users.delete_button': 'Delete',
|
||||||
'admin.users.no_users_found': 'No users found.',
|
'admin.users.no_users_found': 'No users found.',
|
||||||
'admin.toys.title': 'Toy Management',
|
'admin.users.add_user_title': 'Add a New User',
|
||||||
'admin.toys.description': 'View and manage all toy listings in the system.',
|
'admin.users.add_user_description': 'Create a new user account and assign a role.',
|
||||||
'admin.toys.table_header_name': 'Toy Name',
|
'admin.users.edit_user_title': 'Edit User',
|
||||||
'admin.toys.table_header_owner': 'Owner',
|
'admin.users.edit_user_description': "Update the user's details and role.",
|
||||||
'admin.toys.table_header_category': 'Category',
|
'admin.users.form_name_label': 'Full Name',
|
||||||
'admin.toys.table_header_actions': 'Actions',
|
'admin.users.form_email_label': 'Email Address',
|
||||||
'admin.toys.edit_button': 'Edit Toy',
|
'admin.users.form_role_label': 'Role',
|
||||||
'admin.toys.no_toys_found': 'No toys found.',
|
'admin.users.form_role_user': 'User',
|
||||||
|
'admin.users.form_role_admin': 'Admin',
|
||||||
|
'admin.users.form_create_button': 'Create User',
|
||||||
|
'admin.users.form_save_button': 'Save Changes',
|
||||||
|
'admin.users.form_creating_button': 'Creating...',
|
||||||
|
'admin.users.form_saving_button': 'Saving...',
|
||||||
|
'admin.users.delete_confirm_title': 'Are you sure?',
|
||||||
|
'admin.users.delete_confirm_description': "This action cannot be undone. This will permanently delete the user account for {userName}.",
|
||||||
|
'admin.users.delete_confirm_action': 'Yes, delete user',
|
||||||
|
'admin.users.delete_confirm_cancel': 'Cancel',
|
||||||
|
'admin.users.user_not_found': 'User not found.',
|
||||||
|
'admin.users.user_created_toast': 'User created successfully.',
|
||||||
|
'admin.users.user_updated_toast': 'User updated successfully.',
|
||||||
|
'admin.users.user_deleted_toast': 'User deleted successfully.',
|
||||||
|
'admin.users.user_delete_failed_toast': 'Failed to delete user.',
|
||||||
|
'admin.users.toast_error_title': 'An error occurred',
|
||||||
'dashboard.rental_history.title': 'My Rental History',
|
'dashboard.rental_history.title': 'My Rental History',
|
||||||
'dashboard.rental_history.description': 'View your past toy rentals.',
|
'dashboard.rental_history.description': 'View your past toy rentals.',
|
||||||
'dashboard.rental_history.no_history_title': 'No Rental History Yet',
|
'dashboard.rental_history.no_history_title': 'No Rental History Yet',
|
||||||
|
|
|
||||||
|
|
@ -228,20 +228,38 @@ export default {
|
||||||
'admin.site_settings.title_saved_toast': '網站標題(模擬)已儲存!',
|
'admin.site_settings.title_saved_toast': '網站標題(模擬)已儲存!',
|
||||||
'admin.users.title': '使用者管理',
|
'admin.users.title': '使用者管理',
|
||||||
'admin.users.description': '查看和管理使用者帳戶及權限。',
|
'admin.users.description': '查看和管理使用者帳戶及權限。',
|
||||||
|
'admin.users.add_user_button': '新增使用者',
|
||||||
|
'admin.users.back_to_users_button': '返回使用者列表',
|
||||||
'admin.users.table_header_name': '名稱',
|
'admin.users.table_header_name': '名稱',
|
||||||
'admin.users.table_header_email': '電子郵件',
|
'admin.users.table_header_email': '電子郵件',
|
||||||
'admin.users.table_header_role': '角色',
|
'admin.users.table_header_role': '角色',
|
||||||
'admin.users.table_header_actions': '操作',
|
'admin.users.table_header_actions': '操作',
|
||||||
'admin.users.edit_button': '編輯',
|
'admin.users.edit_button': '編輯',
|
||||||
|
'admin.users.delete_button': '刪除',
|
||||||
'admin.users.no_users_found': '找不到使用者。',
|
'admin.users.no_users_found': '找不到使用者。',
|
||||||
'admin.toys.title': '玩具管理',
|
'admin.users.add_user_title': '新增使用者',
|
||||||
'admin.toys.description': '查看和管理系統中的所有玩具列表。',
|
'admin.users.add_user_description': '建立一個新的使用者帳戶並分配角色。',
|
||||||
'admin.toys.table_header_name': '玩具名稱',
|
'admin.users.edit_user_title': '編輯使用者',
|
||||||
'admin.toys.table_header_owner': '擁有者',
|
'admin.users.edit_user_description': '更新使用者的詳細資訊和角色。',
|
||||||
'admin.toys.table_header_category': '類別',
|
'admin.users.form_name_label': '全名',
|
||||||
'admin.toys.table_header_actions': '操作',
|
'admin.users.form_email_label': '電子郵件地址',
|
||||||
'admin.toys.edit_button': '編輯玩具',
|
'admin.users.form_role_label': '角色',
|
||||||
'admin.toys.no_toys_found': '找不到玩具。',
|
'admin.users.form_role_user': '使用者',
|
||||||
|
'admin.users.form_role_admin': '管理員',
|
||||||
|
'admin.users.form_create_button': '建立使用者',
|
||||||
|
'admin.users.form_save_button': '儲存變更',
|
||||||
|
'admin.users.form_creating_button': '建立中...',
|
||||||
|
'admin.users.form_saving_button': '儲存中...',
|
||||||
|
'admin.users.delete_confirm_title': '您確定嗎?',
|
||||||
|
'admin.users.delete_confirm_description': "此操作無法復原。這將永久刪除 {userName} 的使用者帳戶。",
|
||||||
|
'admin.users.delete_confirm_action': '是的,刪除使用者',
|
||||||
|
'admin.users.delete_confirm_cancel': '取消',
|
||||||
|
'admin.users.user_not_found': '找不到使用者。',
|
||||||
|
'admin.users.user_created_toast': '使用者已成功建立。',
|
||||||
|
'admin.users.user_updated_toast': '使用者已成功更新。',
|
||||||
|
'admin.users.user_deleted_toast': '使用者已成功刪除。',
|
||||||
|
'admin.users.user_delete_failed_toast': '刪除使用者失敗。',
|
||||||
|
'admin.users.toast_error_title': '發生錯誤',
|
||||||
'dashboard.rental_history.title': '我的租借歷史',
|
'dashboard.rental_history.title': '我的租借歷史',
|
||||||
'dashboard.rental_history.description': '查看您過去的玩具租借記錄。',
|
'dashboard.rental_history.description': '查看您過去的玩具租借記錄。',
|
||||||
'dashboard.rental_history.no_history_title': '尚無租借歷史',
|
'dashboard.rental_history.no_history_title': '尚無租借歷史',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue