refactor: new layout and new router

This commit is contained in:
moonrailgun 2024-03-21 02:00:23 +08:00
parent 0987ca37d5
commit 3f13447e1f
18 changed files with 976 additions and 232 deletions

518
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,22 @@ import { StatusPage } from './pages/Status';
import { TelemetryPage } from './pages/Telemetry'; import { TelemetryPage } from './pages/Telemetry';
import { LayoutV2 } from './pages/LayoutV2'; import { LayoutV2 } from './pages/LayoutV2';
import { isDev } from './utils/env'; import { isDev } from './utils/env';
import { RouterProvider, createRouter } from '@tanstack/react-router';
import { routeTree } from './routeTree.gen';
const router = createRouter({
routeTree,
context: {
userInfo: undefined,
},
});
// Register the router instance for type safety
declare module '@tanstack/react-router' {
interface Register {
router: typeof router;
}
}
export const AppRoutes: React.FC = React.memo(() => { export const AppRoutes: React.FC = React.memo(() => {
const { info: userInfo } = useUserStore(); const { info: userInfo } = useUserStore();
@ -64,21 +80,26 @@ export const App: React.FC = React.memo(() => {
const colorScheme = useColorSchema(); const colorScheme = useColorSchema();
const algorithm = const algorithm =
colorScheme === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm; colorScheme === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm;
const { info: userInfo } = useUserStore();
return ( return (
<div ref={rootRef} className="App"> <div ref={rootRef} className="App">
<trpc.Provider client={trpcClient} queryClient={queryClient}> <trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<BrowserRouter>
<ConfigProvider <ConfigProvider
theme={{ algorithm }} theme={{ algorithm }}
getPopupContainer={() => rootRef.current!} getPopupContainer={() => rootRef.current!}
> >
<TokenLoginContainer> <TokenLoginContainer>
{isDev ? (
<RouterProvider router={router} context={{ userInfo }} />
) : (
<BrowserRouter>
<AppRoutes /> <AppRoutes />
</BrowserRouter>
)}
</TokenLoginContainer> </TokenLoginContainer>
</ConfigProvider> </ConfigProvider>
</BrowserRouter>
</QueryClientProvider> </QueryClientProvider>
</trpc.Provider> </trpc.Provider>
</div> </div>

View File

@ -0,0 +1,84 @@
import React, { ComponentProps } from 'react';
import { ScrollArea } from './ui/scroll-area';
import { cn } from '@/utils/style';
import { Badge } from './ui/badge';
import { useNavigate, useRouterState } from '@tanstack/react-router';
export interface CommonListItem {
id: string;
title: string;
content: React.ReactNode;
tags: string[];
href: string;
}
interface CommonListProps {
items: CommonListItem[];
}
export const CommonList: React.FC<CommonListProps> = React.memo((props) => {
const { location } = useRouterState();
const navigate = useNavigate();
return (
<ScrollArea className="h-screen">
<div className="flex flex-col gap-2 p-4 pt-0">
{props.items.map((item) => {
const isSelected = item.href === location.pathname;
return (
<button
key={item.id}
className={cn(
'flex flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent',
isSelected && 'bg-muted'
)}
onClick={() =>
navigate({
to: item.href,
})
}
>
<div className="flex w-full flex-col gap-1">
<div className="flex items-center">
<div className="flex items-center gap-2">
<div className="font-semibold">{item.title}</div>
</div>
</div>
</div>
<div className="line-clamp-2 text-xs text-muted-foreground">
{item.content}
</div>
{item.tags.length > 0 ? (
<div className="flex items-center gap-2">
{item.tags.map((tag) => (
<Badge key={tag} variant={getBadgeVariantFromLabel(tag)}>
{tag}
</Badge>
))}
</div>
) : null}
</button>
);
})}
</div>
</ScrollArea>
);
});
CommonList.displayName = 'CommonList';
/**
* TODO
*/
function getBadgeVariantFromLabel(
label: string
): ComponentProps<typeof Badge>['variant'] {
if (['work'].includes(label.toLowerCase())) {
return 'default';
}
if (['personal'].includes(label.toLowerCase())) {
return 'outline';
}
return 'secondary';
}

View File

@ -0,0 +1,36 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/utils/style"
const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }

View File

@ -0,0 +1,46 @@
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/utils/style"
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }

View File

@ -0,0 +1,53 @@
import * as React from 'react';
import * as TabsPrimitive from '@radix-ui/react-tabs';
import { cn } from '@/utils/style';
const Tabs = TabsPrimitive.Root;
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
'inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground',
className
)}
{...props}
/>
));
TabsList.displayName = TabsPrimitive.List.displayName;
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow',
className
)}
{...props}
/>
));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
'mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
className
)}
{...props}
/>
));
TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsList, TabsTrigger, TabsContent };

