aboutsummaryrefslogtreecommitdiff
path: root/src/app/(main)/pixels/PixelEditForm.tsx
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-01-24 13:09:50 +0000
committerFuwn <[email protected]>2026-01-24 13:09:50 +0000
commit396acf3bbbe00a192cb0ea0a9ccf91b1d8d2850b (patch)
treeb9df4ca6a70db45cfffbae6fdd7252e20fb8e93c /src/app/(main)/pixels/PixelEditForm.tsx
downloadumami-396acf3bbbe00a192cb0ea0a9ccf91b1d8d2850b.tar.xz
umami-396acf3bbbe00a192cb0ea0a9ccf91b1d8d2850b.zip
Initial commitHEADmain
Created from https://vercel.com/new
Diffstat (limited to 'src/app/(main)/pixels/PixelEditForm.tsx')
-rw-r--r--src/app/(main)/pixels/PixelEditForm.tsx129
1 files changed, 129 insertions, 0 deletions
diff --git a/src/app/(main)/pixels/PixelEditForm.tsx b/src/app/(main)/pixels/PixelEditForm.tsx
new file mode 100644
index 0000000..aedd3a3
--- /dev/null
+++ b/src/app/(main)/pixels/PixelEditForm.tsx
@@ -0,0 +1,129 @@
+import {
+ Button,
+ Column,
+ Form,
+ FormField,
+ FormSubmitButton,
+ Icon,
+ Label,
+ Loading,
+ Row,
+ TextField,
+} from '@umami/react-zen';
+import { useEffect, useState } from 'react';
+import { useConfig, useMessages, usePixelQuery } from '@/components/hooks';
+import { useUpdateQuery } from '@/components/hooks/queries/useUpdateQuery';
+import { RefreshCw } from '@/components/icons';
+import { PIXELS_URL } from '@/lib/constants';
+import { getRandomChars } from '@/lib/generate';
+
+const generateId = () => getRandomChars(9);
+
+export function PixelEditForm({
+ pixelId,
+ teamId,
+ onSave,
+ onClose,
+}: {
+ pixelId?: string;
+ teamId?: string;
+ onSave?: () => void;
+ onClose?: () => void;
+}) {
+ const { formatMessage, labels, messages, getErrorMessage } = useMessages();
+ const { mutateAsync, error, isPending, touch, toast } = useUpdateQuery(
+ pixelId ? `/pixels/${pixelId}` : '/pixels',
+ {
+ id: pixelId,
+ teamId,
+ },
+ );
+ const { pixelsUrl } = useConfig();
+ const hostUrl = pixelsUrl || PIXELS_URL;
+ const { data, isLoading } = usePixelQuery(pixelId);
+ const [slug, setSlug] = useState(generateId());
+
+ const handleSubmit = async (data: any) => {
+ await mutateAsync(data, {
+ onSuccess: async () => {
+ toast(formatMessage(messages.saved));
+ touch('pixels');
+ onSave?.();
+ onClose?.();
+ },
+ });
+ };
+
+ const handleSlug = () => {
+ const slug = generateId();
+
+ setSlug(slug);
+
+ return slug;
+ };
+
+ useEffect(() => {
+ if (data) {
+ setSlug(data.slug);
+ }
+ }, [data]);
+
+ if (pixelId && isLoading) {
+ return <Loading placement="absolute" />;
+ }
+
+ return (
+ <Form onSubmit={handleSubmit} error={getErrorMessage(error)} defaultValues={{ slug, ...data }}>
+ {({ setValue }) => {
+ return (
+ <>
+ <FormField
+ label={formatMessage(labels.name)}
+ name="name"
+ rules={{ required: formatMessage(labels.required) }}
+ >
+ <TextField autoComplete="off" />
+ </FormField>
+
+ <FormField
+ name="slug"
+ rules={{
+ required: formatMessage(labels.required),
+ }}
+ style={{ display: 'none' }}
+ >
+ <input type="hidden" />
+ </FormField>
+
+ <Column>
+ <Label>{formatMessage(labels.link)}</Label>
+ <Row alignItems="center" gap>
+ <TextField
+ value={`${hostUrl}/${slug}`}
+ autoComplete="off"
+ isReadOnly
+ allowCopy
+ style={{ width: '100%' }}
+ />
+ <Button onPress={() => setValue('slug', handleSlug(), { shouldDirty: true })}>
+ <Icon>
+ <RefreshCw />
+ </Icon>
+ </Button>
+ </Row>
+ </Column>
+
+ <Row justifyContent="flex-end" paddingTop="3" gap="3">
+ {onClose && (
+ <Button isDisabled={isPending} onPress={onClose}>
+ {formatMessage(labels.cancel)}
+ </Button>
+ )}
+ <FormSubmitButton isDisabled={false}>{formatMessage(labels.save)}</FormSubmitButton>
+ </Row>
+ </>
+ );
+ }}
+ </Form>
+ );
+}