From f27f3f2f11c031cfbbea4a927e5fad3866920a7d Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Mon, 25 Mar 2024 21:53:43 +0800 Subject: [PATCH] feat: add telemetry route --- pnpm-lock.yaml | 60 +++++++ src/client/components/CommonHeader.tsx | 4 + src/client/components/CommonList.tsx | 4 +- src/client/components/CommonWrapper.tsx | 8 +- src/client/components/TipIcon.tsx | 22 +++ src/client/components/ui/alert-dialog.tsx | 139 +++++++++++++++ src/client/components/ui/collapsible.tsx | 42 +++++ src/client/package.json | 3 + src/client/routeTree.gen.ts | 31 ++++ src/client/routes/telemetry.tsx | 115 +++++++++++++ src/client/routes/telemetry/$telemetryId.tsx | 170 +++++++++++++++++++ src/client/routes/telemetry/add.tsx | 101 +++++++++++ src/client/routes/website.tsx | 1 - src/client/routes/website/add.tsx | 3 +- 14 files changed, 695 insertions(+), 8 deletions(-) create mode 100644 src/client/components/TipIcon.tsx create mode 100644 src/client/components/ui/alert-dialog.tsx create mode 100644 src/client/components/ui/collapsible.tsx create mode 100644 src/client/routes/telemetry.tsx create mode 100644 src/client/routes/telemetry/$telemetryId.tsx create mode 100644 src/client/routes/telemetry/add.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e16ef90..ca75530 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -87,9 +87,15 @@ importers: '@monaco-editor/react': specifier: ^4.6.0 version: 4.6.0(monaco-editor@0.46.0)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-alert-dialog': + specifier: ^1.0.5 + version: 1.0.5(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-avatar': specifier: ^1.0.4 version: 1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-collapsible': + specifier: ^1.0.3 + version: 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-dialog': specifier: ^1.0.5 version: 1.0.5(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0) @@ -7165,6 +7171,32 @@ packages: '@babel/runtime': 7.24.0 dev: false + /@radix-ui/react-alert-dialog@1.0.5(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-OrVIOcZL0tl6xibeuGt5/+UxoT2N27KCFOPjFyfXMnchxSHZ/OW7cCX2nGlIYJrbHK/fczPcFzAwvNBB6XBNMA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.0 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.21)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.21)(react@18.2.0) + '@radix-ui/react-dialog': 1.0.5(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.21)(react@18.2.0) + '@types/react': 18.2.21 + '@types/react-dom': 18.2.7 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-arrow@1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==} peerDependencies: @@ -7210,6 +7242,34 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-collapsible@1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.0 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.21)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.21)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.21)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.21)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.21)(react@18.2.0) + '@types/react': 18.2.21 + '@types/react-dom': 18.2.7 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.21)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} peerDependencies: diff --git a/src/client/components/CommonHeader.tsx b/src/client/components/CommonHeader.tsx index 5d3dff1..fa7e794 100644 --- a/src/client/components/CommonHeader.tsx +++ b/src/client/components/CommonHeader.tsx @@ -1,7 +1,9 @@ import React from 'react'; +import { TipIcon } from './TipIcon'; interface CommonHeaderProps { title: string; + desc?: React.ReactNode; actions?: React.ReactNode; } export const CommonHeader: React.FC = React.memo((props) => { @@ -9,6 +11,8 @@ export const CommonHeader: React.FC = React.memo((props) => { <>

{props.title}

+ {props.desc && } + {props.actions &&
{props.actions}
} ); diff --git a/src/client/components/CommonList.tsx b/src/client/components/CommonList.tsx index 94d1ca1..b8edf09 100644 --- a/src/client/components/CommonList.tsx +++ b/src/client/components/CommonList.tsx @@ -11,7 +11,7 @@ export interface CommonListItem { id: string; title: string; content?: React.ReactNode; - tags: string[]; + tags?: string[]; href: string; } @@ -91,7 +91,7 @@ export const CommonList: React.FC = React.memo((props) => {
{item.content}
- {item.tags.length > 0 ? ( + {Array.isArray(item.tags) && item.tags.length > 0 ? (
{item.tags.map((tag) => ( diff --git a/src/client/components/CommonWrapper.tsx b/src/client/components/CommonWrapper.tsx index f92f91e..9f918c1 100644 --- a/src/client/components/CommonWrapper.tsx +++ b/src/client/components/CommonWrapper.tsx @@ -8,9 +8,11 @@ export const CommonWrapper: React.FC = React.memo( (props) => { return (
-
- {props.header} -
+ {props.header && ( +
+ {props.header} +
+ )} diff --git a/src/client/components/TipIcon.tsx b/src/client/components/TipIcon.tsx new file mode 100644 index 0000000..1efe87c --- /dev/null +++ b/src/client/components/TipIcon.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'; +import { LuHelpCircle } from 'react-icons/lu'; + +interface TipIconProps { + className?: string; + content: React.ReactNode; +} +export const TipIcon: React.FC = React.memo((props) => { + const { className, content } = props; + + return ( + + + + + + {content} + + ); +}); +TipIcon.displayName = 'TipIcon'; diff --git a/src/client/components/ui/alert-dialog.tsx b/src/client/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..1db4fa3 --- /dev/null +++ b/src/client/components/ui/alert-dialog.tsx @@ -0,0 +1,139 @@ +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/utils/style" +import { buttonVariants } from "@/components/ui/button" + +const AlertDialog = AlertDialogPrimitive.Root + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger + +const AlertDialogPortal = AlertDialogPrimitive.Portal + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)) +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogHeader.displayName = "AlertDialogHeader" + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogFooter.displayName = "AlertDialogFooter" + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/src/client/components/ui/collapsible.tsx b/src/client/components/ui/collapsible.tsx new file mode 100644 index 0000000..386a693 --- /dev/null +++ b/src/client/components/ui/collapsible.tsx @@ -0,0 +1,42 @@ +import { cn } from '@/utils/style'; +import * as CollapsiblePrimitive from '@radix-ui/react-collapsible'; +import React from 'react'; +import { LuChevronRight } from 'react-icons/lu'; + +const Collapsible = CollapsiblePrimitive.Root; + +const CollapsibleTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ asChild, className, ...props }, ref) => { + let children = props.children; + if (typeof props.children === 'string') { + children = ( +
+ + + {props.children} +
+ ); + } + + return ( + + {children} + + ); +}); +CollapsibleTrigger.displayName = + CollapsiblePrimitive.CollapsibleTrigger.displayName; + +const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent; + +export { Collapsible, CollapsibleTrigger, CollapsibleContent }; diff --git a/src/client/package.json b/src/client/package.json index a6f76ed..1f35300 100644 --- a/src/client/package.json +++ b/src/client/package.json @@ -7,6 +7,7 @@ "scripts": { "dev": "vite --port 10000", "build": "vite build", + "ui:add": "shadcn-ui add", "translation:extract": "i18next-toolkit extract", "translation:scan": "i18next-toolkit scan", "translation:translate": "i18next-toolkit translate", @@ -23,7 +24,9 @@ "@hookform/resolvers": "^3.3.4", "@loadable/component": "^5.16.3", "@monaco-editor/react": "^4.6.0", + "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-avatar": "^1.0.4", + "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.0", diff --git a/src/client/routeTree.gen.ts b/src/client/routeTree.gen.ts index d8e26e8..6a873fc 100644 --- a/src/client/routeTree.gen.ts +++ b/src/client/routeTree.gen.ts @@ -12,6 +12,7 @@ import { Route as rootRoute } from './routes/__root' import { Route as WebsiteImport } from './routes/website' +import { Route as TelemetryImport } from './routes/telemetry' import { Route as RegisterImport } from './routes/register' import { Route as MonitorImport } from './routes/monitor' import { Route as LoginImport } from './routes/login' @@ -19,6 +20,8 @@ import { Route as DashboardImport } from './routes/dashboard' import { Route as IndexImport } from './routes/index' import { Route as WebsiteAddImport } from './routes/website/add' import { Route as WebsiteWebsiteIdImport } from './routes/website/$websiteId' +import { Route as TelemetryAddImport } from './routes/telemetry/add' +import { Route as TelemetryTelemetryIdImport } from './routes/telemetry/$telemetryId' import { Route as MonitorAddImport } from './routes/monitor/add' import { Route as MonitorMonitorIdImport } from './routes/monitor/$monitorId' @@ -29,6 +32,11 @@ const WebsiteRoute = WebsiteImport.update({ getParentRoute: () => rootRoute, } as any) +const TelemetryRoute = TelemetryImport.update({ + path: '/telemetry', + getParentRoute: () => rootRoute, +} as any) + const RegisterRoute = RegisterImport.update({ path: '/register', getParentRoute: () => rootRoute, @@ -64,6 +72,16 @@ const WebsiteWebsiteIdRoute = WebsiteWebsiteIdImport.update({ getParentRoute: () => WebsiteRoute, } as any) +const TelemetryAddRoute = TelemetryAddImport.update({ + path: '/add', + getParentRoute: () => TelemetryRoute, +} as any) + +const TelemetryTelemetryIdRoute = TelemetryTelemetryIdImport.update({ + path: '/$telemetryId', + getParentRoute: () => TelemetryRoute, +} as any) + const MonitorAddRoute = MonitorAddImport.update({ path: '/add', getParentRoute: () => MonitorRoute, @@ -98,6 +116,10 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof RegisterImport parentRoute: typeof rootRoute } + '/telemetry': { + preLoaderRoute: typeof TelemetryImport + parentRoute: typeof rootRoute + } '/website': { preLoaderRoute: typeof WebsiteImport parentRoute: typeof rootRoute @@ -110,6 +132,14 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof MonitorAddImport parentRoute: typeof MonitorImport } + '/telemetry/$telemetryId': { + preLoaderRoute: typeof TelemetryTelemetryIdImport + parentRoute: typeof TelemetryImport + } + '/telemetry/add': { + preLoaderRoute: typeof TelemetryAddImport + parentRoute: typeof TelemetryImport + } '/website/$websiteId': { preLoaderRoute: typeof WebsiteWebsiteIdImport parentRoute: typeof WebsiteImport @@ -129,6 +159,7 @@ export const routeTree = rootRoute.addChildren([ LoginRoute, MonitorRoute.addChildren([MonitorMonitorIdRoute, MonitorAddRoute]), RegisterRoute, + TelemetryRoute.addChildren([TelemetryTelemetryIdRoute, TelemetryAddRoute]), WebsiteRoute.addChildren([WebsiteWebsiteIdRoute, WebsiteAddRoute]), ]) diff --git a/src/client/routes/telemetry.tsx b/src/client/routes/telemetry.tsx new file mode 100644 index 0000000..31de183 --- /dev/null +++ b/src/client/routes/telemetry.tsx @@ -0,0 +1,115 @@ +import { trpc } from '@/api/trpc'; +import { CommonHeader } from '@/components/CommonHeader'; +import { CommonList } from '@/components/CommonList'; +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 { useCurrentWorkspaceId } from '@/store/user'; +import { routeAuthBeforeLoad } from '@/utils/route'; +import { Trans, useTranslation } from '@i18next-toolkit/react'; +import { + createFileRoute, + useNavigate, + useRouterState, +} from '@tanstack/react-router'; +import { LuPlus } from 'react-icons/lu'; + +export const Route = createFileRoute('/telemetry')({ + beforeLoad: routeAuthBeforeLoad, + component: TelemetryComponent, +}); + +function TelemetryComponent() { + const workspaceId = useCurrentWorkspaceId(); + const { t } = useTranslation(); + const { data = [] } = trpc.telemetry.all.useQuery({ + workspaceId, + }); + const navigate = useNavigate(); + const pathname = useRouterState({ + select: (state) => state.location.pathname, + }); + + const items = data.map((item) => ({ + id: item.id, + title: item.name, + href: `/telemetry/${item.id}`, + })); + + useDataReady( + () => data.length > 0, + () => { + if (pathname === Route.fullPath) { + navigate({ + to: '/telemetry/$telemetryId', + params: { + telemetryId: data[0].id, + }, + }); + } + } + ); + + const handleClickAdd = useEvent(() => { + navigate({ + to: '/telemetry/add', + }); + }); + + return ( + +

+ + Telemetry is a technology that reports access data even on + pages that are not under your control. As long as the + other website allows the insertion of third-party images + (e.g., forums, blogs, and various rich-text editors), then + the data can be collected and used to analyze the images + when they are loaded by the user. + +

+ +

+ + Generally, we will use a one-pixel blank image so that it + will not affect the user's normal use. + +

+ +

+ + At the same time, we can also use it in some client-side + application scenarios, such as collecting the frequency of + cli usage, such as collecting the installation of + selfhosted apps, and so on. + +

+
+ } + actions={ + + } + /> + } + > + + + } + /> + ); +} diff --git a/src/client/routes/telemetry/$telemetryId.tsx b/src/client/routes/telemetry/$telemetryId.tsx new file mode 100644 index 0000000..e8d1dd9 --- /dev/null +++ b/src/client/routes/telemetry/$telemetryId.tsx @@ -0,0 +1,170 @@ +import { trpc } from '@/api/trpc'; +import { CommonHeader } from '@/components/CommonHeader'; +import { CommonWrapper } from '@/components/CommonWrapper'; +import { TelemetryMetricsTable } from '@/components/telemetry/TelemetryMetricsTable'; +import { TelemetryOverview } from '@/components/telemetry/TelemetryOverview'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from '@/components/ui/alert-dialog'; +import { Button } from '@/components/ui/button'; +import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'; +import { useGlobalRangeDate } from '@/hooks/useGlobalRangeDate'; +import { useCurrentWorkspaceId } from '@/store/user'; +import { routeAuthBeforeLoad } from '@/utils/route'; +import { Trans, useTranslation } from '@i18next-toolkit/react'; +import { createFileRoute } from '@tanstack/react-router'; +import { Card, Typography } from 'antd'; +import { LuChevronRight, LuCode2 } from 'react-icons/lu'; +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from '@/components/ui/collapsible'; + +export const Route = createFileRoute('/telemetry/$telemetryId')({ + beforeLoad: routeAuthBeforeLoad, + component: TelemetryDetailComponent, +}); + +function TelemetryDetailComponent() { + const { telemetryId } = Route.useParams<{ telemetryId: string }>(); + const workspaceId = useCurrentWorkspaceId(); + const { t } = useTranslation(); + const { startDate, endDate } = useGlobalRangeDate(); + const { data: info } = trpc.telemetry.info.useQuery({ + workspaceId, + telemetryId, + }); + + const startAt = startDate.valueOf(); + const endAt = endDate.valueOf(); + + const blankGif = `${window.location.origin}/telemetry/${workspaceId}/${telemetryId}.gif`; + const countBadgeUrl = `${window.location.origin}/telemetry/${workspaceId}/${telemetryId}/badge.svg`; + + return ( + + + + + + + Usage + + +
+

Here is some way to use telemetry:

+ + Insert to article: + +

+ if your article support raw html, you can direct insert it{' '} + + {blankGif} + +

+ + + Advanced + + +
+

+ Some website will not allow send `referer` field. so + its maybe can not track source. so you can mark it by + yourself. for example: +

+

+ + {blankGif}?url=https://xxxxxxxx + +

+
+
+
+ + + Count your website visitor: + +

+ if your article support raw html, you can direct insert it{' '} + + {countBadgeUrl} + +

+

+ Like this: +

+
+ + + {t('Get!')} + +
+ + } + /> + } + > + + + + + + + + + + + + + + + + + + + + + +
+ ); +} diff --git a/src/client/routes/telemetry/add.tsx b/src/client/routes/telemetry/add.tsx new file mode 100644 index 0000000..4c893fd --- /dev/null +++ b/src/client/routes/telemetry/add.tsx @@ -0,0 +1,101 @@ +import { createFileRoute, useNavigate } from '@tanstack/react-router'; +import { useTranslation } from '@i18next-toolkit/react'; +import { Button } from '@/components/ui/button'; +import { useEvent } from '@/hooks/useEvent'; +import { useCurrentWorkspaceId } from '@/store/user'; +import { trpc } from '@/api/trpc'; +import { Card, CardContent, CardFooter } from '@/components/ui/card'; +import { CommonWrapper } from '@/components/CommonWrapper'; +import { routeAuthBeforeLoad } from '@/utils/route'; +import { z } from 'zod'; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; + +export const Route = createFileRoute('/telemetry/add')({ + beforeLoad: routeAuthBeforeLoad, + component: TelemetryAddComponent, +}); + +const addFormSchema = z.object({ + name: z.string(), +}); + +function TelemetryAddComponent() { + const { t } = useTranslation(); + const workspaceId = useCurrentWorkspaceId(); + const addTelemetryMutation = trpc.telemetry.upsert.useMutation(); + const utils = trpc.useUtils(); + const navigate = useNavigate(); + + const form = useForm>({ + resolver: zodResolver(addFormSchema), + defaultValues: { + name: '', + }, + }); + + const onSubmit = useEvent(async (values: z.infer) => { + const res = await addTelemetryMutation.mutateAsync({ + workspaceId, + name: values.name, + }); + + utils.telemetry.all.refetch(); + form.reset(); + + navigate({ + to: '/telemetry/$telemetryId', + params: { + telemetryId: res.id, + }, + }); + }); + + return ( + {t('Add Website')}} + > +
+
+ + + + ( + + {t('Telemetry Name')} + + + + + {t('Telemetry Name to Display')} + + + + )} + /> + + + + + +
+ +
+
+ ); +} diff --git a/src/client/routes/website.tsx b/src/client/routes/website.tsx index a176efc..085ca66 100644 --- a/src/client/routes/website.tsx +++ b/src/client/routes/website.tsx @@ -36,7 +36,6 @@ function WebsiteComponent() { id: item.id, title: item.name, content: item.domain, - tags: [], href: `/website/${item.id}`, })); diff --git a/src/client/routes/website/add.tsx b/src/client/routes/website/add.tsx index 365172a..8db7e2c 100644 --- a/src/client/routes/website/add.tsx +++ b/src/client/routes/website/add.tsx @@ -1,10 +1,9 @@ import { routeAuthBeforeLoad } from '@/utils/route'; import { createFileRoute, useNavigate } from '@tanstack/react-router'; -import { useState } from 'react'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; -import { t, useTranslation } from '@i18next-toolkit/react'; +import { useTranslation } from '@i18next-toolkit/react'; import { Button } from '@/components/ui/button'; import { Form,