mirror of
https://github.com/umami-software/umami.git
synced 2026-02-04 04:37:11 +01:00
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:
parent
0eb598c817
commit
ef3aec09be
4 changed files with 163 additions and 21 deletions
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 ? (
|
||||
<>
|
||||
|
|
|
|||
30
src/app/(main)/websites/[websiteId]/settings/constants.ts
Normal file
30
src/app/(main)/websites/[websiteId]/settings/constants.ts
Normal 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' },
|
||||
],
|
||||
},
|
||||
];
|
||||
Loading…
Add table
Add a link
Reference in a new issue