fix: enhance localization messages and update user interface components

- Added new localization messages for logout failure and success notifications in English and Chinese.
- Updated `SidebarUser` and `UserButton` components to display the username instead of the name for better clarity.
- Improved `UpdateNameCard` to handle username updates with appropriate success and error messages using toast notifications.
- Refactored `MainMobileMenu` to utilize the translation function directly for login and sign-up links, enhancing consistency across the application.
This commit is contained in:
javayhu 2025-03-15 21:47:33 +08:00
parent c497d71757
commit c0c60f2f2f
6 changed files with 76 additions and 31 deletions

View File

@ -12,7 +12,8 @@
"light": "Light",
"dark": "Dark",
"system": "System",
"copy": "Copy"
"copy": "Copy",
"logoutFailed": "Failed to log out"
},
"Metadata": {
"title": "MkSaaS - The Best AI SaaS Boilerplate",
@ -304,7 +305,9 @@
"placeholder": "Enter your name",
"minLength": "Please use 3 characters at minimum",
"maxLength": "Please use 30 characters at maximum",
"hint": "Please use 3-30 characters for your name"
"hint": "Please use 3-30 characters for your name",
"success": "Name updated successfully",
"fail": "Failed to update name"
},
"password": {
"title": "Change Password",

View File

@ -295,12 +295,14 @@
"uploadAvatar": "上传头像"
},
"name": {
"title": "名",
"title": "",
"description": "请输入您的名字",
"placeholder": "输入您的名字",
"placeholder": "名字",
"minLength": "请至少使用 3 个字符",
"maxLength": "请最多使用 30 个字符",
"hint": "请使用 3-30 个字符"
"hint": "请使用 3-30 个字符",
"success": "名字更新成功",
"fail": "更新名字失败"
},
"password": {
"title": "修改密码",

View File

@ -35,6 +35,7 @@ import { useTheme } from 'next-themes';
import { useParams } from 'next/navigation';
import { useTransition } from 'react';
import { UserAvatar } from '../shared/user-avatar';
import { toast } from 'sonner';
export function SidebarUser() {
const { setTheme } = useTheme();
@ -75,7 +76,7 @@ export function SidebarUser() {
},
onError: (error) => {
console.error('sign out error:', error);
// TODO: show error message
toast.error(t('Common.logoutFailed'));
},
},
});
@ -92,14 +93,14 @@ export function SidebarUser() {
data-[state=open]:text-sidebar-accent-foreground"
>
<UserAvatar
name={user.name || undefined}
name={user.username || undefined}
image={user.image || undefined}
className="size-8 border"
/>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
{user.name}
{user.username}
</span>
<span className="truncate text-xs">
{user.email}
@ -117,12 +118,12 @@ export function SidebarUser() {
<DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<UserAvatar
name={user.name || undefined}
name={user.username || undefined}
image={user.image || undefined}
className="size-8 border"
/>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">{user.name}</span>
<span className="truncate font-semibold">{user.username}</span>
<span className="truncate text-xs">{user.email}</span>
</div>
</div>

View File

@ -130,7 +130,6 @@ function MainMobileMenu({ userLoggedIn, onLinkClicked }: MainMobileMenuProps) {
const t = useTranslations();
const translator = createTranslator(t);
const menuLinks = getMenuLinks(translator);
const commonTranslations = useTranslations('Common');
const localePathname = useLocalePathname();
return (
@ -153,7 +152,7 @@ function MainMobileMenu({ userLoggedIn, onLinkClicked }: MainMobileMenuProps) {
'w-full'
)}
>
{commonTranslations('login')}
{t('Common.login')}
</LocaleLink>
<LocaleLink
href={Routes.Register}
@ -166,7 +165,7 @@ function MainMobileMenu({ userLoggedIn, onLinkClicked }: MainMobileMenuProps) {
)}
onClick={onLinkClicked}
>
{commonTranslations('signUp')}
{t('Common.signUp')}
</LocaleLink>
</div>
)}

View File

