From 4a09f2bff6f67b5902bdda98df5ca1aac9269dc8 Mon Sep 17 00:00:00 2001 From: Mike Cao Date: Sat, 24 Jan 2026 02:47:09 -0800 Subject: [PATCH] Share page changes. --- .gitignore | 1 + package.json | 2 +- .../[websiteId]/settings/ShareCreateForm.tsx | 101 ------------------ .../[websiteId]/settings/ShareEditForm.tsx | 66 +++++++----- .../[websiteId]/settings/WebsiteShareForm.tsx | 6 +- src/app/api/share/[slug]/route.ts | 7 +- src/app/share/[...shareId]/ShareFooter.tsx | 4 +- src/app/share/[...shareId]/ShareHeader.tsx | 4 +- src/app/share/[...shareId]/SharePage.tsx | 2 +- src/components/common/PageBody.tsx | 1 + src/lib/types.ts | 6 ++ 11 files changed, 60 insertions(+), 140 deletions(-) delete mode 100644 src/app/(main)/websites/[websiteId]/settings/ShareCreateForm.tsx diff --git a/.gitignore b/.gitignore index 0649999d..8b543f6b 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ pm2.yml .vscode .tool-versions .claude +nul # debug npm-debug.log* diff --git a/package.json b/package.json index 72ae379c..ee22a9ee 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "type": "module", "scripts": { - "dev": "next dev -p 3003 --turbo", + "dev": "next dev -p 3002 --turbo", "build": "npm-run-all check-env build-db check-db build-tracker build-geo build-app", "start": "next start", "build-docker": "npm-run-all build-db build-tracker build-geo build-app", diff --git a/src/app/(main)/websites/[websiteId]/settings/ShareCreateForm.tsx b/src/app/(main)/websites/[websiteId]/settings/ShareCreateForm.tsx deleted file mode 100644 index bf2e050d..00000000 --- a/src/app/(main)/websites/[websiteId]/settings/ShareCreateForm.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { - Button, - Checkbox, - Column, - Form, - FormField, - FormSubmitButton, - Row, - Text, - TextField, -} from '@umami/react-zen'; -import { useState } from 'react'; -import { useApi, useMessages, useModified } from '@/components/hooks'; -import { SHARE_NAV_ITEMS } from './constants'; - -export interface ShareCreateFormProps { - websiteId: string; - onSave?: () => void; - onClose?: () => void; -} - -export function ShareCreateForm({ websiteId, onSave, onClose }: ShareCreateFormProps) { - const { formatMessage, labels } = useMessages(); - const { post } = useApi(); - const { touch } = useModified(); - const [isPending, setIsPending] = useState(false); - - // Build default values - only overview and events enabled by default - const defaultValues: Record = {}; - SHARE_NAV_ITEMS.forEach(section => { - section.items.forEach(item => { - defaultValues[item.id] = item.id === 'overview' || item.id === 'events'; - }); - }); - - // Get all item ids for validation - const allItemIds = SHARE_NAV_ITEMS.flatMap(section => section.items.map(item => item.id)); - - const handleSubmit = async (data: any) => { - setIsPending(true); - try { - const parameters: Record = {}; - SHARE_NAV_ITEMS.forEach(section => { - section.items.forEach(item => { - parameters[item.id] = data[item.id] ?? false; - }); - }); - await post(`/websites/${websiteId}/shares`, { name: data.name, parameters }); - touch('shares'); - onSave?.(); - onClose?.(); - } finally { - setIsPending(false); - } - }; - - return ( -
- {({ watch }) => { - const values = watch(); - const hasSelection = allItemIds.some(id => values[id]); - - return ( - - - - - {SHARE_NAV_ITEMS.map(section => ( - - - {formatMessage((labels as any)[section.section])} - - - {section.items.map(item => ( - - {formatMessage((labels as any)[item.label])} - - ))} - - - ))} - - {onClose && ( - - )} - - {formatMessage(labels.save)} - - - - ); - }} -
- ); -} diff --git a/src/app/(main)/websites/[websiteId]/settings/ShareEditForm.tsx b/src/app/(main)/websites/[websiteId]/settings/ShareEditForm.tsx index dc8a41e6..4b86247a 100644 --- a/src/app/(main)/websites/[websiteId]/settings/ShareEditForm.tsx +++ b/src/app/(main)/websites/[websiteId]/settings/ShareEditForm.tsx @@ -14,25 +14,30 @@ import { } from '@umami/react-zen'; import { useEffect, useState } from 'react'; import { useApi, useConfig, useMessages, useModified } from '@/components/hooks'; -import { useUpdateQuery } from '@/components/hooks/queries/useUpdateQuery'; import { SHARE_NAV_ITEMS } from './constants'; export function ShareEditForm({ shareId, + websiteId, onSave, onClose, }: { - shareId: string; + shareId?: string; + websiteId?: string; onSave?: () => void; onClose?: () => void; }) { - const { formatMessage, labels, messages, getErrorMessage } = useMessages(); - const { mutateAsync, error, isPending, touch, toast } = useUpdateQuery(`/share/id/${shareId}`); + const { formatMessage, labels, getErrorMessage } = useMessages(); const { cloudMode } = useConfig(); - const { get } = useApi(); + const { get, post } = useApi(); + const { touch } = useModified(); const { modified } = useModified('shares'); const [share, setShare] = useState(null); - const [isLoading, setIsLoading] = useState(true); + const [isLoading, setIsLoading] = useState(!!shareId); + const [isPending, setIsPending] = useState(false); + const [error, setError] = useState(null); + + const isEditing = !!shareId; const getUrl = (slug: string) => { if (cloudMode) { @@ -42,6 +47,8 @@ export function ShareEditForm({ }; useEffect(() => { + if (!shareId) return; + const loadShare = async () => { setIsLoading(true); try { @@ -62,24 +69,30 @@ export function ShareEditForm({ }); }); - await mutateAsync( - { name: data.name, slug: share.slug, parameters }, - { - onSuccess: async () => { - toast(formatMessage(messages.saved)); - touch('shares'); - onSave?.(); - onClose?.(); - }, - }, - ); + setIsPending(true); + setError(null); + + try { + if (isEditing) { + await post(`/share/id/${shareId}`, { name: data.name, slug: share.slug, parameters }); + } else { + await post(`/websites/${websiteId}/shares`, { name: data.name, parameters }); + } + touch('shares'); + onSave?.(); + onClose?.(); + } catch (e) { + setError(e); + } finally { + setIsPending(false); + } }; if (isLoading) { return ; } - const url = getUrl(share?.slug || ''); + const url = isEditing ? getUrl(share?.slug || '') : null; // Build default values from share parameters const defaultValues: Record = { @@ -103,16 +116,18 @@ export function ShareEditForm({ return ( - - - - + {url && ( + + + + + )} - + {SHARE_NAV_ITEMS.map(section => ( @@ -134,7 +149,10 @@ export function ShareEditForm({ {formatMessage(labels.cancel)} )} - + {formatMessage(labels.save)} diff --git a/src/app/(main)/websites/[websiteId]/settings/WebsiteShareForm.tsx b/src/app/(main)/websites/[websiteId]/settings/WebsiteShareForm.tsx index 7453b402..8472ca97 100644 --- a/src/app/(main)/websites/[websiteId]/settings/WebsiteShareForm.tsx +++ b/src/app/(main)/websites/[websiteId]/settings/WebsiteShareForm.tsx @@ -2,7 +2,7 @@ import { Column, Heading, Row, Text } from '@umami/react-zen'; import { Plus } from 'lucide-react'; import { useMessages, useWebsiteSharesQuery } from '@/components/hooks'; import { DialogButton } from '@/components/input/DialogButton'; -import { ShareCreateForm } from './ShareCreateForm'; +import { ShareEditForm } from './ShareEditForm'; import { SharesTable } from './SharesTable'; export interface WebsiteShareFormProps { @@ -25,9 +25,9 @@ export function WebsiteShareForm({ websiteId }: WebsiteShareFormProps) { label={formatMessage(labels.add)} title={formatMessage(labels.share)} variant="primary" - width="400px" + width="600px" > - {({ close }) => } + {({ close }) => } {hasShares ? ( diff --git a/src/app/api/share/[slug]/route.ts b/src/app/api/share/[slug]/route.ts index 62e2fc42..e7d5372f 100644 --- a/src/app/api/share/[slug]/route.ts +++ b/src/app/api/share/[slug]/route.ts @@ -4,14 +4,9 @@ import { createToken } from '@/lib/jwt'; import prisma from '@/lib/prisma'; import redis from '@/lib/redis'; import { json, notFound } from '@/lib/response'; +import type { WhiteLabel } from '@/lib/types'; import { getShareByCode, getWebsite } from '@/queries/prisma'; -export interface WhiteLabel { - name: string; - url: string; - image: string; -} - async function getAccountId(website: { userId?: string; teamId?: string }): Promise { if (website.userId) { return website.userId; diff --git a/src/app/share/[...shareId]/ShareFooter.tsx b/src/app/share/[...shareId]/ShareFooter.tsx index 0f17187c..5348ac63 100644 --- a/src/app/share/[...shareId]/ShareFooter.tsx +++ b/src/app/share/[...shareId]/ShareFooter.tsx @@ -1,8 +1,8 @@ import { Row, Text } from '@umami/react-zen'; -import type { WhiteLabel } from '@/app/api/share/[shareId]/route'; import { CURRENT_VERSION, HOMEPAGE_URL } from '@/lib/constants'; +import type { WhiteLabel } from '@/lib/types'; -export function Footer({ whiteLabel }: { whiteLabel?: WhiteLabel }) { +export function ShareFooter({ whiteLabel }: { whiteLabel?: WhiteLabel }) { if (whiteLabel) { return ( diff --git a/src/app/share/[...shareId]/ShareHeader.tsx b/src/app/share/[...shareId]/ShareHeader.tsx index 78e022af..abd8511d 100644 --- a/src/app/share/[...shareId]/ShareHeader.tsx +++ b/src/app/share/[...shareId]/ShareHeader.tsx @@ -1,10 +1,10 @@ import { Icon, Row, Text, ThemeButton } from '@umami/react-zen'; -import type { WhiteLabel } from '@/app/api/share/[shareId]/route'; import { LanguageButton } from '@/components/input/LanguageButton'; import { PreferencesButton } from '@/components/input/PreferencesButton'; import { Logo } from '@/components/svg'; +import type { WhiteLabel } from '@/lib/types'; -export function Header({ whiteLabel }: { whiteLabel?: WhiteLabel }) { +export function ShareHeader({ whiteLabel }: { whiteLabel?: WhiteLabel }) { const logoUrl = whiteLabel?.url || 'https://umami.is'; const logoName = whiteLabel?.name || 'umami'; const logoImage = whiteLabel?.image; diff --git a/src/app/share/[...shareId]/SharePage.tsx b/src/app/share/[...shareId]/SharePage.tsx index 86a0dbda..8493a416 100644 --- a/src/app/share/[...shareId]/SharePage.tsx +++ b/src/app/share/[...shareId]/SharePage.tsx @@ -115,7 +115,7 @@ export function SharePage({ shareId, path = '' }: { shareId: string; path?: stri const PageComponent = PAGE_COMPONENTS[pageKey] || WebsitePage; return ( - +