mirror of
https://github.com/stackblitz/bolt.new
synced 2025-06-26 18:17:50 +00:00
feat: 重构用户菜单组件,引入新的对话框组件以替代链接跳转方式,优化用户交互体验。
This commit is contained in:
parent
df93f0d897
commit
26fa196bdb
38
app/components/auth/ProfileDialog.tsx
Normal file
38
app/components/auth/ProfileDialog.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Dialog, DialogTitle, DialogDescription, DialogRoot } from '~/components/ui/Dialog';
|
||||||
|
import { useAuth } from '~/hooks/useAuth';
|
||||||
|
|
||||||
|
interface ProfileDialogProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ProfileDialog({ isOpen, onClose }: ProfileDialogProps) {
|
||||||
|
const { user } = useAuth();
|
||||||
|
|
||||||
|
if (!user) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogRoot open={isOpen}>
|
||||||
|
<Dialog onBackdrop={onClose} onClose={onClose}>
|
||||||
|
<DialogTitle>个人信息</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-bolt-elements-textPrimary">
|
||||||
|
昵称
|
||||||
|
</label>
|
||||||
|
<p className="mt-1 text-bolt-elements-textSecondary">{user.nickname}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-bolt-elements-textPrimary">
|
||||||
|
手机号
|
||||||
|
</label>
|
||||||
|
<p className="mt-1 text-bolt-elements-textSecondary">{user.phone}</p>
|
||||||
|
</div>
|
||||||
|
{/* 可以根据需要添加更多用户信息 */}
|
||||||
|
</div>
|
||||||
|
</DialogDescription>
|
||||||
|
</Dialog>
|
||||||
|
</DialogRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
24
app/components/auth/SubscriptionDialog.tsx
Normal file
24
app/components/auth/SubscriptionDialog.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { Dialog, DialogTitle, DialogDescription, DialogRoot } from '~/components/ui/Dialog';
|
||||||
|
|
||||||
|
interface SubscriptionDialogProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SubscriptionDialog({ isOpen, onClose }: SubscriptionDialogProps) {
|
||||||
|
return (
|
||||||
|
<DialogRoot open={isOpen}>
|
||||||
|
<Dialog onBackdrop={onClose} onClose={onClose}>
|
||||||
|
<DialogTitle>订阅信息</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-bolt-elements-textSecondary">
|
||||||
|
这里显示用户的订阅信息。您可以根据实际需求添加更多详细内容。
|
||||||
|
</p>
|
||||||
|
{/* 可以添加更多订阅相关的信息 */}
|
||||||
|
</div>
|
||||||
|
</DialogDescription>
|
||||||
|
</Dialog>
|
||||||
|
</DialogRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,74 +1,82 @@
|
|||||||
import { Menu, Transition } from '@headlessui/react';
|
import { Menu, Transition } from '@headlessui/react';
|
||||||
import { Fragment } from 'react';
|
import { Fragment, useState } from 'react';
|
||||||
import { useAuth } from '~/hooks/useAuth';
|
import { useAuth } from '~/hooks/useAuth';
|
||||||
import { Avatar } from '~/components/ui/Avatar';
|
import { Avatar } from '~/components/ui/Avatar';
|
||||||
import { Link } from '@remix-run/react';
|
import { ProfileDialog } from '~/components/auth/ProfileDialog';
|
||||||
|
import { SubscriptionDialog } from '~/components/auth/SubscriptionDialog';
|
||||||
|
|
||||||
export function UserMenu() {
|
export function UserMenu() {
|
||||||
const { user, logout } = useAuth();
|
const { user, logout } = useAuth();
|
||||||
|
const [isProfileOpen, setIsProfileOpen] = useState(false);
|
||||||
|
const [isSubscriptionOpen, setIsSubscriptionOpen] = useState(false);
|
||||||
|
|
||||||
if (!user) return null;
|
if (!user) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu as="div" className="relative inline-block text-left">
|
<>
|
||||||
<div>
|
<Menu as="div" className="relative inline-block text-left">
|
||||||
<Menu.Button className="inline-flex items-center gap-x-1.5 rounded-md bg-bolt-elements-background-depth-2 px-3 py-2 text-sm font-medium text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-3 transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-bolt-elements-button-primary-background">
|
<div>
|
||||||
<Avatar src={user.avatarUrl} alt={user.nickname} />
|
<Menu.Button className="inline-flex items-center gap-x-1.5 rounded-md bg-bolt-elements-background-depth-1 px-3 py-2 text-sm font-medium text-bolt-elements-textPrimary hover:bg-bolt-elements-background-depth-2 transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-bolt-elements-button-primary-background">
|
||||||
<span className="ml-2">{user.nickname}</span>
|
<Avatar src={user.avatarUrl} alt={user.nickname} />
|
||||||
<div className="i-ph:caret-down-fill text-bolt-elements-textSecondary" aria-hidden="true" />
|
<span className="ml-2">{user.nickname}</span>
|
||||||
</Menu.Button>
|
<div className="i-ph:caret-down-fill text-bolt-elements-textSecondary" aria-hidden="true" />
|
||||||
</div>
|
</Menu.Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Transition
|
<Transition
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
enter="transition ease-out duration-100"
|
enter="transition ease-out duration-100"
|
||||||
enterFrom="transform opacity-0 scale-95"
|
enterFrom="transform opacity-0 scale-95"
|
||||||
enterTo="transform opacity-100 scale-100"
|
enterTo="transform opacity-100 scale-100"
|
||||||
leave="transition ease-in duration-75"
|
leave="transition ease-in duration-75"
|
||||||
leaveFrom="transform opacity-100 scale-100"
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
leaveTo="transform opacity-0 scale-95"
|
leaveTo="transform opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<Menu.Items className="absolute right-0 z-10 mt-2 w-56 origin-top-right rounded-md bg-bolt-elements-background-depth-2 shadow-lg ring-1 ring-bolt-elements-borderColor focus:outline-none">
|
<Menu.Items className="absolute right-0 z-10 mt-2 w-56 origin-top-right rounded-md bg-bolt-elements-background-depth-1 shadow-lg ring-1 ring-bolt-elements-borderColor focus:outline-none">
|
||||||
<div className="py-1">
|
<div className="py-1">
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
<Link
|
<button
|
||||||
to="/profile"
|
onClick={() => setIsProfileOpen(true)}
|
||||||
className={`${
|
className={`${
|
||||||
active ? 'bg-bolt-elements-button-primary-background text-bolt-elements-button-primary-text' : 'text-bolt-elements-textSecondary'
|
active ? 'bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary' : 'text-bolt-elements-textSecondary'
|
||||||
} block px-4 py-2 text-sm transition-colors duration-150 ease-in-out`}
|
} block w-full text-left px-4 py-2 text-sm transition-colors duration-150 ease-in-out`}
|
||||||
>
|
>
|
||||||
个人信息
|
个人信息
|
||||||
</Link>
|
</button>
|
||||||
)}
|
)}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
<Link
|
<button
|
||||||
to="/subscription"
|
onClick={() => setIsSubscriptionOpen(true)}
|
||||||
className={`${
|
className={`${
|
||||||
active ? 'bg-bolt-elements-button-primary-background text-bolt-elements-button-primary-text' : 'text-bolt-elements-textSecondary'
|
active ? 'bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary' : 'text-bolt-elements-textSecondary'
|
||||||
} block px-4 py-2 text-sm transition-colors duration-150 ease-in-out`}
|
} block w-full text-left px-4 py-2 text-sm transition-colors duration-150 ease-in-out`}
|
||||||
>
|
>
|
||||||
订阅信息
|
订阅信息
|
||||||
</Link>
|
</button>
|
||||||
)}
|
)}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
<button
|
<button
|
||||||
onClick={logout}
|
onClick={logout}
|
||||||
className={`${
|
className={`${
|
||||||
active ? 'bg-bolt-elements-button-primary-background text-bolt-elements-button-primary-text' : 'text-bolt-elements-textSecondary'
|
active ? 'bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary' : 'text-bolt-elements-textSecondary'
|
||||||
} block w-full text-left px-4 py-2 text-sm transition-colors duration-150 ease-in-out`}
|
} block w-full text-left px-4 py-2 text-sm transition-colors duration-150 ease-in-out`}
|
||||||
>
|
>
|
||||||
退出登录
|
退出登录
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
</div>
|
</div>
|
||||||
</Menu.Items>
|
</Menu.Items>
|
||||||
</Transition>
|
</Transition>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
|
<ProfileDialog isOpen={isProfileOpen} onClose={() => setIsProfileOpen(false)} />
|
||||||
|
<SubscriptionDialog isOpen={isSubscriptionOpen} onClose={() => setIsSubscriptionOpen(false)} />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,13 +15,25 @@ export function useAuth() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = localStorage.getItem('token');
|
const checkAuth = () => {
|
||||||
const storedUser = localStorage.getItem('user');
|
const token = localStorage.getItem('token');
|
||||||
if (token && storedUser) {
|
const storedUser = localStorage.getItem('user');
|
||||||
setIsAuthenticated(true);
|
if (token && storedUser) {
|
||||||
setUser(JSON.parse(storedUser));
|
setIsAuthenticated(true);
|
||||||
}
|
setUser(JSON.parse(storedUser));
|
||||||
setIsLoading(false);
|
} else {
|
||||||
|
setIsAuthenticated(false);
|
||||||
|
setUser(null);
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
checkAuth();
|
||||||
|
window.addEventListener('storage', checkAuth);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('storage', checkAuth);
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const login = (token: string, userData: User) => {
|
const login = (token: string, userData: User) => {
|
||||||
@ -36,6 +48,7 @@ export function useAuth() {
|
|||||||
localStorage.removeItem('user');
|
localStorage.removeItem('user');
|
||||||
setIsAuthenticated(false);
|
setIsAuthenticated(false);
|
||||||
setUser(null);
|
setUser(null);
|
||||||
|
navigate('/');
|
||||||
};
|
};
|
||||||
|
|
||||||
return { isAuthenticated, isLoading, user, login, logout };
|
return { isAuthenticated, isLoading, user, login, logout };
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user