View File

@ -27,13 +27,17 @@
"@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-menubar": "^1.0.4", "@radix-ui/react-menubar": "^1.0.4",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0", "@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7", "@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/react-query": "4.33.0", "@tanstack/react-query": "4.33.0",
"@tanstack/react-router": "^1.20.5",
"@tanstack/react-table": "^8.13.2", "@tanstack/react-table": "^8.13.2",
"@tanstack/react-virtual": "^3.0.2", "@tanstack/react-virtual": "^3.0.2",
"@tanstack/router-devtools": "^1.20.5",
"@tianji/shared": "workspace:^", "@tianji/shared": "workspace:^",
"@trpc/client": "^10.45.0", "@trpc/client": "^10.45.0",
"@trpc/react-query": "^10.45.0", "@trpc/react-query": "^10.45.0",
@ -75,6 +79,7 @@
}, },
"devDependencies": { "devDependencies": {
"@i18next-toolkit/cli": "^1.1.0", "@i18next-toolkit/cli": "^1.1.0",
"@tanstack/router-vite-plugin": "^1.20.5",
"@types/leaflet": "^1.9.8", "@types/leaflet": "^1.9.8",
"@types/loadable__component": "^5.13.8", "@types/loadable__component": "^5.13.8",
"@types/lodash-es": "^4.17.12", "@types/lodash-es": "^4.17.12",
@ -88,7 +93,7 @@
"less": "^4.2.0", "less": "^4.2.0",
"postcss": "^8.4.31", "postcss": "^8.4.31",
"shadcn-ui": "^0.8.0", "shadcn-ui": "^0.8.0",
"tailwindcss": "^3.3.5", "tailwindcss": "^3.4.1",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"vite": "^5.0.12", "vite": "^5.0.12",
"vitest": "^1.2.1" "vitest": "^1.2.1"

View File

@ -1,19 +1,9 @@
import * as React from 'react'; import * as React from 'react';
import { import {
LuAlertCircle,
LuArchive,
LuAreaChart, LuAreaChart,
LuFile,
LuFilePieChart, LuFilePieChart,
LuInbox,
LuMessagesSquare,
LuMonitorDot, LuMonitorDot,
LuMoreVertical,
LuSend,
LuServer, LuServer,
LuShoppingCart,
LuTrash2,
LuUsers2,
LuWifi, LuWifi,
} from 'react-icons/lu'; } from 'react-icons/lu';
import { TooltipProvider } from '@/components/ui/tooltip'; import { TooltipProvider } from '@/components/ui/tooltip';
@ -33,10 +23,14 @@ import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { useUserInfo } from '@/store/user'; import { useUserInfo } from '@/store/user';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { UserConfig } from './Layout/UserConfig'; import { UserConfig } from './Layout/UserConfig';
import { Outlet } from '@tanstack/react-router';
import { CommonList, CommonListItem } from '@/components/CommonList';
const defaultLayout: [number, number, number] = [265, 440, 655]; const defaultLayout: [number, number, number] = [265, 440, 655];
export const LayoutV2: React.FC = React.memo(() => { export const LayoutV2: React.FC<{
list: React.ReactNode;
}> = React.memo((props) => {
const [layout = defaultLayout, setLayout] = useLocalStorageState( const [layout = defaultLayout, setLayout] = useLocalStorageState(
'react-resizable-panels:layout', 'react-resizable-panels:layout',
{ defaultValue: defaultLayout } { defaultValue: defaultLayout }
@ -127,11 +121,13 @@ export const LayoutV2: React.FC = React.memo(() => {
</ResizablePanel> </ResizablePanel>
<ResizableHandle withHandle /> <ResizableHandle withHandle />
<ResizablePanel defaultSize={layout[1]} minSize={30}> <ResizablePanel defaultSize={layout[1]} minSize={30}>
<div>1</div> <div>{props.list}</div>
</ResizablePanel> </ResizablePanel>
<ResizableHandle withHandle /> <ResizableHandle withHandle />
<ResizablePanel defaultSize={layout[2]}> <ResizablePanel defaultSize={layout[2]}>
<div>2</div> <div>
<Outlet />
</div>
</ResizablePanel> </ResizablePanel>
</ResizablePanelGroup> </ResizablePanelGroup>
</TooltipProvider> </TooltipProvider>

View File

@ -0,0 +1,83 @@
/* prettier-ignore-start */
/* eslint-disable */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// This file is auto-generated by TanStack Router
// Import Routes
import { Route as rootRoute } from './routes/__root'
import { Route as WebsiteImport } from './routes/website'
import { Route as RegisterImport } from './routes/register'
import { Route as LoginImport } from './routes/login'
import { Route as IndexImport } from './routes/index'
import { Route as WebsiteWebsiteIdImport } from './routes/website/$websiteId'
// Create/Update Routes
const WebsiteRoute = WebsiteImport.update({
path: '/website',
getParentRoute: () => rootRoute,
} as any)
const RegisterRoute = RegisterImport.update({
path: '/register',
getParentRoute: () => rootRoute,
} as any)
const LoginRoute = LoginImport.update({
path: '/login',
getParentRoute: () => rootRoute,
} as any)
const IndexRoute = IndexImport.update({
path: '/',
getParentRoute: () => rootRoute,
} as any)
const WebsiteWebsiteIdRoute = WebsiteWebsiteIdImport.update({
path: '/$websiteId',
getParentRoute: () => WebsiteRoute,
} as any)
// Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/': {
preLoaderRoute: typeof IndexImport
parentRoute: typeof rootRoute
}
'/login': {
preLoaderRoute: typeof LoginImport
parentRoute: typeof rootRoute
}
'/register': {
preLoaderRoute: typeof RegisterImport
parentRoute: typeof rootRoute
}
'/website': {
preLoaderRoute: typeof WebsiteImport
parentRoute: typeof rootRoute
}
'/website/$websiteId': {
preLoaderRoute: typeof WebsiteWebsiteIdImport
parentRoute: typeof WebsiteImport
}
}
}
// Create and export the route tree
export const routeTree = rootRoute.addChildren([
IndexRoute,
LoginRoute,
RegisterRoute,
WebsiteRoute.addChildren([WebsiteWebsiteIdRoute]),
])
/* prettier-ignore-end */

View File

@ -0,0 +1,20 @@
import { createRootRouteWithContext, Outlet } from '@tanstack/react-router';
import { TanStackRouterDevtools } from '@tanstack/router-devtools';
interface RouterContext {
// The ReturnType of your useAuth hook or the value of your AuthContext
userInfo: any;
}
const defaultLayout: [number, number, number] = [265, 440, 655];
export const Route = createRootRouteWithContext<RouterContext>()({
component: () => {
return (
<>
<Outlet />
<TanStackRouterDevtools position="bottom-right" />
</>
);
},
});

View File

@ -0,0 +1,15 @@
import { routeAuthBeforeLoad } from '@/utils/route';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/')({
beforeLoad: routeAuthBeforeLoad,
component: Index,
});
function Index() {
return (
<div className="p-2">
<h3>Welcome Home!</h3>
</div>
);
}

101
src/client/routes/login.tsx Normal file
View File

@ -0,0 +1,101 @@
import {
createFileRoute,
getRouteApi,
useNavigate,
} from '@tanstack/react-router';
import { useRequest } from '@/hooks/useRequest';
import { setJWT } from '@/api/auth';
import { useGlobalConfig } from '@/hooks/useConfig';
import { trpc } from '@/api/trpc';
import { Button, Form, Input, Typography } from 'antd';
import { useTranslation } from '@i18next-toolkit/react';
import { setUserInfo } from '@/store/user';
import { z } from 'zod';
export const Route = createFileRoute('/login')({
validateSearch: z.object({
// redirect: z.string().catch('/'),
redirect: z.string().optional(),
}),
component: LoginComponent,
});
const routeApi = getRouteApi('/login');
function LoginComponent() {
const navigate = useNavigate();
const { t } = useTranslation();
const loginMutation = trpc.user.login.useMutation();
const search = routeApi.useSearch();
const [{ loading }, handleLogin] = useRequest(async (values: any) => {
const res = await loginMutation.mutateAsync({
username: values.username,
password: values.password,
});
setJWT(res.token);
setUserInfo(res.info);
navigate({
to: search.redirect ?? '/dashboard',
});
});
const { allowRegister } = useGlobalConfig();
return (
<div className="w-full h-full flex justify-center items-center dark:bg-gray-900">
<div className="w-80 -translate-y-1/4">
<div className="text-center">
<img className="w-24 h-24" src="/icon.svg" />
</div>
<Typography.Title className="text-center" level={2}>
Tianji
</Typography.Title>
<Form layout="vertical" disabled={loading} onFinish={handleLogin}>
<Form.Item
label={t('Username')}
name="username"
rules={[{ required: true }]}
>
<Input size="large" />
</Form.Item>
<Form.Item
label={t('Password')}
name="password"
rules={[{ required: true }]}
>
<Input.Password size="large" />
</Form.Item>
<Form.Item>
<Button
type="primary"
size="large"
htmlType="submit"
block={true}
loading={loading}
>
{t('Login')}
</Button>
</Form.Item>
{allowRegister && (
<Form.Item>
<Button
size="large"
htmlType="button"
block={true}
onClick={() => {
navigate({
to: '/register',
});
}}
>
{t('Register')}
</Button>
</Form.Item>
)}
</Form>
</div>
</div>
);
}

View File

@ -0,0 +1,71 @@
import { Button, Form, Input, Typography } from 'antd';
import React from 'react';
import { useNavigate } from 'react-router';
import { useRequest } from '../hooks/useRequest';
import { trpc } from '../api/trpc';
import { setJWT } from '../api/auth';
import { setUserInfo } from '../store/user';
import { useTranslation } from '@i18next-toolkit/react';
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/register')({
component: RegisterComponent,
});
function RegisterComponent() {
const { t } = useTranslation();
const navigate = useNavigate();
const mutation = trpc.user.register.useMutation();
const [{ loading }, handleRegister] = useRequest(async (values: any) => {
const res = await mutation.mutateAsync({
username: values.username,
password: values.password,
});
setJWT(res.token);
setUserInfo(res.info);
navigate('/dashboard');
});
return (
<div className="w-full h-full flex justify-center items-center">
<div className="w-80 -translate-y-1/4">
<div className="text-center">
<img className="w-24 h-24" src="/icon.svg" />
</div>
<Typography.Title className="text-center" level={2}>
{t('Register Account')}
</Typography.Title>
<Form layout="vertical" disabled={loading} onFinish={handleRegister}>
<Form.Item
label={t('Username')}
name="username"
rules={[{ required: true }]}
>
<Input size="large" />
</Form.Item>
<Form.Item
label={t('Password')}
name="password"
rules={[{ required: true }]}
>
<Input.Password size="large" />
</Form.Item>
<Form.Item>
<Button
type="primary"
size="large"
htmlType="submit"
block={true}
loading={loading}
>
{t('Register')}
</Button>
</Form.Item>
</Form>
</div>
</div>
);
}

View File

@ -0,0 +1,71 @@
import { trpc } from '@/api/trpc';
import { CommonList } from '@/components/CommonList';
import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { LayoutV2 } from '@/pages/LayoutV2';
import { useCurrentWorkspaceId } from '@/store/user';
import { routeAuthBeforeLoad } from '@/utils/route';
import { createFileRoute } from '@tanstack/react-router';
import { LuSearch } from 'react-icons/lu';
export const Route = createFileRoute('/website')({
beforeLoad: routeAuthBeforeLoad,
component: WebsiteComponent,
});
function WebsiteComponent() {
const workspaceId = useCurrentWorkspaceId();
const { data = [] } = trpc.website.all.useQuery({
workspaceId,
});
const items = data.map((item) => ({
id: item.id,
title: item.name,
content: item.domain,
tags: [],
href: `/website/${item.id}`,
}));
return (
<LayoutV2
list={
<Tabs defaultValue="all">
<div className="flex items-center px-4 py-2">
<h1 className="text-xl font-bold">Website</h1>
<TabsList className="ml-auto">
<TabsTrigger
value="all"
className="text-zinc-600 dark:text-zinc-200"
>
All mail
</TabsTrigger>
<TabsTrigger
value="unread"
className="text-zinc-600 dark:text-zinc-200"
>
Unread
</TabsTrigger>
</TabsList>
</div>
<Separator />
<div className="bg-background/95 p-4 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<form>
<div className="relative">
<LuSearch className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
<Input placeholder="Search" className="pl-8" />
</div>
</form>
</div>
<TabsContent value="all" className="m-0">
<CommonList items={items} />
</TabsContent>
<TabsContent value="unread" className="m-0">
<CommonList items={items} />
</TabsContent>
</Tabs>
}
/>
);
}

View File

@ -0,0 +1,13 @@
import { routeAuthBeforeLoad } from '@/utils/route';
import { createFileRoute, getRouteApi } from '@tanstack/react-router';
export const Route = createFileRoute('/website/$websiteId')({
beforeLoad: routeAuthBeforeLoad,
component: WebsiteDetailComponent,
});
function WebsiteDetailComponent() {
const params = Route.useParams<{ websiteId: string }>();
return <div>website: {params.websiteId}</div>;
}

View File

@ -11,6 +11,7 @@ module.exports = {
'./index.html', './index.html',
'./components/**/*.{js,jsx,ts,tsx}', './components/**/*.{js,jsx,ts,tsx}',
'./pages/**/*.{js,jsx,ts,tsx}', './pages/**/*.{js,jsx,ts,tsx}',
'./routes/**/*.{js,jsx,ts,tsx}',
], ],
}, },
theme: { theme: {
@ -84,7 +85,7 @@ module.exports = {
}, },
darkMode: 'class', darkMode: 'class',
corePlugins: { corePlugins: {
preflight: false, preflight: true,
}, },
plugins: [animate], plugins: [animate],
} satisfies Config; } satisfies Config;

15
src/client/utils/route.ts Normal file
View File

@ -0,0 +1,15 @@
import { FileBaseRouteOptions, redirect } from '@tanstack/react-router';
export const routeAuthBeforeLoad: FileBaseRouteOptions['beforeLoad'] = ({
context,
location,
}) => {
if (!(context as any).userInfo) {
throw redirect({
to: '/login',
search: {
redirect: location.href,
},
});
}
};

View File

@ -1,11 +1,18 @@
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react'; import react from '@vitejs/plugin-react';
import { resolve } from 'path'; import { resolve } from 'path';
import { TanStackRouterVite } from '@tanstack/router-vite-plugin';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
root: __dirname, root: __dirname,
plugins: [react()], plugins: [
react(),
TanStackRouterVite({
routesDirectory: './routes',
generatedRouteTree: './routeTree.gen.ts',
}),
],
build: { build: {
outDir: '../server/public', outDir: '../server/public',
}, },