I see this error with the app, reported by NextJS, please fix it. The er

This commit is contained in:
Indigo Tang 2025-07-06 12:47:10 +00:00
parent 7e09e249a7
commit c63e4d3648
11 changed files with 83 additions and 34 deletions

View File

@ -18,7 +18,8 @@ import { useI18n, useCurrentLocale } from '@/locales/client';
import { registerUser, updateUser } from '@/app/actions/user'; import { registerUser, updateUser } from '@/app/actions/user';
const formSchema = z.object({ const formSchema = z.object({
name: z.string().min(2, "Name must be at least 2 characters."), name: z.string().min(2, "Full name must be at least 2 characters."),
nickname: z.string().optional(),
email: z.string().email("Invalid email address."), email: z.string().email("Invalid email address."),
role: z.enum(['User', 'Admin']), role: z.enum(['User', 'Admin']),
}); });
@ -40,6 +41,7 @@ export default function UserForm({ initialData, isEditMode = false }: UserFormPr
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
name: initialData?.name || '', name: initialData?.name || '',
nickname: initialData?.nickname || '',
email: initialData?.email || '', email: initialData?.email || '',
role: initialData?.role || 'User', role: initialData?.role || 'User',
}, },
@ -85,7 +87,20 @@ export default function UserForm({ initialData, isEditMode = false }: UserFormPr
name="name" name="name"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t('admin.users.form_name_label')}</FormLabel> <FormLabel>{t('admin.users.form_full_name_label')}</FormLabel>
<FormControl>
<Input placeholder="e.g., John Doe" {...field} disabled={isSubmitting} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="nickname"
render={({ field }) => (
<FormItem>
<FormLabel>{t('admin.users.form_nickname_label')}</FormLabel>
<FormControl> <FormControl>
<Input placeholder="e.g., Johnny" {...field} disabled={isSubmitting} /> <Input placeholder="e.g., Johnny" {...field} disabled={isSubmitting} />
</FormControl> </FormControl>

View File

@ -102,7 +102,8 @@ export default function AdminUserManagementPage() {
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead>{t('admin.users.table_header_name')}</TableHead> <TableHead>{t('admin.users.table_header_full_name')}</TableHead>
<TableHead>{t('admin.users.table_header_nickname')}</TableHead>
<TableHead>{t('admin.users.table_header_email')}</TableHead> <TableHead>{t('admin.users.table_header_email')}</TableHead>
<TableHead>{t('admin.users.table_header_role')}</TableHead> <TableHead>{t('admin.users.table_header_role')}</TableHead>
<TableHead className="text-right">{t('admin.users.table_header_actions')}</TableHead> <TableHead className="text-right">{t('admin.users.table_header_actions')}</TableHead>
@ -112,6 +113,7 @@ export default function AdminUserManagementPage() {
{users.map((user) => ( {users.map((user) => (
<TableRow key={user.id}> <TableRow key={user.id}>
<TableCell className="font-medium">{user.name}</TableCell> <TableCell className="font-medium">{user.name}</TableCell>
<TableCell>{user.nickname}</TableCell>
<TableCell>{user.email}</TableCell> <TableCell>{user.email}</TableCell>
<TableCell>{user.role}</TableCell> <TableCell>{user.role}</TableCell>
<TableCell className="text-right space-x-2"> <TableCell className="text-right space-x-2">

View File

@ -15,6 +15,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
const mockUserProfile = { const mockUserProfile = {
name: 'Alice Wonderland', name: 'Alice Wonderland',
nickname: 'Alice',
email: 'alice@example.com', email: 'alice@example.com',
avatarUrl: 'https://placehold.co/100x100.png?text=AW', avatarUrl: 'https://placehold.co/100x100.png?text=AW',
bio: "Lover of imaginative play and sharing joy. I have a collection of classic storybooks and dress-up costumes.", bio: "Lover of imaginative play and sharing joy. I have a collection of classic storybooks and dress-up costumes.",
@ -29,6 +30,7 @@ export default function ProfilePage() {
const changeLocale = useChangeLocale(); const changeLocale = useChangeLocale();
const [name, setName] = useState(mockUserProfile.name); const [name, setName] = useState(mockUserProfile.name);
const [nickname, setNickname] = useState(mockUserProfile.nickname);
const [email, setEmail] = useState(mockUserProfile.email); const [email, setEmail] = useState(mockUserProfile.email);
const [avatarUrl, setAvatarUrl] = useState(mockUserProfile.avatarUrl); const [avatarUrl, setAvatarUrl] = useState(mockUserProfile.avatarUrl);
const [bio, setBio] = useState(mockUserProfile.bio); const [bio, setBio] = useState(mockUserProfile.bio);
@ -52,7 +54,7 @@ export default function ProfilePage() {
e.preventDefault(); e.preventDefault();
setIsLoading(true); setIsLoading(true);
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
console.log("Updating profile:", { name, avatarUrl, bio, phone, location }); console.log("Updating profile:", { name, nickname, avatarUrl, bio, phone, location });
toast({ title: t('dashboard.profile.save_button'), description: "Your profile information has been saved." }); toast({ title: t('dashboard.profile.save_button'), description: "Your profile information has been saved." });
setIsLoading(false); setIsLoading(false);
}; };
@ -117,14 +119,19 @@ export default function ProfilePage() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-1"> <div className="space-y-1">
<Label htmlFor="name">{t('dashboard.profile.full_name_label')}</Label> <Label htmlFor="name">{t('dashboard.profile.full_name_label')}</Label>
<Input id="name" value={name} onChange={(e) => setName(e.target.value)} placeholder="Your Nickname" disabled={isLoading} /> <Input id="name" value={name} onChange={(e) => setName(e.target.value)} placeholder="Your Full Name" required disabled={isLoading} />
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<Label htmlFor="email">{t('dashboard.profile.email_label')}</Label> <Label htmlFor="nickname">{t('dashboard.profile.nickname_label')}</Label>
<Input id="email" type="email" value={email} readOnly disabled className="bg-muted/50 cursor-not-allowed" /> <Input id="nickname" value={nickname} onChange={(e) => setNickname(e.target.value)} placeholder="Your Nickname" disabled={isLoading} />
</div> </div>
</div> </div>
<div className="space-y-1">
<Label htmlFor="email">{t('dashboard.profile.email_label')}</Label>
<Input id="email" type="email" value={email} readOnly disabled className="bg-muted/50 cursor-not-allowed" />
</div>
<div className="space-y-1"> <div className="space-y-1">
<Label htmlFor="bio">{t('dashboard.profile.bio_label')}</Label> <Label htmlFor="bio">{t('dashboard.profile.bio_label')}</Label>
<Textarea id="bio" value={bio} onChange={(e) => setBio(e.target.value)} placeholder="Tell us a bit about yourself and your toys..." rows={3} disabled={isLoading}/> <Textarea id="bio" value={bio} onChange={(e) => setBio(e.target.value)} placeholder="Tell us a bit about yourself and your toys..." rows={3} disabled={isLoading}/>

View File

@ -20,6 +20,7 @@ export default function RegisterPage() {
const locale = useCurrentLocale(); const locale = useCurrentLocale();
const [name, setName] = useState(''); const [name, setName] = useState('');
const [nickname, setNickname] = useState('');
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState('');
@ -39,7 +40,7 @@ export default function RegisterPage() {
return; return;
} }
const result = await registerUser({ name, email }); const result = await registerUser({ name, nickname, email });
if (result.success && result.user) { if (result.success && result.user) {
// Mock login after successful registration // Mock login after successful registration
@ -85,16 +86,27 @@ export default function RegisterPage() {
<CardContent> <CardContent>
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="name">{t('register.name_label')}</Label> <Label htmlFor="name">{t('register.full_name_label')}</Label>
<Input <Input
id="name" id="name"
type="text" type="text"
placeholder="e.g., Johnny" placeholder="e.g., John Doe"
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
required required
disabled={isLoading} disabled={isLoading}
/> />
</div>
<div className="space-y-2">
<Label htmlFor="nickname">{t('register.nickname_label')}</Label>
<Input
id="nickname"
type="text"
placeholder="e.g., Johnny"
value={nickname}
onChange={(e) => setNickname(e.target.value)}
disabled={isLoading}
/>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="email">{t('login.email_label')}</Label> <Label htmlFor="email">{t('login.email_label')}</Label>

View File

@ -13,11 +13,11 @@ interface RegisterUserResult {
user?: User; user?: User;
} }
export async function registerUser(data: { name: string; email: string; password?: string, role?: 'Admin' | 'User' }): Promise<RegisterUserResult> { export async function registerUser(data: { name: string; nickname?: string; email: string; password?: string, role?: 'Admin' | 'User' }): Promise<RegisterUserResult> {
const { name, email, role = 'User' } = data; const { name, nickname, email, role = 'User' } = data;
if (!name || !email) { if (!name || !email) {
return { success: false, message: 'Name and email are required.' }; return { success: false, message: 'Full name and email are required.' };
} }
try { try {
@ -29,6 +29,7 @@ export async function registerUser(data: { name: string; email: string; password
const newUser: User = { const newUser: User = {
id: `user-${randomUUID()}`, id: `user-${randomUUID()}`,
name, name,
nickname,
email, email,
role: role, role: role,
avatarUrl: '', avatarUrl: '',
@ -36,7 +37,7 @@ export async function registerUser(data: { name: string; email: string; password
}; };
const stmt = db.prepare( const stmt = db.prepare(
'INSERT INTO users (id, name, email, role, avatarUrl, bio) VALUES (@id, @name, @email, @role, @avatarUrl, @bio)' 'INSERT INTO users (id, name, nickname, email, role, avatarUrl, bio) VALUES (@id, @name, @nickname, @email, @role, @avatarUrl, @bio)'
); );
stmt.run(newUser); stmt.run(newUser);
@ -67,12 +68,13 @@ export async function updateUser(user: User): Promise<UpdateUserResult> {
} }
const stmt = db.prepare( const stmt = db.prepare(
'UPDATE users SET name = @name, email = @email, role = @role, avatarUrl = @avatarUrl, bio = @bio WHERE id = @id' 'UPDATE users SET name = @name, nickname = @nickname, email = @email, role = @role, avatarUrl = @avatarUrl, bio = @bio WHERE id = @id'
); );
stmt.run({ stmt.run({
id: user.id, id: user.id,
name: user.name, name: user.name,
nickname: user.nickname ?? null,
email: user.email, email: user.email,
role: user.role, role: user.role,
avatarUrl: user.avatarUrl ?? '', avatarUrl: user.avatarUrl ?? '',

View File

@ -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, avatarUrl, bio FROM users'); const stmt = db.prepare('SELECT id, name, nickname, email, role, avatarUrl, bio FROM users');
return stmt.all() as User[]; return stmt.all() as User[];
} }

View File

@ -22,6 +22,7 @@ function initDb() {
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
name TEXT NOT NULL, name TEXT NOT NULL,
nickname TEXT,
email TEXT NOT NULL UNIQUE, email TEXT NOT NULL UNIQUE,
role TEXT, role TEXT,
avatarUrl TEXT, avatarUrl TEXT,
@ -50,8 +51,8 @@ function initDb() {
// Use INSERT OR IGNORE to only add data if the primary key doesn't exist. // Use INSERT OR IGNORE to only add data if the primary key doesn't exist.
// This prevents errors on subsequent runs and adds missing users/toys without overwriting. // This prevents errors on subsequent runs and adds missing users/toys without overwriting.
const insertUser = db.prepare(` const insertUser = db.prepare(`
INSERT OR IGNORE INTO users (id, name, email, role, avatarUrl, bio) INSERT OR IGNORE INTO users (id, name, nickname, email, role, avatarUrl, bio)
VALUES (@id, @name, @email, @role, @avatarUrl, @bio) VALUES (@id, @name, @nickname, @email, @role, @avatarUrl, @bio)
`); `);
const insertToy = db.prepare(` const insertToy = db.prepare(`
@ -64,6 +65,7 @@ function initDb() {
insertUser.run({ insertUser.run({
id: user.id, id: user.id,
name: user.name, name: user.name,
nickname: user.nickname ?? null,
email: user.email, email: user.email,
role: user.role ?? 'User', role: user.role ?? 'User',
avatarUrl: user.avatarUrl ?? null, avatarUrl: user.avatarUrl ?? null,

View File

@ -5,13 +5,13 @@ import { addDays, formatISO, subDays } from 'date-fns';
const today = new Date(); const today = new Date();
export const rawUsers: User[] = [ export const rawUsers: User[] = [
{ id: 'user1', name: 'Alice Wonderland', email: 'user@example.com', role: 'Admin', avatarUrl: 'https://placehold.co/100x100.png?text=AW', bio: "Lover of imaginative play and sharing joy. I have a collection of classic storybooks and dress-up costumes that my kids have outgrown but still have lots of life left in them!" }, { id: 'user1', name: 'Alice Wonderland', nickname: 'Alice', email: 'user@example.com', role: 'Admin', avatarUrl: 'https://placehold.co/100x100.png?text=AW', bio: "Lover of imaginative play and sharing joy. I have a collection of classic storybooks and dress-up costumes that my kids have outgrown but still have lots of life left in them!" },
{ id: 'user2', name: 'Bob The Builder', email: 'user2@example.com', role: 'User', avatarUrl: 'https://placehold.co/100x100.png?text=BT', bio: "Can we fix it? Yes, we can! Sharing my collection of construction toys, tools, and playsets. Always happy to help another budding builder." }, { id: 'user2', name: 'Bob The Builder', nickname: 'Bob', email: 'user2@example.com', role: 'User', avatarUrl: 'https://placehold.co/100x100.png?text=BT', bio: "Can we fix it? Yes, we can! Sharing my collection of construction toys, tools, and playsets. Always happy to help another budding builder." },
{ id: 'user3', name: 'Carol Danvers', email: 'user3@example.com', role: 'User', avatarUrl: 'https://placehold.co/100x100.png?text=CD', bio: "Higher, further, faster. Sharing toys that inspire adventure, courage, and exploration. My collection includes superhero action figures and space-themed playsets." }, { id: 'user3', name: 'Carol Danvers', nickname: 'Captain Marvel', email: 'user3@example.com', role: 'User', avatarUrl: 'https://placehold.co/100x100.png?text=CD', bio: "Higher, further, faster. Sharing toys that inspire adventure, courage, and exploration. My collection includes superhero action figures and space-themed playsets." },
{ id: 'user4', name: 'Charlie Brown', email: 'user4@example.com', role: 'User' }, { id: 'user4', name: 'Charlie Brown', nickname: 'Chuck', email: 'user4@example.com', role: 'User' },
{ id: 'user5', name: 'Diana Prince', email: 'user5@example.com', role: 'User' }, { id: 'user5', name: 'Diana Prince', nickname: 'Wonder Woman', email: 'user5@example.com', role: 'User' },
{ id: 'user6', name: 'Edward Nigma', email: 'user6@example.com', role: 'User' }, { id: 'user6', name: 'Edward Nigma', nickname: 'Riddler', email: 'user6@example.com', role: 'User' },
{ id: 'admin-main', name: 'Main Admin', email: 'admin@example.com', role: 'Admin' }, { id: 'admin-main', name: 'Main Admin', nickname: 'Head Honcho', email: 'admin@example.com', role: 'Admin' },
]; ];
export const rawToys: Omit<Toy, 'ownerName' | 'dataAiHint'>[] = [ export const rawToys: Omit<Toy, 'ownerName' | 'dataAiHint'>[] = [

View File

@ -31,7 +31,8 @@ export default {
'login.invalid_credentials_toast_user': 'Invalid email or password. (Hint: user@example.com / password or admin@example.com / passwordadmin)', 'login.invalid_credentials_toast_user': 'Invalid email or password. (Hint: user@example.com / password or admin@example.com / passwordadmin)',
'register.create_account': 'Create Your Account', 'register.create_account': 'Create Your Account',
'register.description': 'Join ToyShare and start sharing the fun!', 'register.description': 'Join ToyShare and start sharing the fun!',
'register.name_label': 'Nickname', 'register.full_name_label': 'Full Name',
'register.nickname_label': 'Nickname (Optional)',
'register.confirm_password_label': 'Confirm Password', 'register.confirm_password_label': 'Confirm Password',
'register.submit_button': 'Create Account', 'register.submit_button': 'Create Account',
'register.loading_button': 'Registering...', 'register.loading_button': 'Registering...',
@ -96,7 +97,8 @@ export default {
'dashboard.profile.personal_info_title': 'Personal Information', 'dashboard.profile.personal_info_title': 'Personal Information',
'dashboard.profile.personal_info_description': 'Update your publicly visible profile information.', 'dashboard.profile.personal_info_description': 'Update your publicly visible profile information.',
'dashboard.profile.avatar_url_label': 'Avatar URL', 'dashboard.profile.avatar_url_label': 'Avatar URL',
'dashboard.profile.full_name_label': 'Nickname', 'dashboard.profile.full_name_label': 'Full Name',
'dashboard.profile.nickname_label': 'Nickname',
'dashboard.profile.email_label': 'Email Address (Read-only)', 'dashboard.profile.email_label': 'Email Address (Read-only)',
'dashboard.profile.bio_label': 'Bio', 'dashboard.profile.bio_label': 'Bio',
'dashboard.profile.phone_label': 'Phone Number', 'dashboard.profile.phone_label': 'Phone Number',
@ -230,7 +232,8 @@ export default {
'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.add_user_button': 'Add New User',
'admin.users.back_to_users_button': 'Back to User List', 'admin.users.back_to_users_button': 'Back to User List',
'admin.users.table_header_name': 'Nickname', 'admin.users.table_header_full_name': 'Full Name',
'admin.users.table_header_nickname': 'Nickname',
'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',
@ -241,7 +244,8 @@ export default {
'admin.users.add_user_description': 'Create a new user account and assign a role.', 'admin.users.add_user_description': 'Create a new user account and assign a role.',
'admin.users.edit_user_title': 'Edit User', 'admin.users.edit_user_title': 'Edit User',
'admin.users.edit_user_description': "Update the user's details and role.", 'admin.users.edit_user_description': "Update the user's details and role.",
'admin.users.form_name_label': 'Nickname', 'admin.users.form_full_name_label': 'Full Name',
'admin.users.form_nickname_label': 'Nickname (Optional)',
'admin.users.form_email_label': 'Email Address', 'admin.users.form_email_label': 'Email Address',
'admin.users.form_role_label': 'Role', 'admin.users.form_role_label': 'Role',
'admin.users.form_role_user': 'User', 'admin.users.form_role_user': 'User',

View File

@ -31,7 +31,8 @@ export default {
'login.invalid_credentials_toast_user': '無效的電子郵件或密碼。(提示: user@example.com / password 或 admin@example.com / passwordadmin)', 'login.invalid_credentials_toast_user': '無效的電子郵件或密碼。(提示: user@example.com / password 或 admin@example.com / passwordadmin)',
'register.create_account': '建立您的帳戶', 'register.create_account': '建立您的帳戶',
'register.description': '加入 ToyShare開始分享樂趣', 'register.description': '加入 ToyShare開始分享樂趣',
'register.name_label': '暱稱', 'register.full_name_label': '全名',
'register.nickname_label': '暱稱 (選填)',
'register.confirm_password_label': '確認密碼', 'register.confirm_password_label': '確認密碼',
'register.submit_button': '建立帳戶', 'register.submit_button': '建立帳戶',
'register.loading_button': '註冊中...', 'register.loading_button': '註冊中...',
@ -96,7 +97,8 @@ export default {
'dashboard.profile.personal_info_title': '個人資訊', 'dashboard.profile.personal_info_title': '個人資訊',
'dashboard.profile.personal_info_description': '更新您公開顯示的個人資料資訊。', 'dashboard.profile.personal_info_description': '更新您公開顯示的個人資料資訊。',
'dashboard.profile.avatar_url_label': '頭像 URL', 'dashboard.profile.avatar_url_label': '頭像 URL',
'dashboard.profile.full_name_label': '暱稱', 'dashboard.profile.full_name_label': '全名',
'dashboard.profile.nickname_label': '暱稱',
'dashboard.profile.email_label': '電子郵件地址 (唯讀)', 'dashboard.profile.email_label': '電子郵件地址 (唯讀)',
'dashboard.profile.bio_label': '簡介', 'dashboard.profile.bio_label': '簡介',
'dashboard.profile.phone_label': '電話號碼', 'dashboard.profile.phone_label': '電話號碼',
@ -230,7 +232,8 @@ export default {
'admin.users.description': '查看和管理使用者帳戶及權限。', 'admin.users.description': '查看和管理使用者帳戶及權限。',
'admin.users.add_user_button': '新增使用者', 'admin.users.add_user_button': '新增使用者',
'admin.users.back_to_users_button': '返回使用者列表', 'admin.users.back_to_users_button': '返回使用者列表',
'admin.users.table_header_name': '暱稱', 'admin.users.table_header_full_name': '全名',
'admin.users.table_header_nickname': '暱稱',
'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': '操作',
@ -241,7 +244,8 @@ export default {
'admin.users.add_user_description': '建立一個新的使用者帳戶並分配角色。', 'admin.users.add_user_description': '建立一個新的使用者帳戶並分配角色。',
'admin.users.edit_user_title': '編輯使用者', 'admin.users.edit_user_title': '編輯使用者',
'admin.users.edit_user_description': '更新使用者的詳細資訊和角色。', 'admin.users.edit_user_description': '更新使用者的詳細資訊和角色。',
'admin.users.form_name_label': '暱稱', 'admin.users.form_full_name_label': '全名',
'admin.users.form_nickname_label': '暱稱 (選填)',
'admin.users.form_email_label': '電子郵件地址', 'admin.users.form_email_label': '電子郵件地址',
'admin.users.form_role_label': '角色', 'admin.users.form_role_label': '角色',
'admin.users.form_role_user': '使用者', 'admin.users.form_role_user': '使用者',

View File

@ -23,7 +23,8 @@ export interface Toy {
export interface User { export interface User {
id: string; id: string;
name: string; name: string; // This is now Full Name
nickname?: string;
email: string; email: string;
role?: 'Admin' | 'User'; role?: 'Admin' | 'User';
avatarUrl?: string; avatarUrl?: string;