refactor(v2): login view and register and default error handler and more

This commit is contained in:
moonrailgun 2024-04-05 15:29:47 +08:00
parent af4c6f6bd1
commit 4d260dc45e
11 changed files with 99 additions and 45 deletions

View File

@ -25,6 +25,7 @@ import { routeTree } from './routeTree.gen';
import { DefaultNotFound } from './components/DefaultNotFound'; import { DefaultNotFound } from './components/DefaultNotFound';
import { TooltipProvider } from './components/ui/tooltip'; import { TooltipProvider } from './components/ui/tooltip';
import { Toaster } from './components/ui/sonner'; import { Toaster } from './components/ui/sonner';
import { DefaultError } from './components/DefaultError';
const router = createRouter({ const router = createRouter({
routeTree, routeTree,
@ -32,6 +33,7 @@ const router = createRouter({
userInfo: undefined, userInfo: undefined,
}, },
defaultNotFoundComponent: DefaultNotFound, defaultNotFoundComponent: DefaultNotFound,
defaultErrorComponent: DefaultError,
}); });
// Register the router instance for type safety // Register the router instance for type safety

View File

@ -2,7 +2,6 @@ import dayjs from 'dayjs';
import { useUserStore } from '../../store/user'; import { useUserStore } from '../../store/user';
import { useEvent } from '../../hooks/useEvent'; import { useEvent } from '../../hooks/useEvent';
import { clearJWT } from '../auth'; import { clearJWT } from '../auth';
import { useNavigate } from '@tanstack/react-router';
/** /**
* Mock * Mock
@ -13,15 +12,11 @@ export function getUserTimezone(): string {
} }
export function useLogout() { export function useLogout() {
const navigate = useNavigate();
const logout = useEvent(() => { const logout = useEvent(() => {
window.location.href = '/login'; // not good, need to invest to find better way.
useUserStore.setState({ info: null }); useUserStore.setState({ info: null });
clearJWT(); clearJWT();
navigate({
to: '/login',
replace: true,
});
}); });
return logout; return logout;

View File

@ -26,8 +26,12 @@ import { useTranslation } from '@i18next-toolkit/react';
import { trpc } from '@/api/trpc'; import { trpc } from '@/api/trpc';
import { useCurrentWorkspaceId } from '@/store/user'; import { useCurrentWorkspaceId } from '@/store/user';
import { Button } from './ui/button'; import { Button } from './ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
export const CommandPanel: React.FC = React.memo(() => { interface CommandPanelProps {
isCollapsed: boolean;
}
export const CommandPanel: React.FC<CommandPanelProps> = React.memo((props) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const { t } = useTranslation(); const { t } = useTranslation();
@ -53,6 +57,24 @@ export const CommandPanel: React.FC = React.memo(() => {
return ( return (
<> <>
{props.isCollapsed ? (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="secondary"
size="icon"
Icon={LuSearch}
onClick={() => setOpen(true)}
/>
</TooltipTrigger>
<TooltipContent side="right" className="flex items-center gap-4">
{t('Search and quick jump')}
<span className="ml-1 rounded bg-black/10 px-1 py-0.5">
ctrl + k
</span>
</TooltipContent>
</Tooltip>
) : (
<Button <Button
className="w-full !justify-between" className="w-full !justify-between"
variant="secondary" variant="secondary"
@ -62,6 +84,7 @@ export const CommandPanel: React.FC = React.memo(() => {
> >
<span className="rounded bg-black/10 px-1 py-0.5">ctrl + k</span> <span className="rounded bg-black/10 px-1 py-0.5">ctrl + k</span>
</Button> </Button>
)}
<CommandDialog open={open} onOpenChange={setOpen}> <CommandDialog open={open} onOpenChange={setOpen}>
<Command loop={true}> <Command loop={true}>

View File

@ -0,0 +1,30 @@
import React from 'react';
import { Button } from './ui/button';
import { Link } from '@tanstack/react-router';
import { useTranslation } from '@i18next-toolkit/react';
import { Card, CardContent, CardFooter, CardHeader } from './ui/card';
export const DefaultError: React.FC = React.memo(() => {
const { t } = useTranslation();
return (
<div className="flex h-full w-full items-center justify-center">
<Card className="min-w-[320px] bg-zinc-900">
<CardHeader>
<div className="text-center">
<img className="m-auto h-24 w-24" src="/icon.svg" />
</div>
</CardHeader>
<CardContent className="text-center">
<div>{t('Sorry, but something went wrong')}</div>
</CardContent>
<CardFooter>
<Link className="ml-auto" to="/">
<Button>{t('Back to Homepage')}</Button>
</Link>
</CardFooter>
</Card>
</div>
);
});
DefaultError.displayName = 'DefaultError';

View File

@ -61,7 +61,7 @@ export const DesktopLayout: React.FC<LayoutProps> = React.memo((props) => {
</div> </div>
<Separator /> <Separator />
<div className="p-2"> <div className="p-2">
<CommandPanel /> <CommandPanel isCollapsed={isCollapsed} />
</div> </div>
<Separator /> <Separator />
<Nav <Nav

View File

@ -3,10 +3,12 @@ import { useRequest } from '@/hooks/useRequest';
import { setJWT } from '@/api/auth'; import { setJWT } from '@/api/auth';
import { useGlobalConfig } from '@/hooks/useConfig'; import { useGlobalConfig } from '@/hooks/useConfig';
import { trpc } from '@/api/trpc'; import { trpc } from '@/api/trpc';
import { Button, Form, Input, Typography } from 'antd'; import { Form, Typography } from 'antd';
import { useTranslation } from '@i18next-toolkit/react'; import { useTranslation } from '@i18next-toolkit/react';
import { setUserInfo } from '@/store/user'; import { setUserInfo } from '@/store/user';
import { z } from 'zod'; import { z } from 'zod';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
export const Route = createFileRoute('/login')({ export const Route = createFileRoute('/login')({
validateSearch: z.object({ validateSearch: z.object({
@ -59,21 +61,20 @@ function LoginComponent() {
name="username" name="username"
rules={[{ required: true }]} rules={[{ required: true }]}
> >
<Input size="large" /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t('Password')} label={t('Password')}
name="password" name="password"
rules={[{ required: true }]} rules={[{ required: true }]}
> >
<Input.Password size="large" /> <Input type="password" />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Button <Button
type="primary" size="lg"
size="large" type="submit"
htmlType="submit" className="w-full"
block={true}
loading={loading} loading={loading}
> >
{t('Login')} {t('Login')}
@ -83,9 +84,10 @@ function LoginComponent() {
{allowRegister && ( {allowRegister && (
<Form.Item> <Form.Item>
<Button <Button
size="large" variant="secondary"
htmlType="button" size="lg"
block={true} type="button"
className="w-full"
onClick={() => { onClick={() => {
navigate({ navigate({
to: '/register', to: '/register',

View File

@ -15,6 +15,7 @@ import {
useNavigate, useNavigate,
useRouterState, useRouterState,
} from '@tanstack/react-router'; } from '@tanstack/react-router';
import { compact } from 'lodash-es';
import { LuPlus } from 'react-icons/lu'; import { LuPlus } from 'react-icons/lu';
export const Route = createFileRoute('/monitor')({ export const Route = createFileRoute('/monitor')({
@ -44,7 +45,7 @@ function MonitorComponent() {
showCurrentStatus={true} showCurrentStatus={true}
/> />
), ),
tags: [item.type], tags: compact([item.type, item.active ? false : t('Stopped')]),
href: `/monitor/${item.id}`, href: `/monitor/${item.id}`,
})); }));

View File

@ -1,10 +1,12 @@
import { Button, Form, Input, Typography } from 'antd'; import { Form, Typography } from 'antd';
import { useRequest } from '../hooks/useRequest'; import { useRequest } from '../hooks/useRequest';
import { trpc } from '../api/trpc'; import { trpc } from '../api/trpc';
import { setJWT } from '../api/auth'; import { setJWT } from '../api/auth';
import { setUserInfo } from '../store/user'; import { setUserInfo } from '../store/user';
import { useTranslation } from '@i18next-toolkit/react'; import { useTranslation } from '@i18next-toolkit/react';
import { createFileRoute, useNavigate } from '@tanstack/react-router'; import { createFileRoute, useNavigate } from '@tanstack/react-router';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
export const Route = createFileRoute('/register')({ export const Route = createFileRoute('/register')({
component: RegisterComponent, component: RegisterComponent,
@ -31,7 +33,7 @@ function RegisterComponent() {
}); });
return ( return (
<div className="flex h-full w-full items-center justify-center dark:bg-gray-900"> <div className="flex h-full w-full items-center justify-center dark:bg-zinc-900">
<div className="w-80 -translate-y-1/4"> <div className="w-80 -translate-y-1/4">
<div className="text-center"> <div className="text-center">
<img className="m-auto h-24 w-24 " src="/icon.svg" /> <img className="m-auto h-24 w-24 " src="/icon.svg" />
@ -45,21 +47,20 @@ function RegisterComponent() {
name="username" name="username"
rules={[{ required: true }]} rules={[{ required: true }]}
> >
<Input size="large" /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t('Password')} label={t('Password')}
name="password" name="password"
rules={[{ required: true }]} rules={[{ required: true }]}
> >
<Input.Password size="large" /> <Input type="password" />
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Button <Button
type="primary" size="lg"
size="large" type="submit"
htmlType="submit" className="w-full"
block={true}
loading={loading} loading={loading}
> >
{t('Register')} {t('Register')}

View File

@ -1,14 +1,9 @@
import { trpc } from '@/api/trpc';
import { CommonHeader } from '@/components/CommonHeader'; import { CommonHeader } from '@/components/CommonHeader';
import { CommonList } from '@/components/CommonList'; import { CommonList } from '@/components/CommonList';
import { CommonWrapper } from '@/components/CommonWrapper'; import { CommonWrapper } from '@/components/CommonWrapper';
import { Button } from '@/components/ui/button';
import { useDataReady } from '@/hooks/useDataReady';
import { useEvent } from '@/hooks/useEvent';
import { LayoutV2 } from '@/pages/LayoutV2'; import { LayoutV2 } from '@/pages/LayoutV2';
import { useCurrentWorkspaceId } from '@/store/user';
import { routeAuthBeforeLoad } from '@/utils/route'; import { routeAuthBeforeLoad } from '@/utils/route';
import { Trans, useTranslation } from '@i18next-toolkit/react'; import { useTranslation } from '@i18next-toolkit/react';
import { import {
createFileRoute, createFileRoute,
useNavigate, useNavigate,
@ -18,10 +13,10 @@ import { useEffect } from 'react';
export const Route = createFileRoute('/settings')({ export const Route = createFileRoute('/settings')({
beforeLoad: routeAuthBeforeLoad, beforeLoad: routeAuthBeforeLoad,
component: TelemetryComponent, component: PageComponent,
}); });
function TelemetryComponent() { function PageComponent() {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const pathname = useRouterState({ const pathname = useRouterState({

View File

@ -8,6 +8,7 @@ import { useEvent } from '@/hooks/useEvent';
import { LayoutV2 } from '@/pages/LayoutV2'; import { LayoutV2 } from '@/pages/LayoutV2';
import { useCurrentWorkspaceId } from '@/store/user'; import { useCurrentWorkspaceId } from '@/store/user';
import { routeAuthBeforeLoad } from '@/utils/route'; import { routeAuthBeforeLoad } from '@/utils/route';
import { cn } from '@/utils/style';
import { useTranslation } from '@i18next-toolkit/react'; import { useTranslation } from '@i18next-toolkit/react';
import { import {
createFileRoute, createFileRoute,
@ -70,6 +71,9 @@ function WebsiteComponent() {
actions={ actions={
<div className="space-x-2"> <div className="space-x-2">
<Button <Button
className={cn(
pathname === '/website/overview' && '!bg-muted'
)}
variant="outline" variant="outline"
Icon={LuEye} Icon={LuEye}
onClick={handleClickOverview} onClick={handleClickOverview}

View File

@ -54,6 +54,7 @@ function WebsiteOverviewComponent() {
<div className="space-y-10 p-4"> <div className="space-y-10 p-4">
{websites.map((website) => ( {websites.map((website) => (
<WebsiteOverview <WebsiteOverview
key={website.id}
website={website} website={website}
actions={ actions={
<Link <Link