New goals page. Upgraded prisma.

This commit is contained in:
Mike Cao 2025-05-31 02:11:18 -07:00
parent 99330a1a4d
commit 49bcbfd7f9
65 changed files with 769 additions and 1195 deletions

View file

@ -4,15 +4,14 @@ import { Logo } from '@/components/icons';
export interface EmptyPlaceholderProps {
message?: string;
icon?: ReactNode;
children?: ReactNode;
}
export function EmptyPlaceholder({ message, children }: EmptyPlaceholderProps) {
export function EmptyPlaceholder({ message, icon, children }: EmptyPlaceholderProps) {
return (
<Column alignItems="center" justifyContent="center" gap="5" height="100%" width="100%">
<Icon size="xl" fillColor="3" strokeColor="3">
<Logo />
</Icon>
<Icon size="xl">{icon || <Logo />}</Icon>
<Text>{message}</Text>
<div>{children}</div>
</Column>

View file

@ -1,15 +0,0 @@
.error {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
margin: auto;
display: flex;
background-color: var(--base50);
padding: 10px;
z-index: 1;
}
.icon {
margin-inline-end: 10px;
}

View file

@ -1,5 +1,4 @@
import { Icon, Text } from '@umami/react-zen';
import styles from './ErrorMessage.module.css';
import { Icon, Text, Row } from '@umami/react-zen';
import { useMessages } from '@/components/hooks';
import { Alert } from '@/components/icons';
@ -7,11 +6,11 @@ export function ErrorMessage() {
const { formatMessage, messages } = useMessages();
return (
<div className={styles.error}>
<Icon className={styles.icon} size="lg">
<Row alignItems="center" justifyContent="center" gap>
<Icon>
<Alert />
</Icon>
<Text>{formatMessage(messages.error)}</Text>
</div>
</Row>
);
}

View file

@ -1,14 +1,4 @@
import {
Grid,
Row,
Column,
TextField,
Label,
ListItem,
Select,
Icon,
Button,
} from '@umami/react-zen';
import { Grid, Column, TextField, Label, ListItem, Select, Icon, Button } from '@umami/react-zen';
import { useFilters } from '@/components/hooks';
import { Close } from '@/components/icons';
@ -32,10 +22,10 @@ export function FilterRecord({
const { fields, operators } = useFilters();
return (
<Grid columns="1fr auto">
<Column>
<Label>{fields.find(f => f.name === name)?.label}</Label>
<Row gap alignItems="center">
<>
<Label>{fields.find(f => f.name === name)?.label}</Label>
<Grid columns="1fr auto" gap>
<Grid columns="200px 1fr" gap>
<Select
items={operators.filter(({ type }) => type === 'string')}
value={operator}
@ -50,15 +40,15 @@ export function FilterRecord({
}}
</Select>
<TextField value={value} onChange={e => onChange?.(name, e.target.value)} />
</Row>
</Column>
<Column justifyContent="flex-end">
<Button variant="quiet" onPress={() => onRemove?.(name)}>
<Icon>
<Close />
</Icon>
</Button>
</Column>
</Grid>
</Grid>
<Column justifyContent="flex-end">
<Button variant="quiet" onPress={() => onRemove?.(name)}>
<Icon>
<Close />
</Icon>
</Button>
</Column>
</Grid>
</>
);
}

View file

@ -1,36 +1,36 @@
import { ReactNode } from 'react';
import classNames from 'classnames';
import { Loading } from '@umami/react-zen';
import { Spinner, Dots } from '@umami/react-zen';
import { ErrorMessage } from '@/components/common/ErrorMessage';
import { Empty } from '@/components/common/Empty';
import styles from './LoadingPanel.module.css';
export function LoadingPanel({
data,
error,
isEmpty,
isFetched,
isLoading,
loadingIcon = 'dots',
renderEmpty = () => <Empty />,
className,
children,
}: {
data?: any;
error?: Error;
isEmpty?: boolean;
isFetched?: boolean;
isLoading?: boolean;
loadingIcon?: 'dots' | 'spinner';
isEmpty?: boolean;
renderEmpty?: () => ReactNode;
className?: string;
children: ReactNode;
}) {
const isEmpty = !isLoading && isFetched && data && Array.isArray(data) && data.length === 0;
return (
<div className={classNames(styles.panel, className)}>
{isLoading && !isFetched && <Loading className={styles.loading} icon={loadingIcon} />}
{isLoading && !isFetched && (loadingIcon === 'dots' ? <Dots /> : <Spinner />)}
{error && <ErrorMessage />}
{!error && isEmpty && <Empty />}
{!error && !isEmpty && data && children}
{!error && !isLoading && isEmpty && renderEmpty()}
{!error && !isLoading && !isEmpty && children}
</div>
);
}

View file

@ -1,11 +1,12 @@
import { ReactNode } from 'react';
import { Heading, Icon, Row, Text } from '@umami/react-zen';
import { Heading, Icon, Row, Text, RowProps } from '@umami/react-zen';
export function SectionHeader({
title,
description,
icon,
children,
...props
}: {
title?: string;
description?: string;
@ -13,9 +14,9 @@ export function SectionHeader({
allowEdit?: boolean;
className?: string;
children?: ReactNode;
}) {
} & RowProps) {
return (
<Row justifyContent="space-between" alignItems="center" height="60px">
<Row {...props} justifyContent="space-between" alignItems="center" height="60px">
<Row gap="3" alignItems="center">
{icon && <Icon>{icon}</Icon>}
{title && <Heading size="3">{title}</Heading>}

View file

@ -3,8 +3,10 @@ export * from './queries/useActiveUsersQuery';
export * from './queries/useEventDataEventsQuery';
export * from './queries/useEventDataPropertiesQuery';
export * from './queries/useEventDataValuesQuery';
export * from './queries/useGoalsQuery';
export * from './queries/useLoginQuery';
export * from './queries/useRealtimeQuery';
export * from './queries/useResultQuery';
export * from './queries/useReportQuery';
export * from './queries/useReportsQuery';
export * from './queries/useRetentionQuery';

View file

@ -0,0 +1,11 @@
import { useApi, useModified } from '@/components/hooks';
export function useDeleteQuery(path: string, params?: { [key: string]: any }) {
const { del, useMutation } = useApi();
const { mutate, isPending, error } = useMutation({
mutationFn: () => del(path, params),
});
const { touch } = useModified();
return { mutate, isPending, error, touch };
}

View file

@ -0,0 +1,19 @@
import { useApi } from '../useApi';
import { usePagedQuery } from '../usePagedQuery';
export function useGoalQuery(
{ websiteId, reportId }: { websiteId: string; reportId: string },
params?: { [key: string]: string | number },
) {
const { post } = useApi();
return usePagedQuery({
queryKey: ['goal', { websiteId, reportId, ...params }],
queryFn: (data: any) => {
return post(`/reports/goals`, {
...data,
...params,
});
},
});
}

View file

@ -0,0 +1,21 @@
import { useApi } from '../useApi';
import { usePagedQuery } from '../usePagedQuery';
import { useModified } from '../useModified';
export function useGoalsQuery(
{ websiteId }: { websiteId: string },
params?: { [key: string]: string | number },
) {
const { get } = useApi();
const { modified } = useModified(`goals`);
return usePagedQuery({
queryKey: ['goals', { websiteId, modified, ...params }],
queryFn: (data: any) => {
return get(`/websites/${websiteId}/goals`, {
...data,
...params,
});
},
});
}

View file

@ -1,94 +1,16 @@
import { produce } from 'immer';
import { useCallback, useEffect, useState } from 'react';
import { useApi } from '../useApi';
import { useTimezone } from '../useTimezone';
import { useMessages } from '../useMessages';
import { parseDateRange } from '@/lib/date';
import { useModified } from '../useModified';
export function useReportQuery(
reportId: string,
defaultParameters?: { type: string; parameters: { [key: string]: any } },
) {
const [report, setReport] = useState(null);
const [isRunning, setIsRunning] = useState(false);
const { get, post } = useApi();
const { timezone } = useTimezone();
const { formatMessage, labels } = useMessages();
export function useReportQuery(reportId: string) {
const { get, useQuery } = useApi();
const { modified } = useModified(`report:${reportId}`);
const baseParameters = {
name: formatMessage(labels.untitled),
description: '',
parameters: {},
};
const loadReport = async (id: string) => {
const data: any = await get(`/reports/${id}`);
const { dateRange } = data?.parameters || {};
data.parameters = {
...defaultParameters?.parameters,
...data.parameters,
dateRange: parseDateRange(dateRange.value),
};
setReport(data);
};
const runReport = useCallback(
async (parameters: { [key: string]: any }) => {
setIsRunning(true);
const { type } = report;
const data = await post(`/reports/${type}`, { ...parameters, timezone });
setReport(
produce((state: any) => {
state.parameters = { ...defaultParameters?.parameters, ...parameters };
state.data = data;
return state;
}),
);
setIsRunning(false);
return useQuery({
queryKey: ['report', { reportId, modified }],
queryFn: (data: any) => {
return get(`/reports/${reportId}`, {
...data,
});
},
[report, timezone],
);
const updateReport = useCallback(
async (data: { [x: string]: any; parameters: any }) => {
setReport(
produce((state: any) => {
const { parameters, ...rest } = data;
if (parameters) {
state.parameters = {
...defaultParameters?.parameters,
...state.parameters,
...parameters,
};
}
for (const key in rest) {
state[key] = rest[key];
}
return state;
}),
);
},
[report],
);
useEffect(() => {
if (!reportId) {
setReport({ ...baseParameters, ...defaultParameters });
} else {
loadReport(reportId);
}
}, [reportId]);
return { report, runReport, updateReport, isRunning };
});
}

View file

@ -0,0 +1,17 @@
import { useApi } from '@/components/hooks';
import { UseQueryOptions, QueryKey } from '@tanstack/react-query';
export function useResultQuery<T>(
type: string,
params?: { [key: string]: any },
options?: Omit<UseQueryOptions<T, Error, T, QueryKey>, 'queryKey' | 'queryFn'>,
) {
const { post, useQuery } = useApi();
return useQuery<T>({
queryKey: ['reports', type, params],
queryFn: () => post(`/reports/${type}`, params),
enabled: !!type,
...options,
});
}

View file

@ -3,12 +3,14 @@ export {
ArrowRight as Arrow,
Calendar,
ChevronRight as Chevron,
Clock,
X as Close,
Copy,
Edit,
Ellipsis,
Eye,
ExternalLink,
File,
Globe,
Grid2X2,
LayoutDashboard,
@ -28,6 +30,7 @@ export {
SquarePen,
Sun,
Trash,
User,
Users,
} from 'lucide-react';
export * from '@/components/svg';

View file

@ -1,5 +1,5 @@
import { useState, Key, Fragment } from 'react';
import { Modal, Select, ListItem, ListSeparator, Dialog } from '@umami/react-zen';
import { Modal, Select, ListItem, ListSeparator, Dialog, Row } from '@umami/react-zen';
import { endOfYear } from 'date-fns';
import { DatePickerForm } from '@/components/metrics/DatePickerForm';
import { useMessages } from '@/components/hooks';
@ -100,7 +100,7 @@ export function DateFilter({
};
return (
<>
<Row width="200px">
<Select
value={value}
placeholder={formatMessage(labels.selectDate)}
@ -131,6 +131,6 @@ export function DateFilter({
</Dialog>
</Modal>
)}
</>
</Row>
);
}

View file

View file

@ -13,9 +13,8 @@ import {
Row,
Box,
} from '@umami/react-zen';
import { User, Users } from 'lucide-react';
import { useLoginQuery, useMessages, useTeamsQuery, useNavigation } from '@/components/hooks';
import { Chevron } from '@/components/icons';
import { Chevron, User, Users } from '@/components/icons';
export function TeamsButton({
className,

View file

@ -63,13 +63,13 @@ export function WebsiteDateFilter({
<Row gap="3">
{showButtons && !isAllTime && !isCustomRange && (
<Row gap="1">
<Button onPress={() => handleIncrement(-1)} variant="quiet">
<Icon size="xs" rotate={180}>
<Button onPress={() => handleIncrement(-1)} variant="outline">
<Icon rotate={180}>
<Chevron />
</Icon>
</Button>
<Button onPress={() => handleIncrement(1)} variant="quiet" isDisabled={disableForward}>
<Icon size="xs">
<Button onPress={() => handleIncrement(1)} variant="outline" isDisabled={disableForward}>
<Icon>
<Chevron />
</Icon>
</Button>
@ -86,10 +86,16 @@ export function WebsiteDateFilter({
{!isAllTime && compare && (
<Row alignItems="center" gap>
<Text weight="bold">VS</Text>
<Select value={compare} onChange={handleSelect} popoverProps={{ style: { width: 200 } }}>
<ListItem id="prev">{formatMessage(labels.previousPeriod)}</ListItem>
<ListItem id="yoy">{formatMessage(labels.previousYear)}</ListItem>
</Select>
<Row width="200px">
<Select
value={compare}
onChange={handleSelect}
popoverProps={{ style: { width: 200 } }}
>
<ListItem id="prev">{formatMessage(labels.previousPeriod)}</ListItem>
<ListItem id="yoy">{formatMessage(labels.previousYear)}</ListItem>
</Select>
</Row>
</Row>
)}
{!isAllTime && allowCompare && (

View file

@ -107,6 +107,7 @@ export const labels = defineMessages({
sum: { id: 'label.sum', defaultMessage: 'Sum' },
event: { id: 'label.event', defaultMessage: 'Event' },
events: { id: 'label.events', defaultMessage: 'Events' },
eventName: { id: 'label.event-name', defaultMessage: 'Event name' },
query: { id: 'label.query', defaultMessage: 'Query' },
queryParameters: { id: 'label.query-parameters', defaultMessage: 'Query parameters' },
back: { id: 'label.back', defaultMessage: 'Back' },
@ -268,7 +269,8 @@ export const labels = defineMessages({
id: 'label.utm-description',
defaultMessage: 'Track your campaigns through UTM parameters.',
},
conversionStep: { id: 'label.conversion-step', defaultMessage: 'Conversion Step' },
conversionStep: { id: 'label.conversion-step', defaultMessage: 'Conversion step' },
conversionRate: { id: 'label.conversion-ratep', defaultMessage: 'Conversion rate' },
steps: { id: 'label.steps', defaultMessage: 'Steps' },
startStep: { id: 'label.start-step', defaultMessage: 'Start Step' },
endStep: { id: 'label.end-step', defaultMessage: 'End Step' },

View file

@ -1,7 +1,14 @@
import * as React from 'react';
import type { SVGProps } from 'react';
const SvgFunnel = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" width={512} height={512} viewBox="0 0 32 32" {...props}>
<svg
xmlns="http://www.w3.org/2000/svg"
width={512}
height={512}
fill="currentColor"
viewBox="0 0 32 32"
{...props}
>
<path d="M29 11H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h26a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1M4 9h24V5H4z" />
<path d="M25 17H7a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h18a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1M8 15h16v-4H8z" />
<path d="M22 23H10a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h12a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1m-11-2h10v-4H11z" />

View file

@ -1,7 +1,13 @@
import * as React from 'react';
import type { SVGProps } from 'react';
const SvgLightbulb = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" xmlSpace="preserve" viewBox="0 0 512 512" {...props}>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlSpace="preserve"
fill="currentColor"
viewBox="0 0 512 512"
{...props}
>
<path d="M223.718 124.76c-48.027 11.198-86.688 49.285-98.494 97.031-11.843 47.899 1.711 96.722 36.259 130.601C173.703 364.377 181 383.586 181 403.777V407c0 13.296 5.801 25.26 15 33.505V467c0 24.813 20.187 45 45 45h30c24.813 0 45-20.187 45-45v-26.495c9.199-8.245 15-20.208 15-33.505v-3.282c0-19.884 7.687-39.458 20.563-52.361C376.994 325.87 391 292.005 391 256c0-86.079-79.769-151.638-167.282-131.24M286 467c0 8.271-6.729 15-15 15h-30c-8.271 0-15-6.729-15-15v-15h60zm44.326-136.834C311.689 348.843 301 375.651 301 403.718V407c0 8.271-6.729 15-15 15h-60c-8.271 0-15-6.729-15-15v-3.223c0-28.499-10.393-55.035-28.513-72.804-26.89-26.37-37.409-64.493-28.141-101.981 9.125-36.907 39.029-66.353 76.184-75.015C299.202 137.964 361 189.228 361 256c0 28.004-10.894 54.343-30.674 74.166M139.327 118.114 96.9 75.688c-5.857-5.858-15.355-5.858-21.213 0s-5.858 15.355 0 21.213l42.427 42.426c5.857 5.858 15.356 5.858 21.213 0s5.858-15.355 0-21.213M76 241H15c-8.284 0-15 6.716-15 15s6.716 15 15 15h61c8.284 0 15-6.716 15-15s-6.716-15-15-15m421 0h-61c-8.284 0-15 6.716-15 15s6.716 15 15 15h61c8.284 0 15-6.716 15-15s-6.716-15-15-15M436.313 75.688c-5.856-5.858-15.354-5.858-21.213 0l-42.427 42.426c-5.858 5.857-5.858 15.355 0 21.213s15.355 5.858 21.213 0l42.427-42.426c5.858-5.857 5.858-15.355 0-21.213M256 0c-8.284 0-15 6.716-15 15v61c0 8.284 6.716 15 15 15s15-6.716 15-15V15c0-8.284-6.716-15-15-15" />
<path d="M256 181c-6.166 0-12.447.739-18.658 2.194-25.865 6.037-47.518 27.328-53.879 52.979-1.994 8.041 2.907 16.175 10.947 18.17 8.042 1.994 16.176-2.909 18.17-10.948 3.661-14.758 16.647-27.5 31.593-30.989 3.982-.933 7.962-1.406 11.827-1.406 8.284 0 15-6.716 15-15s-6.716-15-15-15" />
</svg>

View file

@ -5,19 +5,13 @@ const SvgLogo = (props: SVGProps<SVGSVGElement>) => (
xmlns="http://www.w3.org/2000/svg"
width={20}
height={20}
fill="currentColor"
stroke="currentColor"
viewBox="0 0 428 389.11"
{...props}
>
<circle
cx={214.15}
cy={181}
r={171}
fill="none"
stroke="currentColor"
strokeMiterlimit={10}
strokeWidth={20}
/>
<path d="M413 134.11H15.29a15 15 0 0 0-15 15v15.3C.12 168 0 171.52 0 175.11c0 118.19 95.81 214 214 214 116.4 0 211.1-92.94 213.93-208.67 0-.44.07-.88.07-1.33v-30a15 15 0 0 0-15-15" />
<circle cx={214.15} cy={181} r={171} fill="none" strokeMiterlimit={10} strokeWidth={20} />
<path d="M413 134.11H15.29a15 15 0 0 0-15 15v15.3C.12 168 0 171.52 0 175.11c0 118.19 95.81 214 214 214 116.4 0 211.1-92.94 213.93-208.67 0-.44.07-.88.07-1.33v-30a15 15 0 0 0-15-15Z" />
</svg>
);
export default SvgLogo;

View file

@ -5,6 +5,7 @@ const SvgMagnet = (props: SVGProps<SVGSVGElement>) => (
xmlns="http://www.w3.org/2000/svg"
width={512}
height={512}
fill="currentColor"
viewBox="0 0 508.467 508.467"
{...props}
>

View file

@ -1,7 +1,13 @@
import * as React from 'react';
import type { SVGProps } from 'react';
const SvgMoney = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" xmlSpace="preserve" viewBox="0 0 512 512" {...props}>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlSpace="preserve"
fill="currentColor"
viewBox="0 0 512 512"
{...props}
>
<path d="M347 302c8.271 0 15 6.639 15 14.8h30c0-19.468-12.541-36.067-30-42.231V242h-30v32.58c-17.459 6.192-30 22.865-30 42.42 0 24.813 20.187 45 45 45 8.271 0 15 6.729 15 15s-6.729 15-15 15-15-6.729-15-15h-30c0 19.555 12.541 36.228 30 42.42v32.38h30v-32.38c17.459-6.192 30-22.865 30-42.42 0-24.813-20.187-45-45-45-8.271 0-15-6.729-15-15s6.729-15 15-15" />
<path d="M347 182c-5.057 0-10.058.242-15 .689V90c0-26.011-18.548-49.61-52.226-66.449C249.4 8.364 209.35 0 167 0 124.564 0 84.193 8.347 53.323 23.502 18.938 40.385 0 64 0 90v272c0 26 18.938 49.616 53.323 66.498C84.193 443.653 124.564 452 167 452c17.009 0 33.647-1.358 49.615-4.004C246.826 486.909 294.035 512 347 512c90.981 0 165-74.019 165-165s-74.019-165-165-165M66.545 50.432C92.992 37.447 129.606 30 167 30c79.558 0 135 31.621 135 60s-55.442 60-135 60c-37.394 0-74.008-7.447-100.455-20.432C43.32 118.166 30 103.744 30 90s13.32-28.166 36.545-39.568M30 142.265c6.724 5.137 14.512 9.907 23.323 14.233C84.193 171.653 124.564 180 167 180c42.35 0 82.4-8.364 112.774-23.551 8.359-4.18 15.783-8.776 22.226-13.722v45.51c-29.896 8.485-56.359 25.209-76.778 47.548C206.946 239.908 187.386 242 167 242c-37.394 0-74.008-7.447-100.455-20.432C43.32 210.166 30 195.744 30 182zm0 92c6.724 5.137 14.512 9.907 23.323 14.233C84.193 263.653 124.564 272 167 272c11.581 0 22.942-.621 34.021-1.839a163.7 163.7 0 0 0-18.293 61.395c-5.211.286-10.465.444-15.728.444-37.394 0-74.008-7.447-100.455-20.432C43.32 300.166 30 285.744 30 272zM167 422c-37.394 0-74.008-7.447-100.455-20.432C43.32 390.166 30 375.744 30 362v-37.736c6.724 5.137 14.512 9.907 23.323 14.233C84.193 353.653 124.564 362 167 362c5.23 0 10.459-.132 15.654-.388a163.7 163.7 0 0 0 16.486 58.557A281 281 0 0 1 167 422m180 60c-74.439 0-135-60.561-135-135s60.561-135 135-135 135 60.561 135 135-60.561 135-135 135" />
</svg>

View file

@ -1,7 +1,14 @@
import * as React from 'react';
import type { SVGProps } from 'react';
const SvgNetwork = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" width={512} height={512} viewBox="0 0 32 32" {...props}>
<svg
xmlns="http://www.w3.org/2000/svg"
width={512}
height={512}
fill="currentColor"
viewBox="0 0 32 32"
{...props}
>
<path d="M28 19c-.809 0-1.54.325-2.08.847l-6.011-3.01c.058-.271.091-.55.091-.837s-.033-.566-.091-.837l6.011-3.01c.54.522 1.271.847 2.08.847 1.654 0 3-1.346 3-3s-1.346-3-3-3-3 1.346-3 3c0 .123.022.24.036.359L19 13.382a3.98 3.98 0 0 0-2-1.24V6.816A3 3 0 0 0 19 4c0-1.654-1.346-3-3-3s-3 1.346-3 3c0 1.302.838 2.401 2 2.815v5.327a4 4 0 0 0-2 1.24L6.963 10.36c.015-.12.037-.237.037-.36 0-1.654-1.346-3-3-3s-3 1.346-3 3 1.346 3 3 3c.809 0 1.54-.325 2.08-.847l6.011 3.01q-.089.407-.091.837c-.002.43.033.566.091.837l-6.011 3.01A2.98 2.98 0 0 0 4 19c-1.654 0-3 1.346-3 3s1.346 3 3 3 3-1.346 3-3c0-.123-.022-.24-.036-.359L13 18.618a3.98 3.98 0 0 0 2 1.24v5.326A3 3 0 0 0 13 28c0 1.654 1.346 3 3 3s3-1.346 3-3a3 3 0 0 0-2-2.816v-5.326a4 4 0 0 0 2-1.24l6.037 3.022c-.015.12-.037.237-.037.36 0 1.654 1.346 3 3 3s3-1.346 3-3-1.346-3-3-3m0-10c.551 0 1 .449 1 1s-.449 1-1 1-1-.449-1-1 .449-1 1-1M4 11c-.551 0-1-.449-1-1s.449-1 1-1 1 .449 1 1-.449 1-1 1m0 12c-.551 0-1-.449-1-1s.449-1 1-1 1 .449 1 1-.449 1-1 1M16 3c.551 0 1 .449 1 1s-.449 1-1 1-1-.449-1-1 .449-1 1-1m0 26c-.551 0-1-.449-1-1s.449-1 1-1 1 .449 1 1-.449 1-1 1m0-11c-1.103 0-2-.897-2-2s.897-2 2-2 2 .897 2 2-.897 2-2 2m12 5c-.551 0-1-.449-1-1s.449-1 1-1 1 .449 1 1-.449 1-1 1" />
</svg>
);

View file

@ -1,7 +1,14 @@
import * as React from 'react';
import type { SVGProps } from 'react';
const SvgPath = (props: SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" width={512} height={512} viewBox="0 0 64 64" {...props}>
<svg
xmlns="http://www.w3.org/2000/svg"
width={512}
height={512}
fill="currentColor"
viewBox="0 0 64 64"
{...props}
>
<path d="m56.4 47.6-6-6c-.8-.8-2-.8-2.8 0s-.8 2 0 2.8l2.6 2.6H18.5c-3.6 0-6.5-2.9-6.5-6.5s2.9-6.5 6.5-6.5h27C51.3 34 56 29.3 56 23.5S51.3 13 45.5 13H22.7c-.9-3.4-4-6-7.7-6-4.4 0-8 3.6-8 8s3.6 8 8 8c3.7 0 6.8-2.6 7.7-6h22.8c3.6 0 6.5 2.9 6.5 6.5S49.1 30 45.5 30h-27C12.7 30 8 34.7 8 40.5S12.7 51 18.5 51h31.7l-2.6 2.6c-.8.8-.8 2 0 2.8.4.4.9.6 1.4.6s1-.2 1.4-.6l6-6c.8-.8.8-2 0-2.8M15 19c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4" />
</svg>
);

View file

@ -5,6 +5,7 @@ const SvgTag = (props: SVGProps<SVGSVGElement>) => (
xmlns="http://www.w3.org/2000/svg"
width="437pt"
height="437pt"
fill="currentColor"
viewBox="0 0 437.004 437"
{...props}
>

View file

@ -5,6 +5,7 @@ const SvgTarget = (props: SVGProps<SVGSVGElement>) => (
xmlns="http://www.w3.org/2000/svg"
width={512}
height={512}
fill="currentColor"
fillRule="evenodd"
strokeLinejoin="round"
strokeMiterlimit={2}

View file

@ -1,14 +0,0 @@
import * as React from 'react';
import type { SVGProps } from 'react';
const SvgUser = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="512pt"
height="512pt"
viewBox="-56 0 512 512"
{...props}
>
<path d="M267 236.375c36.254-22.582 60.434-62.797 60.434-108.563C327.434 57.337 270.098 0 199.62 0 129.145 0 71.81 57.336 71.81 127.813c0 45.765 24.18 85.976 60.43 108.558C55.222 264.071 0 337.84 0 424.273v72.243C0 505.066 6.934 512 15.484 512H383.75c8.55 0 15.48-6.934 15.48-15.484v-72.243c0-86.43-55.218-160.195-132.23-187.898m101.266 244.656H30.969v-56.758c0-92.992 75.652-168.644 168.648-168.644 92.992 0 168.649 75.652 168.649 168.644zm-71.801-353.219c0 53.403-43.442 96.848-96.844 96.848s-96.844-43.445-96.844-96.847c0-53.399 43.442-96.844 96.844-96.844s96.844 43.445 96.844 96.844zm0 0" />
</svg>
);
export default SvgUser;

View file

@ -1,13 +0,0 @@
import * as React from 'react';
import type { SVGProps } from 'react';
const SvgUsers = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
xmlSpace="preserve"
viewBox="0 0 477.869 477.869"
{...props}
>
<path d="M387.415 233.496c48.976-44.029 52.987-119.424 8.958-168.4C355.991 20.177 288.4 12.546 239.02 47.332c-53.83-37.99-128.264-25.149-166.254 28.68-34.859 49.393-27.259 117.054 17.689 157.483C34.606 262.935-.251 320.976.002 384.108v51.2c0 9.426 7.641 17.067 17.067 17.067h443.733c9.426 0 17.067-7.641 17.067-17.067v-51.2c.252-63.132-34.605-121.173-90.454-150.612M307.201 59.842c47.062-.052 85.256 38.057 85.309 85.119.037 33.564-19.631 64.023-50.237 77.799-1.314.597-2.628 1.143-3.959 1.707a83.7 83.7 0 0 1-12.988 4.045c-.853.188-1.707.29-2.577.461a85.4 85.4 0 0 1-15.019 1.519c-2.27 0-4.557-.171-6.827-.375-.853 0-1.707 0-2.56-.171a86.2 86.2 0 0 1-27.904-8.226c-.324-.154-.7-.137-1.024-.273-1.707-.819-3.413-1.536-4.932-2.458.137-.171.222-.358.358-.529a119.7 119.7 0 0 0 18.278-33.297l.529-1.434a120.4 120.4 0 0 0 4.523-17.562c.154-.87.273-1.707.41-2.645.987-6.067 1.506-12.2 1.553-18.347a120 120 0 0 0-1.553-18.313c-.137-.887-.256-1.707-.41-2.645a120.4 120.4 0 0 0-4.523-17.562l-.529-1.434a119.8 119.8 0 0 0-18.278-33.297c-.137-.171-.222-.358-.358-.529a84.8 84.8 0 0 1 42.718-11.553M85.335 145.176c-.121-47.006 37.886-85.21 84.892-85.331a85.1 85.1 0 0 1 59.134 23.686c.99.956 1.963 1.911 2.918 2.901a88 88 0 0 1 8.09 9.813c.751 1.058 1.434 2.185 2.133 3.277a84 84 0 0 1 6.263 11.52c.427.973.751 1.963 1.126 2.935a83.4 83.4 0 0 1 4.233 13.653c.12.512.154 1.024.256 1.553a80.3 80.3 0 0 1 0 32.119c-.102.529-.137 1.041-.256 1.553a83 83 0 0 1-4.233 13.653c-.375.973-.7 1.963-1.126 2.935a84 84 0 0 1-6.263 11.503c-.7 1.092-1.382 2.219-2.133 3.277a87.6 87.6 0 0 1-8.09 9.813c-.956.99-1.929 1.946-2.918 2.901a85.2 85.2 0 0 1-23.569 15.906 49 49 0 0 1-4.198 1.707 86 86 0 0 1-12.663 3.925c-1.075.239-2.185.375-3.277.563a84.7 84.7 0 0 1-14.046 1.417h-1.877a84.6 84.6 0 0 1-14.046-1.417c-1.092-.188-2.202-.324-3.277-.563a86 86 0 0 1-12.663-3.925c-1.417-.563-2.816-1.143-4.198-1.707-30.534-13.786-50.173-44.166-50.212-77.667m221.866 273.066H34.135v-34.133c-.25-57.833 36.188-109.468 90.76-128.614a119.1 119.1 0 0 0 91.546 0 137 137 0 0 1 16.623 7.356c3.55 1.826 6.827 3.908 10.24 6.007 2.219 1.382 4.471 2.731 6.605 4.25 3.294 2.338 6.4 4.881 9.455 7.492 1.963 1.707 3.908 3.413 5.751 5.12 2.816 2.662 5.461 5.478 8.004 8.363a135 135 0 0 1 5.291 6.383 133 133 0 0 1 6.349 8.823c1.707 2.56 3.226 5.222 4.727 7.885 1.707 2.935 3.277 5.871 4.71 8.926 1.434 3.055 2.697 6.4 3.925 9.66 1.075 2.833 2.219 5.649 3.106 8.533 1.195 3.959 2.031 8.055 2.867 12.151.512 2.423 1.178 4.796 1.553 7.253a141 141 0 0 1 1.553 20.412v34.133zm136.534 0h-102.4v-34.133c0-5.342-.307-10.633-.785-15.872-.137-1.536-.375-3.055-.546-4.591-.461-3.772-.99-7.509-1.707-11.213a247 247 0 0 0-.973-4.762q-1.228-5.7-2.85-11.298c-.358-1.229-.683-2.475-1.058-3.686a169.1 169.1 0 0 0-20.565-43.127l-.666-.973a169 169 0 0 0-9.404-12.646l-.119-.154a155 155 0 0 0-11.008-12.237h.7a121 121 0 0 0 14.524 1.024h.939c4.496-.039 8.985-.33 13.449-.87 1.399-.171 2.782-.427 4.181-.649a117 117 0 0 0 10.752-2.167c1.007-.256 2.031-.495 3.055-.785a116 116 0 0 0 13.653-4.642c54.612 19.127 91.083 70.785 90.829 128.649v34.132z" />
</svg>
);
export default SvgUsers;

View file

@ -4,7 +4,6 @@ export { default as Bars } from './Bars';
export { default as Bolt } from './Bolt';
export { default as Bookmark } from './Bookmark';
export { default as Change } from './Change';
export { default as Clock } from './Clock';
export { default as Compare } from './Compare';
export { default as Dashboard } from './Dashboard';
export { default as Expand } from './Expand';
@ -31,6 +30,5 @@ export { default as Security } from './Security';
export { default as Speaker } from './Speaker';
export { default as Tag } from './Tag';
export { default as Target } from './Target';
export { default as User } from './User';
export { default as Visitor } from './Visitor';
export { default as Website } from './Website';