Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Mike Cao 2024-03-04 16:09:05 -08:00
commit bbd7c4b6ea
10 changed files with 129 additions and 28 deletions

View File

@ -4,6 +4,7 @@ import { useMessages, useApi, useNavigation, useTeamUrl } from 'components/hooks
import { ReportContext } from './Report'; import { ReportContext } from './Report';
import styles from './ReportHeader.module.css'; import styles from './ReportHeader.module.css';
import { REPORT_TYPES } from 'lib/constants'; import { REPORT_TYPES } from 'lib/constants';
import Breadcrumb from 'components/common/Breadcrumb';
export function ReportHeader({ icon }) { export function ReportHeader({ icon }) {
const { report, updateReport } = useContext(ReportContext); const { report, updateReport } = useContext(ReportContext);
@ -57,9 +58,16 @@ export function ReportHeader({ icon }) {
<div className={styles.header}> <div className={styles.header}>
<div> <div>
<div className={styles.type}> <div className={styles.type}>
{formatMessage( <Breadcrumb
labels[Object.keys(REPORT_TYPES).find(key => REPORT_TYPES[key] === report?.type)], data={[
)} { label: formatMessage(labels.reports), url: '/reports' },
{
label: formatMessage(
labels[Object.keys(REPORT_TYPES).find(key => REPORT_TYPES[key] === report?.type)],
),
},
]}
/>
</div> </div>
<div className={styles.title}> <div className={styles.title}>
<Icon size="lg">{icon}</Icon> <Icon size="lg">{icon}</Icon>

View File

@ -6,6 +6,7 @@ import PageHeader from 'components/layout/PageHeader';
import { useMessages } from 'components/hooks'; import { useMessages } from 'components/hooks';
import UserWebsites from './UserWebsites'; import UserWebsites from './UserWebsites';
import { UserContext } from './UserProvider'; import { UserContext } from './UserProvider';
import Breadcrumb from 'components/common/Breadcrumb';
export function UserSettings({ userId }: { userId: string }) { export function UserSettings({ userId }: { userId: string }) {
const { formatMessage, labels, messages } = useMessages(); const { formatMessage, labels, messages } = useMessages();
@ -17,9 +18,23 @@ export function UserSettings({ userId }: { userId: string }) {
showToast({ message: formatMessage(messages.saved), variant: 'success' }); showToast({ message: formatMessage(messages.saved), variant: 'success' });
}; };
const breadcrumb = (
<Breadcrumb
data={[
{
label: formatMessage(labels.users),
url: '/settings/users',
},
{
label: user.username,
},
]}
/>
);
return ( return (
<> <>
<PageHeader title={user?.username} icon={<Icons.User />} /> <PageHeader title={user?.username} icon={<Icons.User />} breadcrumb={breadcrumb} />
<Tabs selectedKey={tab} onSelect={setTab} style={{ marginBottom: 30, fontSize: 14 }}> <Tabs selectedKey={tab} onSelect={setTab} style={{ marginBottom: 30, fontSize: 14 }}>
<Item key="details">{formatMessage(labels.details)}</Item> <Item key="details">{formatMessage(labels.details)}</Item>
<Item key="websites">{formatMessage(labels.websites)}</Item> <Item key="websites">{formatMessage(labels.websites)}</Item>

View File

@ -1,14 +1,15 @@
import { useState, Key, useContext } from 'react'; import { WebsiteContext } from 'app/(main)/websites/[websiteId]/WebsiteProvider';
import { Item, Tabs, Button, Text, Icon, useToasts } from 'react-basics'; import Breadcrumb from 'components/common/Breadcrumb';
import Link from 'next/link'; import { useMessages } from 'components/hooks';
import Icons from 'components/icons'; import Icons from 'components/icons';
import PageHeader from 'components/layout/PageHeader'; import PageHeader from 'components/layout/PageHeader';
import WebsiteEditForm from './WebsiteEditForm'; import Link from 'next/link';
import WebsiteData from './WebsiteData'; import { Key, useContext, useState } from 'react';
import TrackingCode from './TrackingCode'; import { Button, Icon, Item, Tabs, Text, useToasts } from 'react-basics';
import ShareUrl from './ShareUrl'; import ShareUrl from './ShareUrl';
import { useMessages } from 'components/hooks'; import TrackingCode from './TrackingCode';
import { WebsiteContext } from 'app/(main)/websites/[websiteId]/WebsiteProvider'; import WebsiteData from './WebsiteData';
import WebsiteEditForm from './WebsiteEditForm';
export function WebsiteSettings({ export function WebsiteSettings({
websiteId, websiteId,
@ -28,9 +29,23 @@ export function WebsiteSettings({
showToast({ message: formatMessage(messages.saved), variant: 'success' }); showToast({ message: formatMessage(messages.saved), variant: 'success' });
}; };
const breadcrumb = (
<Breadcrumb
data={[
{
label: formatMessage(labels.websites),
url: website.teamId ? `/teams/${website.teamId}/settings/websites` : '/settings/websites',
},
{
label: website.name,
},
]}
/>
);
return ( return (
<> <>
<PageHeader title={website?.name} icon={<Icons.Globe />}> <PageHeader title={website?.name} icon={<Icons.Globe />} breadcrumb={breadcrumb}>
<Link href={`/websites/${websiteId}`} target={openExternal ? '_blank' : null}> <Link href={`/websites/${websiteId}`} target={openExternal ? '_blank' : null}>
<Button variant="primary"> <Button variant="primary">
<Icon> <Icon>

View File

@ -1,12 +1,12 @@
import { ReactNode } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Text, Button, Icon } from 'react-basics'; import Favicon from 'components/common/Favicon';
import { useMessages, useWebsite } from 'components/hooks';
import Icons from 'components/icons';
import ActiveUsers from 'components/metrics/ActiveUsers';
import Link from 'next/link'; import Link from 'next/link';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import Favicon from 'components/common/Favicon'; import { ReactNode } from 'react';
import ActiveUsers from 'components/metrics/ActiveUsers'; import { Button, Icon, Text } from 'react-basics';
import Icons from 'components/icons';
import { useMessages, useWebsite } from 'components/hooks';
import styles from './WebsiteHeader.module.css'; import styles from './WebsiteHeader.module.css';
export function WebsiteHeader({ export function WebsiteHeader({

View File

@ -0,0 +1,10 @@
.bar {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
color: var(--base600);
}
.link span {
color: var(--base700) !important;
}

View File

@ -0,0 +1,37 @@
import Link from 'next/link';
import { Flexbox, Icon, Icons, Text } from 'react-basics';
import styles from './Breadcrumb.module.css';
export interface BreadcrumbProps {
data: {
url?: string;
label: string;
}[];
}
export function Breadcrumb({ data }: BreadcrumbProps) {
return (
<Flexbox alignItems="center" gap={3} className={styles.bar}>
{data.map((a, i) => {
return (
<>
{a.url ? (
<Link href={a.url} className={styles.link}>
<Text>{a.label}</Text>
</Link>
) : (
<Text>{a.label}</Text>
)}
{i !== data.length - 1 ? (
<Icon rotate={270}>
<Icons.ChevronDown />
</Icon>
) : null}
</>
);
})}
</Flexbox>
);
}
export default Breadcrumb;

View File

@ -27,6 +27,10 @@
flex: 1; flex: 1;
} }
.breadcrumb {
padding-top: 20px;
}
.icon { .icon {
color: var(--base700); color: var(--base700);
margin-inline-end: 1rem; margin-inline-end: 1rem;

View File

@ -7,23 +7,29 @@ export function PageHeader({
title, title,
icon, icon,
className, className,
breadcrumb,
children, children,
}: { }: {
title?: ReactNode; title?: ReactNode;
icon?: ReactNode; icon?: ReactNode;
className?: string; className?: string;
breadcrumb?: ReactNode;
children?: ReactNode; children?: ReactNode;
}) { }) {
return ( return (
<div className={classNames(styles.header, className)}> <>
{icon && ( <div className={styles.breadcrumb}>{breadcrumb}</div>
<Icon size="lg" className={styles.icon}> <div className={classNames(styles.header, className)}>
{icon} {icon && (
</Icon> <Icon size="lg" className={styles.icon}>
)} {icon}
{title && <div className={styles.title}>{title}</div>} </Icon>
<div className={styles.actions}>{children}</div> )}
</div>
{title && <div className={styles.title}>{title}</div>}
<div className={styles.actions}>{children}</div>
</div>
</>
); );
} }

View File

@ -27,6 +27,9 @@ const schema = {
password: yup.string(), password: yup.string(),
role: yup.string().matches(/admin|user|view-only/i), role: yup.string().matches(/admin|user|view-only/i),
}), }),
DELETE: yup.object().shape({
userId: yup.string().uuid().required(),
}),
}; };
export default async ( export default async (

View File

@ -28,6 +28,9 @@ const schema = {
domain: yup.string(), domain: yup.string(),
shareId: yup.string().matches(SHARE_ID_REGEX, { excludeEmptyString: true }).nullable(), shareId: yup.string().matches(SHARE_ID_REGEX, { excludeEmptyString: true }).nullable(),
}), }),
DELETE: yup.object().shape({
websiteId: yup.string().uuid().required(),
}),
}; };
export default async ( export default async (