From 994118169793fdc2ff34d3246ad13f8e91c7f2a4 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Mon, 9 Mar 2026 22:09:54 -0700 Subject: [PATCH] Move pixel and link editing to dedicated pages --- src/app/(main)/links/LinkEditButton.tsx | 13 +-- src/app/(main)/links/LinkEditForm.tsx | 3 +- src/app/(main)/links/[linkId]/LinkHeader.tsx | 10 +- .../(main)/links/[linkId]/LinkShareButton.tsx | 14 --- ...{LinkShareDialog.tsx => LinkShareForm.tsx} | 6 +- .../links/[linkId]/edit/LinkEditPage.tsx | 46 ++++++++ src/app/(main)/links/[linkId]/edit/page.tsx | 18 +++ src/app/(main)/pixels/PixelEditButton.tsx | 13 +-- src/app/(main)/pixels/PixelEditForm.tsx | 3 +- .../(main)/pixels/[pixelId]/PixelHeader.tsx | 10 +- .../pixels/[pixelId]/PixelShareButton.tsx | 14 --- ...ixelShareDialog.tsx => PixelShareForm.tsx} | 6 +- .../pixels/[pixelId]/edit/PixelEditPage.tsx | 46 ++++++++ src/app/(main)/pixels/[pixelId]/edit/page.tsx | 18 +++ .../share/SimpleShareEditButton.tsx | 14 +++ src/components/share/SimpleShareEditForm.tsx | 104 ++++++++++++++++++ src/components/share/SimpleSharesTable.tsx | 7 +- 17 files changed, 285 insertions(+), 60 deletions(-) delete mode 100644 src/app/(main)/links/[linkId]/LinkShareButton.tsx rename src/app/(main)/links/[linkId]/{LinkShareDialog.tsx => LinkShareForm.tsx} (90%) create mode 100644 src/app/(main)/links/[linkId]/edit/LinkEditPage.tsx create mode 100644 src/app/(main)/links/[linkId]/edit/page.tsx delete mode 100644 src/app/(main)/pixels/[pixelId]/PixelShareButton.tsx rename src/app/(main)/pixels/[pixelId]/{PixelShareDialog.tsx => PixelShareForm.tsx} (90%) create mode 100644 src/app/(main)/pixels/[pixelId]/edit/PixelEditPage.tsx create mode 100644 src/app/(main)/pixels/[pixelId]/edit/page.tsx create mode 100644 src/components/share/SimpleShareEditButton.tsx create mode 100644 src/components/share/SimpleShareEditForm.tsx diff --git a/src/app/(main)/links/LinkEditButton.tsx b/src/app/(main)/links/LinkEditButton.tsx index bd309282f..8969ad1bd 100644 --- a/src/app/(main)/links/LinkEditButton.tsx +++ b/src/app/(main)/links/LinkEditButton.tsx @@ -1,16 +1,15 @@ +import { LinkButton } from '@/components/common/LinkButton'; +import { useNavigation } from '@/components/hooks'; import { useMessages } from '@/components/hooks'; import { Edit } from '@/components/icons'; -import { DialogButton } from '@/components/input/DialogButton'; -import { LinkEditForm } from './LinkEditForm'; export function LinkEditButton({ linkId }: { linkId: string }) { const { t, labels } = useMessages(); + const { renderUrl } = useNavigation(); return ( - } title={t(labels.link)} variant="quiet" width="800px"> - {({ close }) => { - return ; - }} - + + + ); } diff --git a/src/app/(main)/links/LinkEditForm.tsx b/src/app/(main)/links/LinkEditForm.tsx index 50f3bdb60..4f8dcf7ae 100644 --- a/src/app/(main)/links/LinkEditForm.tsx +++ b/src/app/(main)/links/LinkEditForm.tsx @@ -40,7 +40,8 @@ export function LinkEditForm({ teamId, }, ); - const { linksUrl } = useConfig(); + const config = useConfig(); + const linksUrl = config?.linksUrl; const hostUrl = linksUrl || LINKS_URL; const { data, isLoading } = useLinkQuery(linkId); const [defaultSlug] = useState(generateId()); diff --git a/src/app/(main)/links/[linkId]/LinkHeader.tsx b/src/app/(main)/links/[linkId]/LinkHeader.tsx index 9765dea76..ee145963d 100644 --- a/src/app/(main)/links/[linkId]/LinkHeader.tsx +++ b/src/app/(main)/links/[linkId]/LinkHeader.tsx @@ -2,9 +2,8 @@ import { Row } from '@umami/react-zen'; import { IconLabel } from '@/components/common/IconLabel'; import { LinkButton } from '@/components/common/LinkButton'; import { PageHeader } from '@/components/common/PageHeader'; -import { useLink, useMessages, useSlug } from '@/components/hooks'; -import { ExternalLink, Link } from '@/components/icons'; -import { LinkShareButton } from './LinkShareButton'; +import { useLink, useMessages, useNavigation, useSlug } from '@/components/hooks'; +import { Edit, ExternalLink, Link } from '@/components/icons'; export function LinkHeader({ showActions = true }: { showActions?: boolean }) { const link = useLink(); @@ -18,11 +17,14 @@ export function LinkHeader({ showActions = true }: { showActions?: boolean }) { function LinkHeaderActions({ linkId, slug }: { linkId: string; slug: string }) { const { t, labels } = useMessages(); + const { renderUrl } = useNavigation(); const { getSlugUrl } = useSlug('link'); return ( - + + } label={t(labels.edit)} /> + } label={t(labels.view)} /> diff --git a/src/app/(main)/links/[linkId]/LinkShareButton.tsx b/src/app/(main)/links/[linkId]/LinkShareButton.tsx deleted file mode 100644 index 4c3084ed5..000000000 --- a/src/app/(main)/links/[linkId]/LinkShareButton.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { useMessages } from '@/components/hooks'; -import { Share } from '@/components/icons'; -import { DialogButton } from '@/components/input/DialogButton'; -import { LinkShareDialog } from './LinkShareDialog'; - -export function LinkShareButton({ linkId }: { linkId: string }) { - const { t, labels } = useMessages(); - - return ( - } label={t(labels.share)} title={null} width="900px"> - - - ); -} diff --git a/src/app/(main)/links/[linkId]/LinkShareDialog.tsx b/src/app/(main)/links/[linkId]/LinkShareForm.tsx similarity index 90% rename from src/app/(main)/links/[linkId]/LinkShareDialog.tsx rename to src/app/(main)/links/[linkId]/LinkShareForm.tsx index 3bf55ebf5..6e6c4f8dc 100644 --- a/src/app/(main)/links/[linkId]/LinkShareDialog.tsx +++ b/src/app/(main)/links/[linkId]/LinkShareForm.tsx @@ -7,19 +7,19 @@ import { Plus } from '@/components/icons'; import { SimpleShareCreateForm } from '@/components/share/SimpleShareCreateForm'; import { SimpleSharesTable } from '@/components/share/SimpleSharesTable'; -export function LinkShareDialog({ linkId }: { linkId: string }) { +export function LinkShareForm({ linkId }: { linkId: string }) { const { data, error, isLoading } = useLinkSharesQuery({ linkId }); const shares = data?.data || []; const hasShares = shares.length > 0; return ( - + ); } -function LinkShareDialogContent({ +function LinkShareFormContent({ linkId, hasShares, shares, diff --git a/src/app/(main)/links/[linkId]/edit/LinkEditPage.tsx b/src/app/(main)/links/[linkId]/edit/LinkEditPage.tsx new file mode 100644 index 000000000..356873ceb --- /dev/null +++ b/src/app/(main)/links/[linkId]/edit/LinkEditPage.tsx @@ -0,0 +1,46 @@ +'use client'; +import { Column } from '@umami/react-zen'; +import Link from 'next/link'; +import { LinkEditForm } from '@/app/(main)/links/LinkEditForm'; +import { LinkProvider } from '@/app/(main)/links/LinkProvider'; +import { LinkShareForm } from '@/app/(main)/links/[linkId]/LinkShareForm'; +import { Panel } from '@/components/common/Panel'; +import { IconLabel } from '@/components/common/IconLabel'; +import { PageHeader } from '@/components/common/PageHeader'; +import { useLink, useMessages, useNavigation } from '@/components/hooks'; +import { ArrowLeft, Link as LinkIcon } from '@/components/icons'; + +export function LinkEditPage({ linkId }: { linkId: string }) { + return ( + + + + + + + + + + + + + + ); +} + +function LinkEditHeader() { + const link = useLink(); + const { t, labels } = useMessages(); + const { renderUrl } = useNavigation(); + + return ( + <> + + + } label={t(labels.link)} /> + + + } /> + + ); +} diff --git a/src/app/(main)/links/[linkId]/edit/page.tsx b/src/app/(main)/links/[linkId]/edit/page.tsx new file mode 100644 index 000000000..1720b696e --- /dev/null +++ b/src/app/(main)/links/[linkId]/edit/page.tsx @@ -0,0 +1,18 @@ +import type { Metadata } from 'next'; +import { getLink } from '@/queries/prisma'; +import { LinkEditPage } from './LinkEditPage'; + +export default async function ({ params }: { params: Promise<{ linkId: string }> }) { + const { linkId } = await params; + const link = await getLink(linkId); + + if (!link || link.deletedAt) { + return null; + } + + return ; +} + +export const metadata: Metadata = { + title: 'Edit Link', +}; diff --git a/src/app/(main)/pixels/PixelEditButton.tsx b/src/app/(main)/pixels/PixelEditButton.tsx index 2a16118c2..878387dc3 100644 --- a/src/app/(main)/pixels/PixelEditButton.tsx +++ b/src/app/(main)/pixels/PixelEditButton.tsx @@ -1,16 +1,15 @@ +import { LinkButton } from '@/components/common/LinkButton'; +import { useNavigation } from '@/components/hooks'; import { useMessages } from '@/components/hooks'; import { Edit } from '@/components/icons'; -import { DialogButton } from '@/components/input/DialogButton'; -import { PixelEditForm } from './PixelEditForm'; export function PixelEditButton({ pixelId }: { pixelId: string }) { const { t, labels } = useMessages(); + const { renderUrl } = useNavigation(); return ( - } title={t(labels.addPixel)} variant="quiet" width="600px"> - {({ close }) => { - return ; - }} - + + + ); } diff --git a/src/app/(main)/pixels/PixelEditForm.tsx b/src/app/(main)/pixels/PixelEditForm.tsx index 54c5e64c5..aa3061d55 100644 --- a/src/app/(main)/pixels/PixelEditForm.tsx +++ b/src/app/(main)/pixels/PixelEditForm.tsx @@ -38,7 +38,8 @@ export function PixelEditForm({ teamId, }, ); - const { pixelsUrl } = useConfig(); + const config = useConfig(); + const pixelsUrl = config?.pixelsUrl; const hostUrl = pixelsUrl || PIXELS_URL; const { data, isLoading } = usePixelQuery(pixelId); const [slug, setSlug] = useState(generateId()); diff --git a/src/app/(main)/pixels/[pixelId]/PixelHeader.tsx b/src/app/(main)/pixels/[pixelId]/PixelHeader.tsx index 89da44483..5cda3e5da 100644 --- a/src/app/(main)/pixels/[pixelId]/PixelHeader.tsx +++ b/src/app/(main)/pixels/[pixelId]/PixelHeader.tsx @@ -2,9 +2,8 @@ import { Row } from '@umami/react-zen'; import { IconLabel } from '@/components/common/IconLabel'; import { LinkButton } from '@/components/common/LinkButton'; import { PageHeader } from '@/components/common/PageHeader'; -import { useMessages, usePixel, useSlug } from '@/components/hooks'; -import { ExternalLink, Grid2x2 } from '@/components/icons'; -import { PixelShareButton } from './PixelShareButton'; +import { useMessages, useNavigation, usePixel, useSlug } from '@/components/hooks'; +import { Edit, ExternalLink, Grid2x2 } from '@/components/icons'; export function PixelHeader({ showActions = true }: { showActions?: boolean }) { const pixel = usePixel(); @@ -18,11 +17,14 @@ export function PixelHeader({ showActions = true }: { showActions?: boolean }) { function PixelHeaderActions({ pixelId, slug }: { pixelId: string; slug: string }) { const { t, labels } = useMessages(); + const { renderUrl } = useNavigation(); const { getSlugUrl } = useSlug('pixel'); return ( - + + } label={t(labels.edit)} /> + } label={t(labels.view)} /> diff --git a/src/app/(main)/pixels/[pixelId]/PixelShareButton.tsx b/src/app/(main)/pixels/[pixelId]/PixelShareButton.tsx deleted file mode 100644 index 69ffd0adf..000000000 --- a/src/app/(main)/pixels/[pixelId]/PixelShareButton.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { useMessages } from '@/components/hooks'; -import { Share } from '@/components/icons'; -import { DialogButton } from '@/components/input/DialogButton'; -import { PixelShareDialog } from './PixelShareDialog'; - -export function PixelShareButton({ pixelId }: { pixelId: string }) { - const { t, labels } = useMessages(); - - return ( - } label={t(labels.share)} title={null} width="900px"> - - - ); -} diff --git a/src/app/(main)/pixels/[pixelId]/PixelShareDialog.tsx b/src/app/(main)/pixels/[pixelId]/PixelShareForm.tsx similarity index 90% rename from src/app/(main)/pixels/[pixelId]/PixelShareDialog.tsx rename to src/app/(main)/pixels/[pixelId]/PixelShareForm.tsx index 58d6b37ca..43f4d3169 100644 --- a/src/app/(main)/pixels/[pixelId]/PixelShareDialog.tsx +++ b/src/app/(main)/pixels/[pixelId]/PixelShareForm.tsx @@ -7,19 +7,19 @@ import { Plus } from '@/components/icons'; import { SimpleShareCreateForm } from '@/components/share/SimpleShareCreateForm'; import { SimpleSharesTable } from '@/components/share/SimpleSharesTable'; -export function PixelShareDialog({ pixelId }: { pixelId: string }) { +export function PixelShareForm({ pixelId }: { pixelId: string }) { const { data, error, isLoading } = usePixelSharesQuery({ pixelId }); const shares = data?.data || []; const hasShares = shares.length > 0; return ( - + ); } -function PixelShareDialogContent({ +function PixelShareFormContent({ pixelId, hasShares, shares, diff --git a/src/app/(main)/pixels/[pixelId]/edit/PixelEditPage.tsx b/src/app/(main)/pixels/[pixelId]/edit/PixelEditPage.tsx new file mode 100644 index 000000000..4b1b05b3d --- /dev/null +++ b/src/app/(main)/pixels/[pixelId]/edit/PixelEditPage.tsx @@ -0,0 +1,46 @@ +'use client'; +import { Column } from '@umami/react-zen'; +import Link from 'next/link'; +import { PixelEditForm } from '@/app/(main)/pixels/PixelEditForm'; +import { PixelProvider } from '@/app/(main)/pixels/PixelProvider'; +import { PixelShareForm } from '@/app/(main)/pixels/[pixelId]/PixelShareForm'; +import { Panel } from '@/components/common/Panel'; +import { IconLabel } from '@/components/common/IconLabel'; +import { PageHeader } from '@/components/common/PageHeader'; +import { useMessages, useNavigation, usePixel } from '@/components/hooks'; +import { ArrowLeft, Grid2x2 } from '@/components/icons'; + +export function PixelEditPage({ pixelId }: { pixelId: string }) { + return ( + + + + + + + + + + + + + + ); +} + +function PixelEditHeader() { + const pixel = usePixel(); + const { t, labels } = useMessages(); + const { renderUrl } = useNavigation(); + + return ( + <> + + + } label={t(labels.pixel)} /> + + + } /> + + ); +} diff --git a/src/app/(main)/pixels/[pixelId]/edit/page.tsx b/src/app/(main)/pixels/[pixelId]/edit/page.tsx new file mode 100644 index 000000000..2e6b1d387 --- /dev/null +++ b/src/app/(main)/pixels/[pixelId]/edit/page.tsx @@ -0,0 +1,18 @@ +import type { Metadata } from 'next'; +import { getPixel } from '@/queries/prisma'; +import { PixelEditPage } from './PixelEditPage'; + +export default async function ({ params }: { params: Promise<{ pixelId: string }> }) { + const { pixelId } = await params; + const pixel = await getPixel(pixelId); + + if (!pixel || pixel.deletedAt) { + return null; + } + + return ; +} + +export const metadata: Metadata = { + title: 'Edit Pixel', +}; diff --git a/src/components/share/SimpleShareEditButton.tsx b/src/components/share/SimpleShareEditButton.tsx new file mode 100644 index 000000000..eb56fda4d --- /dev/null +++ b/src/components/share/SimpleShareEditButton.tsx @@ -0,0 +1,14 @@ +import { useMessages } from '@/components/hooks'; +import { Edit } from '@/components/icons'; +import { DialogButton } from '@/components/input/DialogButton'; +import { SimpleShareEditForm } from './SimpleShareEditForm'; + +export function SimpleShareEditButton({ shareId }: { shareId: string }) { + const { t, labels } = useMessages(); + + return ( + } title={t(labels.share)} variant="quiet" width="600px"> + {({ close }) => } + + ); +} diff --git a/src/components/share/SimpleShareEditForm.tsx b/src/components/share/SimpleShareEditForm.tsx new file mode 100644 index 000000000..3c83fed52 --- /dev/null +++ b/src/components/share/SimpleShareEditForm.tsx @@ -0,0 +1,104 @@ +import { + Button, + Column, + Form, + FormField, + FormSubmitButton, + Label, + Loading, + Row, + TextField, +} from '@umami/react-zen'; +import { useEffect, useState } from 'react'; +import { useApi, useConfig, useMessages, useModified } from '@/components/hooks'; + +export function SimpleShareEditForm({ + shareId, + onSave, + onClose, +}: { + shareId: string; + onSave?: () => void; + onClose?: () => void; +}) { + const { t, labels, getErrorMessage } = useMessages(); + const config = useConfig(); + const { get, post } = useApi(); + const { touch } = useModified(); + const { modified } = useModified('shares'); + const [share, setShare] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isPending, setIsPending] = useState(false); + const [error, setError] = useState(null); + + const getUrl = (slug: string) => { + return `${config?.cloudMode ? process.env.cloudUrl : window?.location.origin}${process.env.basePath || ''}/share/${slug}`; + }; + + useEffect(() => { + const loadShare = async () => { + setIsLoading(true); + try { + const data = await get(`/share/id/${shareId}`); + setShare(data); + } finally { + setIsLoading(false); + } + }; + + loadShare(); + }, [get, modified, shareId]); + + const handleSubmit = async (data: { name: string }) => { + setIsPending(true); + setError(null); + + try { + await post(`/share/id/${shareId}`, { + name: data.name, + slug: share.slug, + parameters: share.parameters || {}, + }); + + touch('shares'); + onSave?.(); + onClose?.(); + } catch (e) { + setError(e); + } finally { + setIsPending(false); + } + }; + + if (isLoading) { + return ; + } + + return ( +
+ + + + + + + + + + {onClose && ( + + )} + + {t(labels.save)} + + + +
+ ); +} diff --git a/src/components/share/SimpleSharesTable.tsx b/src/components/share/SimpleSharesTable.tsx index b938e6412..01a8dfb9d 100644 --- a/src/components/share/SimpleSharesTable.tsx +++ b/src/components/share/SimpleSharesTable.tsx @@ -4,10 +4,12 @@ import { CopyButton } from '@/components/common/CopyButton'; import { DateDistance } from '@/components/common/DateDistance'; import { ExternalLink } from '@/components/common/ExternalLink'; import { useConfig, useMessages, useMobile } from '@/components/hooks'; +import { SimpleShareEditButton } from './SimpleShareEditButton'; export function SimpleSharesTable(props: DataTableProps) { const { t, labels } = useMessages(); - const { cloudMode } = useConfig(); + const config = useConfig(); + const cloudMode = config?.cloudMode; const { isMobile } = useMobile(); const getUrl = (slug: string) => { @@ -40,9 +42,10 @@ export function SimpleSharesTable(props: DataTableProps) { {(row: any) => } - + {({ id, slug }: any) => ( + )}