Add display options form for website shares

Allow users to select which navigation items to display when creating
or editing a share. Options include traffic, behavior, and growth
sections with checkboxes for each nav item (excluding segments/cohorts).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mike Cao 2026-01-20 17:22:16 -08:00
parent 0eb598c817
commit ef3aec09be
4 changed files with 163 additions and 21 deletions

View file

@ -0,0 +1,83 @@
import {
Button,
Checkbox,
Column,
Form,
FormField,
FormSubmitButton,
Row,
Text,
} 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 - all enabled by default
const defaultValues: Record<string, boolean> = {};
SHARE_NAV_ITEMS.forEach(section => {
section.items.forEach(item => {
defaultValues[item.id] = true;
});
});
const handleSubmit = async (data: any) => {
setIsPending(true);
try {
const parameters: Record<string, boolean> = {};
SHARE_NAV_ITEMS.forEach(section => {
section.items.forEach(item => {
parameters[item.id] = data[item.id] ?? true;
});
});
await post(`/websites/${websiteId}/shares`, { parameters });
touch('shares');
onSave?.();
onClose?.();
} finally {
setIsPending(false);
}
};
return (
<Form onSubmit={handleSubmit} defaultValues={defaultValues}>
<Column gap="3">
{SHARE_NAV_ITEMS.map(section => (
<Column key={section.section} gap="1">
<Text size="2" weight="bold">
{formatMessage((labels as any)[section.section])}
</Text>
<Column gap="1">
{section.items.map(item => (
<FormField key={item.id} name={item.id}>
<Checkbox>
<Text size="1">{formatMessage((labels as any)[item.label])}</Text>
</Checkbox>
</FormField>
))}
</Column>
</Column>
))}
<Row justifyContent="flex-end" paddingTop="3" gap="3">
{onClose && (
<Button isDisabled={isPending} onPress={onClose}>
{formatMessage(labels.cancel)}
</Button>
)}
<FormSubmitButton isDisabled={isPending}>{formatMessage(labels.save)}</FormSubmitButton>
</Row>
</Column>
</Form>
);
}

View file

@ -1,16 +1,20 @@
import {
Button,
Checkbox,
Column,
Form,
FormField,
FormSubmitButton,
Label,
Loading,
Row,
Text,
TextField,
} 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,
@ -50,8 +54,15 @@ export function ShareEditForm({
}, [shareId, modified]);
const handleSubmit = async (data: any) => {
const parameters: Record<string, boolean> = {};
SHARE_NAV_ITEMS.forEach(section => {
section.items.forEach(item => {
parameters[item.id] = data[item.id] ?? true;
});
});
await mutateAsync(
{ slug: data.slug, parameters: share?.parameters || {} },
{ slug: share.slug, parameters },
{
onSuccess: async () => {
toast(formatMessage(messages.saved));
@ -69,24 +80,42 @@ export function ShareEditForm({
const url = getUrl(share?.slug || '');
// Build default values from share parameters
const defaultValues: Record<string, boolean> = {};
SHARE_NAV_ITEMS.forEach(section => {
section.items.forEach(item => {
defaultValues[item.id] = share?.parameters?.[item.id] ?? true;
});
});
return (
<Form
onSubmit={handleSubmit}
error={getErrorMessage(error)}
defaultValues={{ slug: share?.slug }}
>
<Column gap>
<Form onSubmit={handleSubmit} error={getErrorMessage(error)} defaultValues={defaultValues}>
<Column gap="3">
<Column>
<Label>{formatMessage(labels.shareUrl)}</Label>
<TextField value={url} isReadOnly allowCopy />
</Column>
{SHARE_NAV_ITEMS.map(section => (
<Column key={section.section} gap="1">
<Text size="2" weight="bold">
{formatMessage((labels as any)[section.section])}
</Text>
<Column gap="1">
{section.items.map(item => (
<FormField key={item.id} name={item.id}>
<Checkbox>{formatMessage((labels as any)[item.label])}</Checkbox>
</FormField>
))}
</Column>
</Column>
))}
<Row justifyContent="flex-end" paddingTop="3" gap="3">
{onClose && (
<Button isDisabled={isPending} onPress={onClose}>
{formatMessage(labels.cancel)}
</Button>
)}
<FormSubmitButton>{formatMessage(labels.save)}</FormSubmitButton>
<FormSubmitButton variant="primary">{formatMessage(labels.save)}</FormSubmitButton>
</Row>
</Column>
</Form>

View file

@ -1,6 +1,8 @@
import { Button, Column, Heading, Row, Text } from '@umami/react-zen';
import { Column, Heading, Row, Text } from '@umami/react-zen';
import { Plus } from 'lucide-react';
import { useApi, useMessages, useModified, useWebsiteSharesQuery } from '@/components/hooks';
import { useMessages, useWebsiteSharesQuery } from '@/components/hooks';
import { DialogButton } from '@/components/input/DialogButton';
import { ShareCreateForm } from './ShareCreateForm';
import { SharesTable } from './SharesTable';
export interface WebsiteShareFormProps {
@ -10,13 +12,6 @@ export interface WebsiteShareFormProps {
export function WebsiteShareForm({ websiteId }: WebsiteShareFormProps) {
const { formatMessage, labels, messages } = useMessages();
const { data, isLoading } = useWebsiteSharesQuery({ websiteId });
const { post } = useApi();
const { touch } = useModified();
const handleCreate = async () => {
await post(`/websites/${websiteId}/shares`, { parameters: {} });
touch('shares');
};
const shares = data?.data || [];
const hasShares = shares.length > 0;
@ -25,10 +20,15 @@ export function WebsiteShareForm({ websiteId }: WebsiteShareFormProps) {
<Column gap="4">
<Row justifyContent="space-between" alignItems="center">
<Heading>{formatMessage(labels.share)}</Heading>
<Button variant="primary" onPress={handleCreate}>
<Plus size={16} />
<Text>{formatMessage(labels.add)}</Text>
</Button>
<DialogButton
icon={<Plus size={16} />}
label={formatMessage(labels.add)}
title={formatMessage(labels.share)}
variant="primary"
width="400px"
>
{({ close }) => <ShareCreateForm websiteId={websiteId} onClose={close} />}
</DialogButton>
</Row>
{hasShares ? (
<>

View file

@ -0,0 +1,30 @@
export const SHARE_NAV_ITEMS = [
{
section: 'traffic',
items: [
{ id: 'overview', label: 'overview' },
{ id: 'events', label: 'events' },
{ id: 'sessions', label: 'sessions' },
{ id: 'realtime', label: 'realtime' },
{ id: 'compare', label: 'compare' },
{ id: 'breakdown', label: 'breakdown' },
],
},
{
section: 'behavior',
items: [
{ id: 'goals', label: 'goals' },
{ id: 'funnels', label: 'funnels' },
{ id: 'journeys', label: 'journeys' },
{ id: 'retention', label: 'retention' },
],
},
{
section: 'growth',
items: [
{ id: 'utm', label: 'utm' },
{ id: 'revenue', label: 'revenue' },
{ id: 'attribution', label: 'attribution' },
],
},
];