Improve frontend layout and auth routing

This commit is contained in:
2026-05-12 09:38:51 +08:00
parent 2138e1cc0d
commit f401a5a104
8 changed files with 347 additions and 125 deletions
+94 -18
View File
@@ -3,20 +3,41 @@
import { useEffect, Suspense } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { useAuthStore } from '@/lib/auth-store';
import { getUserInfo } from '@/lib/api';
import Layout from '@/components/layout';
import { Activity, BadgeCheck, Boxes, ShieldCheck } from 'lucide-react';
const cards = [
{
title: '欢迎回来',
value: 'IMTIM Portal',
description: '您已成功登录企业门户',
icon: ShieldCheck,
},
{
title: '账户状态',
value: '已激活',
description: '当前账号可正常访问系统',
icon: BadgeCheck,
},
{
title: '系统版本',
value: '1.0.0',
description: '基础登录门户服务',
icon: Boxes,
},
];
function DashboardContent() {
const { login, initialize } = useAuthStore();
const { user, isAuthenticated, isInitialized, login, initialize } = useAuthStore();
const router = useRouter();
const searchParams = useSearchParams();
const token = searchParams.get('token');
useEffect(() => {
initialize();
}, [initialize]);
useEffect(() => {
const token = searchParams.get('token');
if (token) {
const fetchUser = async () => {
try {
@@ -39,25 +60,74 @@ function DashboardContent() {
};
fetchUser();
}
}, [searchParams, login, router]);
}, [token, login, router]);
useEffect(() => {
if (isInitialized && !isAuthenticated && !token) {
router.replace('/login');
}
}, [isInitialized, isAuthenticated, token, router]);
if (token || !isInitialized || !isAuthenticated) {
return (
<div className="mx-auto flex min-h-[calc(100svh-9.6rem)] w-full max-w-6xl items-center justify-center px-4 py-10 sm:px-6">
<div className="rounded-xl border bg-white px-5 py-4 text-sm text-muted-foreground shadow-sm">
...
</div>
</div>
);
}
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-6"></h1>
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<div className="rounded-lg border p-6 shadow-sm">
<h2 className="text-xl font-semibold mb-2"></h2>
<p className="text-muted-foreground">
IMTIM Portal
<div className="mx-auto w-full max-w-6xl px-4 py-8 sm:px-6 sm:py-10">
<div className="mb-8 flex flex-col justify-between gap-4 sm:flex-row sm:items-end">
<div>
<p className="text-sm font-medium text-primary"></p>
<h1 className="mt-2 text-3xl font-bold tracking-tight sm:text-4xl"></h1>
<p className="mt-3 max-w-2xl text-sm leading-6 text-muted-foreground sm:text-base">
{user?.username ? `${user.username},查看登录状态、账户可用性和基础系统信息。` : '查看登录状态、账户可用性和基础系统信息。'}
</p>
</div>
<div className="rounded-lg border p-6 shadow-sm">
<h2 className="text-xl font-semibold mb-2"></h2>
<p className="text-green-600 font-medium"></p>
<div className="inline-flex w-fit items-center gap-2 rounded-full border bg-white px-3 py-1.5 text-sm font-medium text-emerald-700 shadow-sm">
<Activity className="h-4 w-4" />
线
</div>
<div className="rounded-lg border p-6 shadow-sm">
<h2 className="text-xl font-semibold mb-2"></h2>
<p className="text-muted-foreground"> 1.0.0</p>
</div>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{cards.map((card) => {
const Icon = card.icon;
return (
<div key={card.title} className="rounded-xl border bg-white p-5 shadow-sm sm:p-6">
<div className="mb-5 flex items-center justify-between gap-4">
<span className="flex h-11 w-11 items-center justify-center rounded-lg bg-primary/10 text-primary">
<Icon className="h-5 w-5" />
</span>
{card.title === '账户状态' ? (
<span className="rounded-full bg-emerald-100 px-2.5 py-1 text-xs font-medium text-emerald-700">
Active
</span>
) : null}
</div>
<h2 className="text-sm font-medium text-muted-foreground">{card.title}</h2>
<p className="mt-2 text-2xl font-semibold tracking-tight">{card.value}</p>
<p className="mt-2 text-sm leading-6 text-muted-foreground">{card.description}</p>
</div>
);
})}
</div>
<div className="mt-4 rounded-xl border bg-white p-5 shadow-sm sm:mt-6 sm:p-6">
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h2 className="text-lg font-semibold"></h2>
<p className="mt-1 text-sm text-muted-foreground">
OAuth
</p>
</div>
<span className="w-fit rounded-full bg-secondary px-3 py-1 text-sm font-medium text-secondary-foreground">
Ready
</span>
</div>
</div>
</div>
@@ -67,7 +137,13 @@ function DashboardContent() {
export default function DashboardPage() {
return (
<Layout>
<Suspense fallback={<div className="container mx-auto px-4 py-8">...</div>}>
<Suspense
fallback={
<div className="mx-auto w-full max-w-6xl px-4 py-8 text-sm text-muted-foreground sm:px-6">
...
</div>
}
>
<DashboardContent />
</Suspense>
</Layout>
+46 -14
View File
@@ -1,34 +1,66 @@
@import "tailwindcss";
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--background: oklch(0.985 0.006 247.858);
--foreground: oklch(0.21 0.034 264.665);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--card-foreground: oklch(0.21 0.034 264.665);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--popover-foreground: oklch(0.21 0.034 264.665);
--primary: oklch(0.52 0.19 253.3);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--secondary: oklch(0.96 0.013 247.858);
--secondary-foreground: oklch(0.279 0.041 260.031);
--muted: oklch(0.96 0.013 247.858);
--muted-foreground: oklch(0.446 0.043 257.281);
--accent: oklch(0.94 0.025 255.585);
--accent-foreground: oklch(0.279 0.041 260.031);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--border: oklch(0.9 0.022 252.894);
--input: oklch(0.9 0.022 252.894);
--ring: oklch(0.52 0.19 253.3);
--radius: 0.5rem;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
}
* {
border-color: var(--border);
}
html {
min-width: 320px;
scroll-behavior: smooth;
}
body {
background-color: var(--background);
color: var(--foreground);
font-family: system-ui, -apple-system, sans-serif;
}
button,
a {
-webkit-tap-highlight-color: transparent;
}
+58 -13
View File
@@ -4,18 +4,22 @@ import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useAuthStore } from '@/lib/auth-store';
import { getLoginUrl } from '@/lib/api';
import { LogIn } from 'lucide-react';
import { BadgeCheck, KeyRound, LogIn } from 'lucide-react';
import Layout from '@/components/layout';
export default function LoginPage() {
const { isAuthenticated } = useAuthStore();
const { isAuthenticated, isInitialized, initialize } = useAuthStore();
const router = useRouter();
useEffect(() => {
if (isAuthenticated) {
router.push('/dashboard');
initialize();
}, [initialize]);
useEffect(() => {
if (isInitialized && isAuthenticated) {
router.replace('/dashboard');
}
}, [isAuthenticated, router]);
}, [isInitialized, isAuthenticated, router]);
const handleLogin = async () => {
try {
@@ -28,25 +32,66 @@ export default function LoginPage() {
return (
<Layout>
<div className="flex items-center justify-center min-h-[calc(100vh-12rem)]">
<div className="w-full max-w-md">
<div className="rounded-lg border p-8 shadow-sm">
<div className="text-center mb-8">
<h1 className="text-2xl font-bold mb-2"></h1>
<p className="text-muted-foreground">
<div className="mx-auto grid min-h-[calc(100svh-9.6rem)] w-full max-w-6xl items-center gap-10 px-4 py-10 sm:px-6 lg:grid-cols-[1fr_0.88fr]">
<section className="hidden lg:block">
<p className="text-sm font-medium text-primary"></p>
<h1 className="mt-3 max-w-xl text-4xl font-bold tracking-tight">
Casdoor IMTIM Portal
</h1>
<div className="mt-8 grid max-w-xl gap-4">
<div className="rounded-xl border bg-white p-5 shadow-sm">
<div className="flex items-start gap-4">
<span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
<KeyRound className="h-5 w-5" />
</span>
<div>
<h2 className="font-semibold"></h2>
<p className="mt-1 text-sm leading-6 text-muted-foreground">
使
</p>
</div>
</div>
</div>
<div className="rounded-xl border bg-white p-5 shadow-sm">
<div className="flex items-start gap-4">
<span className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-emerald-100 text-emerald-700">
<BadgeCheck className="h-5 w-5" />
</span>
<div>
<h2 className="font-semibold"></h2>
<p className="mt-1 text-sm leading-6 text-muted-foreground">
</p>
</div>
</div>
</div>
</div>
</section>
<section className="mx-auto w-full max-w-md lg:mx-0 lg:justify-self-end">
<div className="rounded-xl border bg-white p-6 shadow-sm sm:p-8">
<div className="mb-8">
<div className="mb-5 flex h-12 w-12 items-center justify-center rounded-xl bg-primary text-primary-foreground">
<LogIn className="h-6 w-6" />
</div>
<h1 className="text-2xl font-semibold tracking-tight"></h1>
<p className="mt-2 text-sm leading-6 text-muted-foreground">
使 Casdoor IMTIM Portal
</p>
</div>
<button
onClick={handleLogin}
className="w-full flex items-center justify-center gap-2 px-4 py-3 text-sm font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors"
className="flex h-12 w-full items-center justify-center gap-2 rounded-lg bg-primary px-4 text-sm font-medium text-primary-foreground shadow-sm transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
<LogIn className="h-5 w-5" />
<span>使 Casdoor </span>
</button>
<p className="mt-5 text-center text-xs leading-5 text-muted-foreground">
Casdoor
</p>
</div>
</div>
</section>
</div>
</Layout>
);
+87 -37
View File
@@ -3,71 +3,121 @@
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useAuthStore } from '@/lib/auth-store';
import { ArrowRight, Shield, Zap, Users } from 'lucide-react';
import { ArrowRight, CheckCircle2, Shield, Users, Zap } from 'lucide-react';
import Layout from '@/components/layout';
const features = [
{
title: '安全可靠',
description: '基于 OAuth 2.0 的企业级认证,支持统一身份入口和多因素认证。',
icon: Shield,
},
{
title: '快速部署',
description: '面向容器化交付,适合快速接入现有服务并降低运维成本。',
icon: Zap,
},
{
title: '易于扩展',
description: '模块化架构设计,便于扩展自定义业务逻辑和第三方集成。',
icon: Users,
},
];
const metrics = [
['OAuth 2.0', '认证协议'],
['Docker', '部署方式'],
['SSO', '统一登录'],
];
export default function Home() {
const { isAuthenticated, initialize } = useAuthStore();
const { initialize } = useAuthStore();
const router = useRouter();
useEffect(() => {
initialize();
}, [initialize]);
useEffect(() => {
if (isAuthenticated) {
router.push('/dashboard');
}
}, [isAuthenticated, router]);
return (
<Layout>
<div className="flex flex-col items-center">
{/* Hero Section */}
<section className="w-full py-24 bg-gradient-to-b from-background to-secondary/20">
<div className="container px-4 text-center">
<h1 className="text-4xl md:text-6xl font-bold tracking-tight mb-4">
<div className="overflow-hidden">
<section className="border-b bg-[linear-gradient(180deg,white_0%,oklch(0.96_0.013_247.858)_100%)]">
<div className="mx-auto grid min-h-[calc(100svh-9.6rem)] w-full max-w-6xl items-center gap-10 px-4 py-12 sm:px-6 sm:py-16 lg:grid-cols-[1fr_0.84fr] lg:py-20">
<div className="max-w-2xl">
<div className="mb-5 inline-flex items-center gap-2 rounded-full border bg-white px-3 py-1 text-sm font-medium text-muted-foreground shadow-sm">
<CheckCircle2 className="h-4 w-4 text-emerald-600" />
</div>
<h1 className="text-4xl font-bold tracking-tight text-foreground sm:text-5xl lg:text-6xl">
IMTIM Portal
</h1>
<p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto">
<p className="mt-5 max-w-xl text-base leading-7 text-muted-foreground sm:text-lg">
</p>
<div className="mt-8 flex flex-col gap-3 sm:flex-row">
<button
onClick={() => router.push('/login')}
className="inline-flex items-center gap-2 px-6 py-3 text-base font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90"
className="inline-flex h-12 items-center justify-center gap-2 rounded-lg bg-primary px-6 text-base font-medium text-primary-foreground shadow-sm transition hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
使
<ArrowRight className="h-5 w-5" />
</button>
<button
onClick={() => router.push('/dashboard')}
className="inline-flex h-12 items-center justify-center rounded-lg border bg-white px-6 text-base font-medium shadow-sm transition hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
</button>
</div>
</div>
<div className="rounded-xl border bg-white p-4 shadow-sm sm:p-5">
<div className="rounded-lg bg-secondary p-4">
<div className="flex items-center justify-between gap-4 border-b pb-4">
<div>
<p className="text-sm text-muted-foreground"></p>
<p className="mt-1 text-2xl font-semibold"></p>
</div>
<span className="rounded-full bg-emerald-100 px-3 py-1 text-sm font-medium text-emerald-700">
Active
</span>
</div>
<div className="mt-4 grid gap-3 sm:grid-cols-3 lg:grid-cols-1 xl:grid-cols-3">
{metrics.map(([value, label]) => (
<div key={label} className="rounded-lg bg-white p-4">
<p className="text-lg font-semibold">{value}</p>
<p className="mt-1 text-sm text-muted-foreground">{label}</p>
</div>
))}
</div>
</div>
</div>
</div>
</section>
{/* Features Section */}
<section className="w-full py-16">
<div className="container px-4">
<h2 className="text-3xl font-bold text-center mb-12"></h2>
<div className="grid gap-8 md:grid-cols-3 max-w-5xl mx-auto">
<div className="rounded-lg border p-6 text-center">
<Shield className="h-12 w-12 mx-auto mb-4 text-primary" />
<h3 className="text-xl font-semibold mb-2"></h3>
<p className="text-muted-foreground">
OAuth 2.0
<section className="bg-white py-12 sm:py-16">
<div className="mx-auto w-full max-w-6xl px-4 sm:px-6">
<div className="max-w-2xl">
<h2 className="text-2xl font-semibold tracking-tight sm:text-3xl"></h2>
<p className="mt-3 text-muted-foreground">
</p>
</div>
<div className="rounded-lg border p-6 text-center">
<Zap className="h-12 w-12 mx-auto mb-4 text-primary" />
<h3 className="text-xl font-semibold mb-2"></h3>
<p className="text-muted-foreground">
Docker
</p>
</div>
<div className="rounded-lg border p-6 text-center">
<Users className="h-12 w-12 mx-auto mb-4 text-primary" />
<h3 className="text-xl font-semibold mb-2"></h3>
<p className="text-muted-foreground">
<div className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{features.map((feature) => {
const Icon = feature.icon;
return (
<div key={feature.title} className="rounded-xl border bg-card p-6 shadow-sm">
<div className="mb-5 flex h-11 w-11 items-center justify-center rounded-lg bg-primary/10 text-primary">
<Icon className="h-5 w-5" />
</div>
<h3 className="text-lg font-semibold">{feature.title}</h3>
<p className="mt-2 text-sm leading-6 text-muted-foreground">
{feature.description}
</p>
</div>
);
})}
</div>
</div>
</section>
+20 -13
View File
@@ -1,7 +1,7 @@
import Link from 'next/link';
import { useAuthStore } from '@/lib/auth-store';
import { getLoginUrl } from '@/lib/api';
import { LogOut, User } from 'lucide-react';
import { LogIn, LogOut, User } from 'lucide-react';
import * as Avatar from '@radix-ui/react-avatar';
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
@@ -23,18 +23,23 @@ export default function Header() {
};
return (
<header className="border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="container flex h-16 items-center justify-between px-4">
<Link href="/" className="text-xl font-bold">
<header className="sticky top-0 z-40 border-b bg-white/85 backdrop-blur-xl supports-[backdrop-filter]:bg-white/70">
<div className="mx-auto flex h-16 w-full max-w-6xl items-center justify-between gap-3 px-4 sm:px-6">
<Link href="/" className="flex min-w-0 items-center gap-3">
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-primary text-sm font-bold text-primary-foreground shadow-sm">
IM
</span>
<span className="truncate text-base font-semibold tracking-tight sm:text-lg">
IMTIM Portal
</span>
</Link>
<nav className="flex items-center gap-4">
<nav className="flex shrink-0 items-center gap-2">
{isAuthenticated && user ? (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<button className="flex items-center gap-2 rounded-full hover:bg-accent p-1">
<Avatar.Root className="h-8 w-8 rounded-full bg-secondary flex items-center justify-center overflow-hidden">
<button className="flex max-w-[44vw] items-center gap-2 rounded-full border bg-white p-1 pr-2 text-left shadow-sm transition hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring sm:max-w-none sm:pr-3">
<Avatar.Root className="flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full bg-secondary">
{user.avatar ? (
<Avatar.Image
src={user.avatar}
@@ -47,16 +52,17 @@ export default function Header() {
</Avatar.Fallback>
)}
</Avatar.Root>
<span className="text-sm font-medium">{user.username}</span>
<span className="truncate text-sm font-medium">{user.username}</span>
</button>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
className="min-w-[220px] bg-popover rounded-md p-1 shadow-md border"
className="z-50 min-w-56 rounded-lg border bg-popover p-1.5 shadow-xl"
align="end"
sideOffset={5}
>
<DropdownMenu.Item className="flex items-center gap-2 px-3 py-2 text-sm rounded cursor-pointer hover:bg-accent outline-none">
<DropdownMenu.Item className="flex cursor-pointer items-center gap-2 rounded-md px-3 py-2 text-sm outline-none hover:bg-accent focus:bg-accent">
<User className="h-4 w-4" />
<span></span>
</DropdownMenu.Item>
@@ -64,7 +70,7 @@ export default function Header() {
<DropdownMenu.Separator className="h-px bg-border my-1" />
<DropdownMenu.Item
className="flex items-center gap-2 px-3 py-2 text-sm rounded cursor-pointer hover:bg-accent text-red-600 outline-none"
className="flex cursor-pointer items-center gap-2 rounded-md px-3 py-2 text-sm text-red-600 outline-none hover:bg-red-50 focus:bg-red-50"
onClick={handleLogout}
>
<LogOut className="h-4 w-4" />
@@ -76,9 +82,10 @@ export default function Header() {
) : (
<button
onClick={handleLogin}
className="px-4 py-2 text-sm font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90"
className="inline-flex h-10 items-center justify-center gap-2 rounded-lg bg-primary px-3 text-sm font-medium text-primary-foreground shadow-sm transition hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring sm:px-4"
>
<LogIn className="h-4 w-4" />
<span></span>
</button>
)}
</nav>
+3 -3
View File
@@ -6,11 +6,11 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
<div className="min-h-screen flex flex-col">
<div className="flex min-h-screen flex-col bg-background text-foreground">
<Header />
<main className="flex-1">{children}</main>
<footer className="border-t py-6">
<div className="container px-4 text-center text-sm text-muted-foreground">
<footer className="border-t bg-white/70 py-5">
<div className="mx-auto w-full max-w-6xl px-4 text-center text-xs text-muted-foreground sm:px-6">
© 2026 IMTIM Portal. All rights reserved.
</div>
</footer>
+9 -7
View File
@@ -5,7 +5,7 @@ import { useRouter } from 'next/navigation';
import { useAuthStore } from '@/lib/auth-store';
export default function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated, initialize } = useAuthStore();
const { isAuthenticated, isInitialized, initialize } = useAuthStore();
const router = useRouter();
useEffect(() => {
@@ -13,15 +13,17 @@ export default function ProtectedRoute({ children }: { children: React.ReactNode
}, [initialize]);
useEffect(() => {
if (!isAuthenticated) {
router.push('/login');
if (isInitialized && !isAuthenticated) {
router.replace('/login');
}
}, [isAuthenticated, router]);
}, [isInitialized, isAuthenticated, router]);
if (!isAuthenticated) {
if (!isInitialized || !isAuthenticated) {
return (
<div className="flex items-center justify-center min-h-[200px]">
<div className="text-muted-foreground">...</div>
<div className="mx-auto flex min-h-[calc(100svh-9.6rem)] w-full max-w-6xl items-center justify-center px-4 py-10 sm:px-6">
<div className="rounded-xl border bg-white px-5 py-4 text-sm text-muted-foreground shadow-sm">
...
</div>
</div>
);
}
+11 -1
View File
@@ -13,6 +13,7 @@ interface AuthState {
user: User | null;
token: string | null;
isAuthenticated: boolean;
isInitialized: boolean;
login: (token: string, user: User) => void;
logout: () => void;
initialize: () => void;
@@ -22,6 +23,7 @@ export const useAuthStore = create<AuthState>((set) => ({
user: null,
token: null,
isAuthenticated: false,
isInitialized: false,
login: (token, user) => {
if (typeof window !== 'undefined') {
@@ -36,7 +38,7 @@ export const useAuthStore = create<AuthState>((set) => ({
localStorage.removeItem('token');
localStorage.removeItem('user');
}
set({ user: null, token: null, isAuthenticated: false });
set({ user: null, token: null, isAuthenticated: false, isInitialized: true });
},
initialize: () => {
@@ -44,12 +46,20 @@ export const useAuthStore = create<AuthState>((set) => ({
const token = localStorage.getItem('token');
const userStr = localStorage.getItem('user');
if (token && userStr) {
try {
set({
token,
user: JSON.parse(userStr),
isAuthenticated: true,
isInitialized: true,
});
return;
} catch {
localStorage.removeItem('token');
localStorage.removeItem('user');
}
}
}
set({ user: null, token: null, isAuthenticated: false, isInitialized: true });
},
}));