@ -32,7 +32,6 @@ export function UserButton() {
const t = useTranslations();
const translator = createTranslator(t);
const avatarLinks = getAvatarLinks(translator);
const commonTranslations = useTranslations('Common');
const handleSignOut = async () => {
await authClient.signOut({
@ -63,7 +62,7 @@ export function UserButton() {
<Drawer open={open} onClose={closeDrawer}>
<DrawerTrigger onClick={() => setOpen(true)}>
<UserAvatar
name={user?.name || undefined}
name={user?.username || undefined}
image={user?.image || undefined}
className="size-8 border"
/>
@ -79,12 +78,14 @@ export function UserButton() {
</DrawerHeader>
<div className="flex items-center justify-start gap-4 p-2">
<UserAvatar
name={user?.name || undefined}
name={user?.username || undefined}
image={user?.image || undefined}
className="size-8 border"
/>
<div className="flex flex-col">
{user?.name && <p className="font-medium">{user.name}</p>}
{user?.username && <p className="font-medium">
{user.username}
</p>}
{user?.email && (
<p className="w-[200px] truncate text-muted-foreground">
{user?.email}
@ -125,7 +126,7 @@ export function UserButton() {
className="flex w-full items-center gap-3 px-2.5 py-2"
>
<LogOutIcon className="size-4" />
<p className="text-sm">{commonTranslations('logout')}</p>
<p className="text-sm">{t('Common.logout')}</p>
</a>
</li>
</ul>
@ -140,7 +141,7 @@ export function UserButton() {
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger>
<UserAvatar
name={user?.name || undefined}
name={user?.username || undefined}
image={user?.image || undefined}
className="size-8 border"
/>
@ -148,7 +149,9 @@ export function UserButton() {
<DropdownMenuContent align="end">
<div className="flex items-center justify-start gap-2 p-2">
<div className="flex flex-col space-y-1 leading-none">
{user?.name && <p className="font-medium">{user.name}</p>}
{user?.username && <p className="font-medium">
{user.username}
</p>}
{user?.email && (
<p className="w-[200px] truncate text-sm text-muted-foreground">
{user?.email}
@ -186,7 +189,7 @@ export function UserButton() {
>
<div className="flex items-center space-x-2.5">
<LogOutIcon className="size-4" />
<p className="text-sm">{commonTranslations('logout')}</p>
<p className="text-sm">{t('Common.logout')}</p>
</div>
</DropdownMenuItem>
</DropdownMenuContent>

View File

@ -20,14 +20,22 @@ import { Input } from '@/components/ui/input';
import { authClient } from '@/lib/auth-client';
import { zodResolver } from '@hookform/resolvers/zod';
import { useTranslations } from 'next-intl';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import { z } from 'zod';
/**
* update user name
*
* NOTICE: we update username instead of name in user table
*
* https://www.better-auth.com/docs/plugins/username
*/
export function UpdateNameCard() {
const t = useTranslations('Dashboard.sidebar.settings.items.account');
const [isSaving, setIsSaving] = useState(false);
const { data: session, error } = authClient.useSession();
const { data: session, refetch, error } = authClient.useSession();
// Create a schema for name validation
const formSchema = z.object({
@ -41,10 +49,16 @@ export function UpdateNameCard() {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
name: session?.user?.name || '',
name: session?.user?.username || '',
},
});
useEffect(() => {
if (session?.user?.username) {
form.setValue('name', session.user.username);
}
}, [session, form]);
// Check if user exists after all hooks are initialized
const user = session?.user;
if (!user) {
@ -53,14 +67,37 @@ export function UpdateNameCard() {
// Handle form submission
const onSubmit = async (values: z.infer<typeof formSchema>) => {
setIsSaving(true);
// Don't update if the name hasn't changed
if (values.name === session?.user?.username) {
console.log("No changes to save");
return;
}
// Here you would typically send the data to your server
// For now, we're just simulating the request
setTimeout(() => {
setIsSaving(false);
// In a real implementation, you would handle the response from your server
}, 1000);
const { data, error } = await authClient.updateUser(
{
username: values.name,
},
{
onRequest: (ctx) => {
// console.log('update name, request:', ctx.url);
setIsSaving(true);
},
onResponse: (ctx) => {
// console.log('update name, response:', ctx.response);
setIsSaving(false);
},
onSuccess: (ctx) => {
// update name success, user information stored in ctx.data
// console.log("update name, success:", ctx.data);
toast.success(t('name.success'));
refetch();
},
onError: (ctx) => {
// update name fail, display the error message
console.error('update name, error:', ctx.error);
toast.error(t('name.fail'));
},
});
};
return (