refactor(v2): login view and register and default error handler and more
This commit is contained in:
parent
af4c6f6bd1
commit
4d260dc45e
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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,15 +57,34 @@ export const CommandPanel: React.FC = React.memo(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
{props.isCollapsed ? (
|
||||||
className="w-full !justify-between"
|
<Tooltip>
|
||||||
variant="secondary"
|
<TooltipTrigger asChild>
|
||||||
size="sm"
|
<Button
|
||||||
Icon={LuSearch}
|
variant="secondary"
|
||||||
onClick={() => setOpen(true)}
|
size="icon"
|
||||||
>
|
Icon={LuSearch}
|
||||||
<span className="rounded bg-black/10 px-1 py-0.5">ctrl + k</span>
|
onClick={() => setOpen(true)}
|
||||||
</Button>
|
/>
|
||||||
|
</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
|
||||||
|
className="w-full !justify-between"
|
||||||
|
variant="secondary"
|
||||||
|
size="sm"
|
||||||
|
Icon={LuSearch}
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
>
|
||||||
|
<span className="rounded bg-black/10 px-1 py-0.5">ctrl + k</span>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||||
<Command loop={true}>
|
<Command loop={true}>
|
||||||
|
30
src/client/components/DefaultError.tsx
Normal file
30
src/client/components/DefaultError.tsx
Normal 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';
|
@ -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
|
||||||
|
@ -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',
|
||||||
|
@ -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}`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -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')}
|
||||||
|
@ -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({
|
||||||
|
@ -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}
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user