mirror of
https://github.com/umami-software/umami.git
synced 2026-02-04 04:37:11 +01:00
Zen components conversion.
This commit is contained in:
parent
aac1a12e51
commit
5999bf6256
142 changed files with 1235 additions and 1454 deletions
|
|
@ -200,24 +200,6 @@ const config = {
|
|||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
experimental: {
|
||||
turbo: {
|
||||
rules: {
|
||||
'*.svg': {
|
||||
loaders: ['@svgr/webpack'],
|
||||
as: '*.js',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
webpack(config) {
|
||||
config.module.rules.push({
|
||||
test: /\.svg$/,
|
||||
issuer: /\.(js|ts)x?$/,
|
||||
use: ['@svgr/webpack'],
|
||||
});
|
||||
return config;
|
||||
},
|
||||
async headers() {
|
||||
return headers;
|
||||
},
|
||||
|
|
|
|||
14
package.json
14
package.json
|
|
@ -10,7 +10,7 @@
|
|||
"url": "https://github.com/umami-software/umami.git"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3000 --turbo",
|
||||
"dev": "next dev -p 3000",
|
||||
"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",
|
||||
|
|
@ -71,12 +71,13 @@
|
|||
"@dicebear/core": "^9.2.2",
|
||||
"@fontsource/inter": "^4.5.15",
|
||||
"@hello-pangea/dnd": "^17.0.0",
|
||||
"@prisma/client": "6.1.0",
|
||||
"@internationalized/date": "^3.7.0",
|
||||
"@prisma/client": "6.4.1",
|
||||
"@prisma/extension-read-replicas": "^0.4.0",
|
||||
"@react-spring/web": "^9.7.5",
|
||||
"@tanstack/react-query": "^5.66.11",
|
||||
"@umami/prisma-client": "^0.14.0",
|
||||
"@umami/react-zen": "^0.62.0",
|
||||
"@umami/react-zen": "link:C:/Users/mike/AppData/Local/pnpm/global/5/node_modules/@umami/react-zen",
|
||||
"@umami/redis-client": "^0.26.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"chalk": "^4.1.2",
|
||||
|
|
@ -105,10 +106,10 @@
|
|||
"lucide-react": "^0.475.0",
|
||||
"maxmind": "^4.3.24",
|
||||
"md5": "^2.3.0",
|
||||
"next": "15.2.0",
|
||||
"next": "15.2.1",
|
||||
"node-fetch": "^3.3.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prisma": "6.1.0",
|
||||
"prisma": "6.4.1",
|
||||
"pure-rand": "^6.1.0",
|
||||
"react": "^19.0.0",
|
||||
"react-aria-components": "^1.6.0",
|
||||
|
|
@ -119,7 +120,6 @@
|
|||
"react-simple-maps": "^2.3.0",
|
||||
"react-use-measure": "^2.1.7",
|
||||
"react-window": "^1.8.11",
|
||||
"react-zen": "link:C:/Users/mike/AppData/Local/pnpm/global/5/node_modules/@umami/react-zen",
|
||||
"request-ip": "^3.3.0",
|
||||
"semver": "^7.7.1",
|
||||
"serialize-error": "^12.0.0",
|
||||
|
|
@ -196,7 +196,7 @@
|
|||
"sharp"
|
||||
],
|
||||
"overrides": {
|
||||
"react-zen": "link:C:/Users/mike/AppData/Local/pnpm/global/5/node_modules/@umami/react-zen"
|
||||
"@umami/react-zen": "link:C:/Users/mike/AppData/Local/pnpm/global/5/node_modules/@umami/react-zen"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
569
pnpm-lock.yaml
generated
569
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -6,7 +6,7 @@ import { UpdateNotice } from './UpdateNotice';
|
|||
import { NavBar } from '@/app/(main)/NavBar';
|
||||
import { Page } from '@/components/layout/Page';
|
||||
import { useLogin, useConfig } from '@/components/hooks';
|
||||
import { SideNav } from '@/app/(main)/SideNav';
|
||||
import { Nav } from '@/app/(main)/Nav';
|
||||
|
||||
export function App({ children }) {
|
||||
const { user, isLoading, error } = useLogin();
|
||||
|
|
@ -30,8 +30,8 @@ export function App({ children }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<Grid>
|
||||
<SideNav />
|
||||
<Grid height="100vh" width="100%" columns="auto 1fr">
|
||||
<Nav />
|
||||
<Grid rows="auto 1fr">
|
||||
<NavBar />
|
||||
<Page>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,21 @@
|
|||
import { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { SideNav as Nav, SideNavHeader, SideNavSection, SideNavItem } from '@umami/react-zen';
|
||||
import {
|
||||
SideNav,
|
||||
SideNavHeader,
|
||||
SideNavSection,
|
||||
SideNavItem,
|
||||
Button,
|
||||
Icon,
|
||||
Row,
|
||||
} from '@umami/react-zen';
|
||||
import { Lucide, Icons } from '@/components/icons';
|
||||
import { useMessages, useTeamUrl } from '@/components/hooks';
|
||||
|
||||
export function SideNav() {
|
||||
export function Nav() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { renderTeamUrl } = useTeamUrl();
|
||||
const [isCollapsed, setCollapsed] = useState(false);
|
||||
|
||||
const links = [
|
||||
{
|
||||
|
|
@ -36,7 +46,7 @@ export function SideNav() {
|
|||
].filter(n => n);
|
||||
|
||||
return (
|
||||
<Nav>
|
||||
<SideNav isCollapsed={isCollapsed}>
|
||||
<SideNavSection>
|
||||
<SideNavHeader name="umami" icon={<Icons.Logo />} />
|
||||
</SideNavSection>
|
||||
|
|
@ -49,6 +59,15 @@ export function SideNav() {
|
|||
);
|
||||
})}
|
||||
</SideNavSection>
|
||||
</Nav>
|
||||
<SideNavSection>
|
||||
<Row justifyContent="flex-start">
|
||||
<Button onPress={() => setCollapsed(!isCollapsed)} variant="quiet">
|
||||
<Icon>
|
||||
<Lucide.PanelLeft />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Row>
|
||||
</SideNavSection>
|
||||
</SideNav>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
.navbar {
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr 1fr;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
height: 60px;
|
||||
background: var(--base75);
|
||||
border-bottom: 1px solid var(--base300);
|
||||
padding: 0 20px;
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
.links {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 30px;
|
||||
padding: 0 40px;
|
||||
font-weight: 700;
|
||||
max-height: 60px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.links a,
|
||||
.links a:active,
|
||||
.links a:visited {
|
||||
color: var(--font-color200);
|
||||
line-height: 60px;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
.links a:hover {
|
||||
color: var(--font-color100);
|
||||
border-bottom: 2px solid var(--primary400);
|
||||
}
|
||||
|
||||
.links a.selected {
|
||||
color: var(--font-color100);
|
||||
border-bottom: 2px solid var(--primary400);
|
||||
}
|
||||
|
||||
.mobile {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.navbar {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.links {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
.sidenav {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 200px;
|
||||
height: 100vh;
|
||||
background-color: var(--layer-color-1);
|
||||
border-right: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--gap);
|
||||
padding: var(--padding);
|
||||
}
|
||||
|
||||
.name {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: var(--spacing-3);
|
||||
gap: var(--spacing-9);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.items {
|
||||
display: grid;
|
||||
gap: var(--gap);
|
||||
}
|
||||
|
||||
.item {
|
||||
color: var(--font-color-muted) !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--gap);
|
||||
padding: var(--padding);
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
color: var(--font-color) !important;
|
||||
background-color: var(--layer-color-2);
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Button } from 'react-basics';
|
||||
import { Button } from '@umami/react-zen';
|
||||
import Link from 'next/link';
|
||||
import Script from 'next/script';
|
||||
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useState, useMemo, useEffect } from 'react';
|
||||
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd';
|
||||
import classNames from 'classnames';
|
||||
import { Button, Loading, Toggle, SearchField } from 'react-basics';
|
||||
import { Button, Loading, Toggle, SearchField } from '@umami/react-zen';
|
||||
import { firstBy } from 'thenby';
|
||||
import { useDashboard, saveDashboard } from '@/store/dashboard';
|
||||
import { useMessages, useWebsites } from '@/components/hooks';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
'use client';
|
||||
import { Icon, Icons, Loading, Text } from 'react-basics';
|
||||
import { Icon, Icons, Loading, Text } from '@umami/react-zen';
|
||||
import { PageHeader } from '@/components/layout/PageHeader';
|
||||
import { Pager } from '@/components/common/Pager';
|
||||
import { WebsiteChartList } from '../websites/[websiteId]/WebsiteChartList';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { TooltipPopup, Icon, Text, Flexbox, Button } from 'react-basics';
|
||||
import { Row, TooltipTrigger, Tooltip, Icon, Text, Button } from '@umami/react-zen';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { saveDashboard } from '@/store/dashboard';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
|
@ -15,20 +15,21 @@ export function DashboardSettingsButton() {
|
|||
};
|
||||
|
||||
return (
|
||||
<Flexbox gap={10}>
|
||||
<TooltipPopup label={formatMessage(labels.toggleCharts)} position="bottom">
|
||||
<Button onClick={handleToggleCharts}>
|
||||
<Row gap="3">
|
||||
<TooltipTrigger>
|
||||
<Button onPress={handleToggleCharts}>
|
||||
<Icon>
|
||||
<Icons.BarChart />
|
||||
</Icon>
|
||||
</Button>
|
||||
</TooltipPopup>
|
||||
<Button onClick={handleEdit}>
|
||||
<Tooltip placement="bottom">{formatMessage(labels.toggleCharts)}</Tooltip>
|
||||
</TooltipTrigger>
|
||||
<Button onPress={handleEdit}>
|
||||
<Icon>
|
||||
<Icons.Edit />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.edit)}</Text>
|
||||
</Button>
|
||||
</Flexbox>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { DateFilter } from '@/components/input/DateFilter';
|
||||
import { Button, Flexbox } from 'react-basics';
|
||||
import { Button, Flexbox } from '@umami/react-zen';
|
||||
import { useDateRange, useMessages } from '@/components/hooks';
|
||||
import { DEFAULT_DATE_RANGE } from '@/lib/constants';
|
||||
import { DateRange } from '@/lib/types';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState } from 'react';
|
||||
import { Button, Dropdown, Item, Flexbox } from 'react-basics';
|
||||
import { Button, Dropdown, Item, Flexbox } from '@umami/react-zen';
|
||||
import { useLocale, useMessages } from '@/components/hooks';
|
||||
import { DEFAULT_LOCALE } from '@/lib/constants';
|
||||
import { languages } from '@/lib/lang';
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
import { Button, Icon, Text, useToasts, ModalTrigger, Modal } from 'react-basics';
|
||||
import { Button, Icon, Text, useToast, DialogTrigger, Dialog, Modal } from '@umami/react-zen';
|
||||
import { PasswordEditForm } from '@/app/(main)/profile/PasswordEditForm';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function PasswordChangeButton() {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { showToast } = useToasts();
|
||||
const { toast } = useToast();
|
||||
|
||||
const handleSave = () => {
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
toast(formatMessage(messages.saved));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ModalTrigger>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.Lock />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.changePassword)}</Text>
|
||||
</Button>
|
||||
<Modal title={formatMessage(labels.changePassword)}>
|
||||
{close => <PasswordEditForm onSave={handleSave} onClose={close} />}
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
</>
|
||||
<DialogTrigger>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.Lock />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.changePassword)}</Text>
|
||||
</Button>
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.changePassword)}>
|
||||
{({ close }) => <PasswordEditForm onSave={handleSave} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</DialogTrigger>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useRef } from 'react';
|
||||
import { Form, FormRow, FormInput, FormButtons, PasswordField, Button } from 'react-basics';
|
||||
import { Form, FormRow, FormInput, FormButtons, PasswordField, Button } from '@umami/react-zen';
|
||||
import { useApi, useMessages } from '@/components/hooks';
|
||||
|
||||
export function PasswordEditForm({ onSave, onClose }) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Form, FormRow } from 'react-basics';
|
||||
import { Form, FormRow } from '@umami/react-zen';
|
||||
import { TimezoneSetting } from '@/app/(main)/profile/TimezoneSetting';
|
||||
import { DateRangeSetting } from '@/app/(main)/profile/DateRangeSetting';
|
||||
import { LanguageSetting } from '@/app/(main)/profile/LanguageSetting';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import classNames from 'classnames';
|
||||
import { Button, Icon } from 'react-basics';
|
||||
import { Button, Icon } from '@umami/react-zen';
|
||||
import { useTheme } from '@/components/hooks';
|
||||
import { Icons } from '@/components/icons';
|
||||
import styles from './ThemeSetting.module.css';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState } from 'react';
|
||||
import { Dropdown, Item, Button, Flexbox } from 'react-basics';
|
||||
import { Dropdown, Item, Button, Flexbox } from '@umami/react-zen';
|
||||
import { useTimezone, useMessages } from '@/components/hooks';
|
||||
import { getTimezone } from '@/lib/date';
|
||||
import styles from './TimezoneSetting.module.css';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Button, Icon, Icons, Modal, ModalTrigger, Text } from 'react-basics';
|
||||
import { Button, Icon, Icons, Modal, DialogTrigger, Dialog, Text } from '@umami/react-zen';
|
||||
import { useApi, useMessages, useModified } from '@/components/hooks';
|
||||
import { ConfirmationForm } from '@/components/common/ConfirmationForm';
|
||||
|
||||
|
|
@ -29,25 +29,29 @@ export function ReportDeleteButton({
|
|||
};
|
||||
|
||||
return (
|
||||
<ModalTrigger>
|
||||
<DialogTrigger>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.Trash />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.delete)}</Text>
|
||||
</Button>
|
||||
<Modal title={formatMessage(labels.deleteReport)}>
|
||||
{(close: () => void) => (
|
||||
<ConfirmationForm
|
||||
message={formatMessage(messages.confirmDelete, { target: <b>{reportName}</b> })}
|
||||
isLoading={isPending}
|
||||
error={error}
|
||||
onConfirm={handleConfirm.bind(null, close)}
|
||||
onClose={close}
|
||||
buttonLabel={formatMessage(labels.delete)}
|
||||
/>
|
||||
)}
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.deleteReport)}>
|
||||
{({ close }) => (
|
||||
<ConfirmationForm
|
||||
message={formatMessage(messages.confirmDelete, {
|
||||
target: <b key="report-name">{reportName}</b>,
|
||||
})}
|
||||
isLoading={isPending}
|
||||
error={error}
|
||||
onConfirm={handleConfirm.bind(null, close)}
|
||||
onClose={close}
|
||||
buttonLabel={formatMessage(labels.delete)}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
</DialogTrigger>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useReports } from '@/components/hooks';
|
||||
import { ReportsTable } from './ReportsTable';
|
||||
import { DataGrid } from '@/components/common/DataGrid';
|
||||
import { ReactNode } from 'react';
|
||||
import { useReports } from '@/components/hooks';
|
||||
import { DataGrid } from '@/components/common/DataGrid';
|
||||
import { ReportsTable } from './ReportsTable';
|
||||
|
||||
export function ReportsDataTable({
|
||||
websiteId,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { PageHeader } from '@/components/layout/PageHeader';
|
||||
import { Icon, Icons, Text } from 'react-basics';
|
||||
import { Icon, Icons, Text } from '@umami/react-zen';
|
||||
import { useLogin, useMessages, useTeamUrl } from '@/components/hooks';
|
||||
import { LinkButton } from '@/components/common/LinkButton';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GridColumn, GridTable, Icon, Icons, Text } from 'react-basics';
|
||||
import { Icon, Icons, Text, DataTable, DataColumn, Row } from '@umami/react-zen';
|
||||
import { LinkButton } from '@/components/common/LinkButton';
|
||||
import { useMessages, useLogin, useTeamUrl } from '@/components/hooks';
|
||||
import { REPORT_TYPES } from '@/lib/constants';
|
||||
|
|
@ -10,39 +10,39 @@ export function ReportsTable({ data = [], showDomain }: { data: any[]; showDomai
|
|||
const { renderTeamUrl } = useTeamUrl();
|
||||
|
||||
return (
|
||||
<GridTable data={data}>
|
||||
<GridColumn name="name" label={formatMessage(labels.name)} />
|
||||
<GridColumn name="description" label={formatMessage(labels.description)} />
|
||||
<GridColumn name="type" label={formatMessage(labels.type)}>
|
||||
{row => {
|
||||
<DataTable data={data}>
|
||||
<DataColumn id="name" label={formatMessage(labels.name)} />
|
||||
<DataColumn id="description" label={formatMessage(labels.description)} />
|
||||
<DataColumn id="type" label={formatMessage(labels.type)}>
|
||||
{(row: any) => {
|
||||
return formatMessage(
|
||||
labels[Object.keys(REPORT_TYPES).find(key => REPORT_TYPES[key] === row.type)],
|
||||
);
|
||||
}}
|
||||
</GridColumn>
|
||||
</DataColumn>
|
||||
{showDomain && (
|
||||
<GridColumn name="domain" label={formatMessage(labels.domain)}>
|
||||
{row => row?.website?.domain}
|
||||
</GridColumn>
|
||||
<DataColumn id="domain" label={formatMessage(labels.domain)}>
|
||||
{(row: any) => row?.website?.domain}
|
||||
</DataColumn>
|
||||
)}
|
||||
<GridColumn name="action" label="" alignment="end">
|
||||
{row => {
|
||||
<DataColumn id="action" label="" align="end">
|
||||
{(row: any) => {
|
||||
const { id, name, userId, website } = row;
|
||||
return (
|
||||
<>
|
||||
<Row gap="3">
|
||||
{(user.id === userId || user.id === website?.userId) && (
|
||||
<ReportDeleteButton reportId={id} reportName={name} />
|
||||
)}
|
||||
<LinkButton href={renderTeamUrl(`/reports/${id}`)}>
|
||||
<Icon>
|
||||
<Icons.ArrowRight />
|
||||
<Icons.Arrow />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.view)}</Text>
|
||||
</LinkButton>
|
||||
</>
|
||||
</Row>
|
||||
);
|
||||
}}
|
||||
</GridColumn>
|
||||
</GridTable>
|
||||
</DataColumn>
|
||||
</DataTable>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useContext } from 'react';
|
||||
import { FormRow } from 'react-basics';
|
||||
import { Column, Label } from '@umami/react-zen';
|
||||
import { parseDateRange } from '@/lib/date';
|
||||
import { DateFilter } from '@/components/input/DateFilter';
|
||||
import { WebsiteSelect } from '@/components/input/WebsiteSelect';
|
||||
|
|
@ -40,16 +40,18 @@ export function BaseParameters({
|
|||
return (
|
||||
<>
|
||||
{showWebsiteSelect && (
|
||||
<FormRow label={formatMessage(labels.website)}>
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.website)}</Label>
|
||||
{allowWebsiteSelect ? (
|
||||
<WebsiteSelect teamId={teamId} websiteId={websiteId} onSelect={handleWebsiteSelect} />
|
||||
) : (
|
||||
name
|
||||
)}
|
||||
</FormRow>
|
||||
</Column>
|
||||
)}
|
||||
{showDateSelect && (
|
||||
<FormRow label={formatMessage(labels.dateRange)} className={styles.dropdown}>
|
||||
<Column className={styles.dropdown}>
|
||||
<Label>{formatMessage(labels.dateRange)}</Label>
|
||||
{allowDateSelect && (
|
||||
<DateFilter
|
||||
value={value}
|
||||
|
|
@ -58,7 +60,7 @@ export function BaseParameters({
|
|||
onChange={handleDateChange}
|
||||
/>
|
||||
)}
|
||||
</FormRow>
|
||||
</Column>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Form, FormRow, Menu, Item } from 'react-basics';
|
||||
import { Form, FormRow, Menu, Item } from '@umami/react-zen';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export function FieldAggregateForm({
|
||||
|
|
|
|||
|
|
@ -4,19 +4,21 @@ import { OPERATORS } from '@/lib/constants';
|
|||
import { isEqualsOperator } from '@/lib/params';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Column,
|
||||
Row,
|
||||
Select,
|
||||
Flexbox,
|
||||
Form,
|
||||
FormRow,
|
||||
Icon,
|
||||
Icons,
|
||||
Item,
|
||||
Loading,
|
||||
Menu,
|
||||
MenuItem,
|
||||
ListItem,
|
||||
SearchField,
|
||||
Text,
|
||||
TextField,
|
||||
} from 'react-basics';
|
||||
Label,
|
||||
} from '@umami/react-zen';
|
||||
import styles from './FieldFilterEditForm.module.css';
|
||||
|
||||
export interface FieldFilterFormProps {
|
||||
|
|
@ -94,10 +96,6 @@ export function FieldFilterEditForm({
|
|||
: values;
|
||||
}, [value, formattedValues]);
|
||||
|
||||
const renderFilterValue = (value: any) => {
|
||||
return filters.find((filter: { value: any }) => filter.value === value)?.label;
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
onChange({ name, type, operator, value: isEquals ? selected : value });
|
||||
};
|
||||
|
|
@ -133,21 +131,21 @@ export function FieldFilterEditForm({
|
|||
};
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<FormRow label={label} className={styles.filter}>
|
||||
<Column>
|
||||
<Row className={styles.filter}>
|
||||
<Label>{label}</Label>
|
||||
<Flexbox gap={10}>
|
||||
{allowFilterSelect && (
|
||||
<Dropdown
|
||||
<Select
|
||||
className={styles.dropdown}
|
||||
items={filterDropdownItems(name)}
|
||||
value={operator}
|
||||
renderValue={renderFilterValue}
|
||||
onChange={handleOperatorChange}
|
||||
>
|
||||
{({ value, label }) => {
|
||||
return <Item key={value}>{label}</Item>;
|
||||
{({ value, label }: any) => {
|
||||
return <ListItem key={value}>{label}</ListItem>;
|
||||
}}
|
||||
</Dropdown>
|
||||
</Select>
|
||||
)}
|
||||
{selected && isEquals && (
|
||||
<div className={styles.selected} onClick={handleReset}>
|
||||
|
|
@ -163,7 +161,6 @@ export function FieldFilterEditForm({
|
|||
className={styles.text}
|
||||
value={value}
|
||||
placeholder={formatMessage(labels.enter)}
|
||||
onChange={e => setValue(e.target.value)}
|
||||
onSearch={handleSearch}
|
||||
delay={500}
|
||||
onFocus={() => setShowMenu(true)}
|
||||
|
|
@ -187,11 +184,11 @@ export function FieldFilterEditForm({
|
|||
/>
|
||||
)}
|
||||
</Flexbox>
|
||||
<Button variant="primary" onClick={handleAdd} disabled={isDisabled}>
|
||||
<Button variant="primary" onPress={handleAdd} isDisabled={isDisabled}>
|
||||
{formatMessage(isNew ? labels.add : labels.update)}
|
||||
</Button>
|
||||
</FormRow>
|
||||
</Form>
|
||||
</Row>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -199,10 +196,10 @@ const ResultsMenu = ({ values, type, isLoading, onSelect }) => {
|
|||
const { formatValue } = useFormat();
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Menu className={styles.menu} variant="popup">
|
||||
<Item>
|
||||
<Menu className={styles.menu}>
|
||||
<MenuItem>
|
||||
<Loading icon="dots" position="center" />
|
||||
</Item>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
|
@ -212,9 +209,9 @@ const ResultsMenu = ({ values, type, isLoading, onSelect }) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<Menu className={styles.menu} variant="popup" onSelect={onSelect}>
|
||||
<Menu className={styles.menu} onSelectionChange={onSelect}>
|
||||
{values?.map(({ value }) => {
|
||||
return <Item key={value}>{formatValue(value, type)}</Item>;
|
||||
return <MenuItem key={value}>{formatValue(value, type)}</MenuItem>;
|
||||
})}
|
||||
</Menu>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useFields, useMessages } from '@/components/hooks';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { useContext } from 'react';
|
||||
import { Button, FormRow, Icon, Popup, PopupTrigger } from 'react-basics';
|
||||
import { Button, FormRow, Icon, Popup, PopupTrigger } from '@umami/react-zen';
|
||||
import { FieldSelectForm } from '../[reportId]/FieldSelectForm';
|
||||
import { ParameterList } from '../[reportId]/ParameterList';
|
||||
import { PopupForm } from '../[reportId]/PopupForm';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Menu, Item, Form, FormRow } from 'react-basics';
|
||||
import { List, ListItem, Label, Column } from '@umami/react-zen';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import styles from './FieldSelectForm.module.css';
|
||||
import { Key } from 'react';
|
||||
|
|
@ -13,19 +13,18 @@ export function FieldSelectForm({ fields = [], onSelect, showType = true }: Fiel
|
|||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<FormRow label={formatMessage(labels.fields)}>
|
||||
<Menu className={styles.menu} onSelect={key => onSelect(fields[key as any])}>
|
||||
{fields.map(({ name, label, type }: any, index: Key) => {
|
||||
return (
|
||||
<Item key={index} className={styles.item}>
|
||||
<div>{label || name}</div>
|
||||
{showType && type && <div className={styles.type}>{type}</div>}
|
||||
</Item>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
</FormRow>
|
||||
</Form>
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.fields)}</Label>
|
||||
<List onSelectionChange={key => onSelect(fields[key as any])}>
|
||||
{fields.map(({ name, label, type }: any, index: Key) => {
|
||||
return (
|
||||
<ListItem key={index} className={styles.item}>
|
||||
<div>{label || name}</div>
|
||||
{showType && type && <div className={styles.type}>{type}</div>}
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useContext } from 'react';
|
||||
import { useMessages, useFormat, useFilters, useFields } from '@/components/hooks';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { Button, FormRow, Icon, Popup, PopupTrigger } from 'react-basics';
|
||||
import { Button, FormRow, Icon, Popup, PopupTrigger } from '@umami/react-zen';
|
||||
import { FilterSelectForm } from '../[reportId]/FilterSelectForm';
|
||||
import { ParameterList } from '../[reportId]/ParameterList';
|
||||
import { PopupForm } from '../[reportId]/PopupForm';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Icon } from 'react-basics';
|
||||
import { Icon } from '@umami/react-zen';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { createContext, ReactNode } from 'react';
|
||||
import { Loading } from 'react-basics';
|
||||
import { Loading } from '@umami/react-zen';
|
||||
import classNames from 'classnames';
|
||||
import { useReport } from '@/components/hooks';
|
||||
import styles from './Report.module.css';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useContext } from 'react';
|
||||
import { Icon, LoadingButton, InlineEditField, useToasts } from 'react-basics';
|
||||
import { Icon, LoadingButton, InlineEditField, useToast } from '@umami/react-zen';
|
||||
import { useMessages, useApi, useNavigation, useTeamUrl } from '@/components/hooks';
|
||||
import { ReportContext } from './Report';
|
||||
import styles from './ReportHeader.module.css';
|
||||
|
|
@ -9,7 +9,7 @@ import { Breadcrumb } from '@/components/common/Breadcrumb';
|
|||
export function ReportHeader({ icon }) {
|
||||
const { report, updateReport } = useContext(ReportContext);
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { showToast } = useToasts();
|
||||
const { toast } = useToast();
|
||||
const { router } = useNavigation();
|
||||
const { renderTeamUrl } = useTeamUrl();
|
||||
|
||||
|
|
@ -29,14 +29,14 @@ export function ReportHeader({ icon }) {
|
|||
if (!report.id) {
|
||||
create(report, {
|
||||
onSuccess: async ({ id }) => {
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
toast(formatMessage(messages.saved));
|
||||
router.push(renderTeamUrl(`/reports/${id}`));
|
||||
},
|
||||
});
|
||||
} else {
|
||||
update(report, {
|
||||
onSuccess: async () => {
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
toast(formatMessage(messages.saved));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -94,7 +94,7 @@ export function ReportHeader({ icon }) {
|
|||
variant="primary"
|
||||
isLoading={isCreating || isUpdating}
|
||||
disabled={!websiteId || !dateRange?.value || !name}
|
||||
onClick={handleSave}
|
||||
onPress={handleSave}
|
||||
>
|
||||
{formatMessage(labels.save)}
|
||||
</LoadingButton>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useContext, useState } from 'react';
|
||||
import { ReportContext } from './Report';
|
||||
import styles from './ReportMenu.module.css';
|
||||
import { Icon, Icons } from 'react-basics';
|
||||
import { Icon, Icons } from '@umami/react-zen';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export function ReportMenu({ children }) {
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
.reports {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.report {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
border: 1px solid var(--base500);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.description {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
import { Button, Icon, Text } from '@umami/react-zen';
|
||||
import { Icon, Text, Row, Column, Grid } from '@umami/react-zen';
|
||||
import { useMessages, useTeamUrl } from '@/components/hooks';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { PageHeader } from '@/components/layout/PageHeader';
|
||||
import Link from 'next/link';
|
||||
import styles from './ReportTemplates.module.css';
|
||||
import { LinkButton } from '@/components/common/LinkButton';
|
||||
|
||||
export function ReportTemplates({ showHeader = true }: { showHeader?: boolean }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
|
@ -57,13 +56,13 @@ export function ReportTemplates({ showHeader = true }: { showHeader?: boolean })
|
|||
return (
|
||||
<>
|
||||
{showHeader && <PageHeader title={formatMessage(labels.reports)} />}
|
||||
<div className={styles.reports}>
|
||||
<Grid columns="repeat(3, minmax(200px, 1fr))" gap="3">
|
||||
{reports.map(({ title, description, url, icon }) => {
|
||||
return (
|
||||
<ReportItem key={title} icon={icon} title={title} description={description} url={url} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -72,22 +71,22 @@ function ReportItem({ title, description, url, icon }) {
|
|||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<div className={styles.report}>
|
||||
<div className={styles.title}>
|
||||
<Icon size="lg">{icon}</Icon>
|
||||
<Text>{title}</Text>
|
||||
</div>
|
||||
<div className={styles.description}>{description}</div>
|
||||
<div className={styles.buttons}>
|
||||
<Link href={url}>
|
||||
<Button variant="primary">
|
||||
<Icon>
|
||||
<Icons.Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.create)}</Text>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<Column gap="6" padding="6" borderSize="1" borderRadius="3" justifyContent="space-between">
|
||||
<Row gap="3" alignItems="center">
|
||||
<Icon size="md">{icon}</Icon>
|
||||
<Text size="5" weight="bold">
|
||||
{title}
|
||||
</Text>
|
||||
</Row>
|
||||
<Text>{description}</Text>
|
||||
<Row justifyContent="flex-end">
|
||||
<LinkButton href={url} variant="primary">
|
||||
<Icon fillColor="currentColor">
|
||||
<Icons.Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.create)}</Text>
|
||||
</LinkButton>
|
||||
</Row>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
import { useContext } from 'react';
|
||||
import { Form, FormRow, FormButtons, SubmitButton, PopupTrigger, Icon, Popup } from 'react-basics';
|
||||
import {
|
||||
Form,
|
||||
FormRow,
|
||||
FormButtons,
|
||||
SubmitButton,
|
||||
PopupTrigger,
|
||||
Icon,
|
||||
Popup,
|
||||
} from '@umami/react-zen';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { useApi, useMessages } from '@/components/hooks';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useContext } from 'react';
|
||||
import { GridTable, GridColumn } from 'react-basics';
|
||||
import { GridTable, GridColumn } from '@umami/react-zen';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { ReportContext } from '../[reportId]/Report';
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
SubmitButton,
|
||||
TextField,
|
||||
Button,
|
||||
} from 'react-basics';
|
||||
} from '@umami/react-zen';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { FunnelStepAddForm } from './FunnelStepAddForm';
|
||||
import { ReportContext } from '../[reportId]/Report';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useState } from 'react';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { Button, FormRow, TextField, Flexbox, Dropdown, Item } from 'react-basics';
|
||||
import { Button, FormRow, TextField, Flexbox, Dropdown, Item } from '@umami/react-zen';
|
||||
import styles from './FunnelStepAddForm.module.css';
|
||||
|
||||
export interface FunnelStepAddFormProps {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useMessages } from '@/components/hooks';
|
||||
import { useState } from 'react';
|
||||
import { Button, Dropdown, Flexbox, FormRow, Item, TextField } from 'react-basics';
|
||||
import { Button, Dropdown, Flexbox, FormRow, Item, TextField } from '@umami/react-zen';
|
||||
import styles from './GoalsAddForm.module.css';
|
||||
|
||||
export function GoalsAddForm({
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
Popup,
|
||||
PopupTrigger,
|
||||
SubmitButton,
|
||||
} from 'react-basics';
|
||||
} from '@umami/react-zen';
|
||||
import { BaseParameters } from '../[reportId]/BaseParameters';
|
||||
import { ParameterList } from '../[reportId]/ParameterList';
|
||||
import { PopupForm } from '../[reportId]/PopupForm';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useMessages } from '@/components/hooks';
|
||||
import { useContext } from 'react';
|
||||
import { Form, FormButtons, SubmitButton } from 'react-basics';
|
||||
import { Form, FormButtons, SubmitButton } from '@umami/react-zen';
|
||||
import { BaseParameters } from '../[reportId]/BaseParameters';
|
||||
import { ReportContext } from '../[reportId]/Report';
|
||||
import { FieldParameters } from '../[reportId]/FieldParameters';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useContext, useEffect, useState } from 'react';
|
||||
import { GridTable, GridColumn } from 'react-basics';
|
||||
import { GridTable, GridColumn } from '@umami/react-zen';
|
||||
import { useFormat, useMessages } from '@/components/hooks';
|
||||
import { ReportContext } from '../[reportId]/Report';
|
||||
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
Item,
|
||||
SubmitButton,
|
||||
TextField,
|
||||
} from 'react-basics';
|
||||
} from '@umami/react-zen';
|
||||
import { ReportContext } from '../[reportId]/Report';
|
||||
import { BaseParameters } from '../[reportId]/BaseParameters';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useContext, useMemo, useState } from 'react';
|
||||
import { TextOverflow, TooltipPopup } from 'react-basics';
|
||||
import { TextOverflow, TooltipPopup } from '@umami/react-zen';
|
||||
import { firstBy } from 'thenby';
|
||||
import classNames from 'classnames';
|
||||
import { useEscapeKey, useMessages } from '@/components/hooks';
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { useContext } from 'react';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { Form, FormButtons, FormRow, SubmitButton } from 'react-basics';
|
||||
import { Form, FormButtons, FormSubmitButton } from '@umami/react-zen';
|
||||
import { ReportContext } from '../[reportId]/Report';
|
||||
import { MonthSelect } from '@/components/input/MonthSelect';
|
||||
import { BaseParameters } from '../[reportId]/BaseParameters';
|
||||
import { parseDateRange } from '@/lib/date';
|
||||
|
||||
|
|
@ -31,13 +30,11 @@ export function RetentionParameters() {
|
|||
return (
|
||||
<Form values={parameters} onSubmit={handleSubmit} preventSubmit={true}>
|
||||
<BaseParameters showDateSelect={false} allowWebsiteSelect={!id} />
|
||||
<FormRow label={formatMessage(labels.date)}>
|
||||
<MonthSelect date={startDate} onChange={handleDateChange} />
|
||||
</FormRow>
|
||||
|
||||
<FormButtons>
|
||||
<SubmitButton variant="primary" disabled={queryDisabled} isLoading={isRunning}>
|
||||
<FormSubmitButton variant="primary" disabled={queryDisabled} isLoading={isRunning}>
|
||||
{formatMessage(labels.runQuery)}
|
||||
</SubmitButton>
|
||||
</FormSubmitButton>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
import { useMessages } from '@/components/hooks';
|
||||
import { useRevenueValues } from '@/components/hooks/queries/useRevenueValues';
|
||||
import { useContext } from 'react';
|
||||
import { Dropdown, Form, FormButtons, FormInput, FormRow, Item, SubmitButton } from 'react-basics';
|
||||
import {
|
||||
Dropdown,
|
||||
Form,
|
||||
FormButtons,
|
||||
FormInput,
|
||||
FormRow,
|
||||
Item,
|
||||
SubmitButton,
|
||||
} from '@umami/react-zen';
|
||||
import { BaseParameters } from '../[reportId]/BaseParameters';
|
||||
import { ReportContext } from '../[reportId]/Report';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { EmptyPlaceholder } from '@/components/common/EmptyPlaceholder';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { useContext } from 'react';
|
||||
import { GridColumn, GridTable } from 'react-basics';
|
||||
import { GridColumn, GridTable } from '@umami/react-zen';
|
||||
import { ReportContext } from '../[reportId]/Report';
|
||||
import { formatLongCurrency } from '@/lib/format';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useContext } from 'react';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { Form, FormButtons, SubmitButton } from 'react-basics';
|
||||
import { Form, FormButtons, SubmitButton } from '@umami/react-zen';
|
||||
import { ReportContext } from '../[reportId]/Report';
|
||||
import { BaseParameters } from '../[reportId]/BaseParameters';
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,10 @@ import {
|
|||
Button,
|
||||
Form,
|
||||
FormButtons,
|
||||
FormInput,
|
||||
FormRow,
|
||||
SubmitButton,
|
||||
FormField,
|
||||
FormSubmitButton,
|
||||
TextField,
|
||||
} from 'react-basics';
|
||||
} from '@umami/react-zen';
|
||||
|
||||
export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
|
@ -27,16 +26,14 @@ export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose:
|
|||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} error={error}>
|
||||
<FormRow label={formatMessage(labels.name)}>
|
||||
<FormInput name="name" rules={{ required: formatMessage(labels.required) }}>
|
||||
<TextField autoComplete="off" />
|
||||
</FormInput>
|
||||
</FormRow>
|
||||
<FormButtons flex>
|
||||
<SubmitButton variant="primary" disabled={isPending}>
|
||||
<FormField name="name" label={formatMessage(labels.name)}>
|
||||
<TextField autoComplete="off" />
|
||||
</FormField>
|
||||
<FormButtons>
|
||||
<FormSubmitButton variant="primary" disabled={isPending}>
|
||||
{formatMessage(labels.save)}
|
||||
</SubmitButton>
|
||||
<Button disabled={isPending} onClick={onClose}>
|
||||
</FormSubmitButton>
|
||||
<Button isDisabled={isPending} onPress={onClose}>
|
||||
{formatMessage(labels.cancel)}
|
||||
</Button>
|
||||
</FormButtons>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,17 @@
|
|||
import { useRef } from 'react';
|
||||
import {
|
||||
Form,
|
||||
FormRow,
|
||||
FormInput,
|
||||
FormField,
|
||||
FormButtons,
|
||||
TextField,
|
||||
Button,
|
||||
SubmitButton,
|
||||
} from 'react-basics';
|
||||
FormSubmitButton,
|
||||
} from '@umami/react-zen';
|
||||
import { useApi, useMessages, useModified } from '@/components/hooks';
|
||||
|
||||
export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { post, useMutation } = useApi();
|
||||
const { mutate, error } = useMutation({ mutationFn: (data: any) => post('/teams/join', data) });
|
||||
const ref = useRef(null);
|
||||
const { touch } = useModified();
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
|
|
@ -28,15 +25,17 @@ export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose:
|
|||
};
|
||||
|
||||
return (
|
||||
<Form ref={ref} onSubmit={handleSubmit} error={error}>
|
||||
<FormRow label={formatMessage(labels.accessCode)}>
|
||||
<FormInput name="accessCode" rules={{ required: formatMessage(labels.required) }}>
|
||||
<TextField autoComplete="off" />
|
||||
</FormInput>
|
||||
</FormRow>
|
||||
<FormButtons flex>
|
||||
<SubmitButton variant="primary">{formatMessage(labels.join)}</SubmitButton>
|
||||
<Button onClick={onClose}>{formatMessage(labels.cancel)}</Button>
|
||||
<Form onSubmit={handleSubmit} error={error}>
|
||||
<FormField
|
||||
label={formatMessage(labels.accessCode)}
|
||||
name="accessCode"
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<TextField autoComplete="off" />
|
||||
</FormField>
|
||||
<FormButtons>
|
||||
<FormSubmitButton variant="primary">{formatMessage(labels.join)}</FormSubmitButton>
|
||||
<Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useLocale, useLogin, useMessages, useModified } from '@/components/hooks';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Button, Icon, Icons, Modal, ModalTrigger, Text } from 'react-basics';
|
||||
import { Button, Icon, Icons, Modal, DialogTrigger, Dialog, Text } from '@umami/react-zen';
|
||||
import { TeamLeaveForm } from './TeamLeaveForm';
|
||||
|
||||
export function TeamLeaveButton({ teamId, teamName }: { teamId: string; teamName: string }) {
|
||||
|
|
@ -16,24 +16,26 @@ export function TeamLeaveButton({ teamId, teamName }: { teamId: string; teamName
|
|||
};
|
||||
|
||||
return (
|
||||
<ModalTrigger>
|
||||
<DialogTrigger>
|
||||
<Button variant="secondary">
|
||||
<Icon rotate={dir === 'rtl' ? 180 : 0}>
|
||||
<Icons.Logout />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.leave)}</Text>
|
||||
</Button>
|
||||
<Modal title={formatMessage(labels.leaveTeam)}>
|
||||
{(close: () => void) => (
|
||||
<TeamLeaveForm
|
||||
teamId={teamId}
|
||||
userId={user.id}
|
||||
teamName={teamName}
|
||||
onSave={handleLeave}
|
||||
onClose={close}
|
||||
/>
|
||||
)}
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.leaveTeam)}>
|
||||
{({ close }) => (
|
||||
<TeamLeaveForm
|
||||
teamId={teamId}
|
||||
userId={user.id}
|
||||
teamName={teamName}
|
||||
onSave={handleLeave}
|
||||
onClose={close}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
</DialogTrigger>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Button, Icon, Modal, ModalTrigger, Text, useToasts } from 'react-basics';
|
||||
import { Button, Icon, Modal, DialogTrigger, Dialog, Text, useToast } from '@umami/react-zen';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { useMessages, useModified } from '@/components/hooks';
|
||||
import { TeamAddForm } from './TeamAddForm';
|
||||
|
|
@ -6,26 +6,28 @@ import { messages } from '@/components/messages';
|
|||
|
||||
export function TeamsAddButton({ onSave }: { onSave?: () => void }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { showToast } = useToasts();
|
||||
const { toast } = useToast();
|
||||
const { touch } = useModified();
|
||||
|
||||
const handleSave = async () => {
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
toast(formatMessage(messages.saved));
|
||||
touch('teams');
|
||||
onSave?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalTrigger>
|
||||
<DialogTrigger>
|
||||
<Button variant="primary">
|
||||
<Icon>
|
||||
<Icons.Plus />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.createTeam)}</Text>
|
||||
</Button>
|
||||
<Modal title={formatMessage(labels.createTeam)}>
|
||||
{(close: () => void) => <TeamAddForm onSave={handleSave} onClose={close} />}
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.createTeam)}>
|
||||
{({ close }) => <TeamAddForm onSave={handleSave} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
</DialogTrigger>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Flexbox } from 'react-basics';
|
||||
import { Row } from '@umami/react-zen';
|
||||
import { PageHeader } from '@/components/layout/PageHeader';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
import { useLogin, useMessages } from '@/components/hooks';
|
||||
|
|
@ -12,10 +12,10 @@ export function TeamsHeader({ allowCreate = true }: { allowCreate?: boolean }) {
|
|||
|
||||
return (
|
||||
<PageHeader title={formatMessage(labels.teams)}>
|
||||
<Flexbox gap={10}>
|
||||
<Row gap="3">
|
||||
{!cloudMode && <TeamsJoinButton />}
|
||||
{allowCreate && user.role !== ROLES.viewOnly && <TeamsAddButton />}
|
||||
</Flexbox>
|
||||
</Row>
|
||||
</PageHeader>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,31 @@
|
|||
import { Button, Icon, Modal, ModalTrigger, Text, useToasts } from 'react-basics';
|
||||
import { Button, Icon, Modal, DialogTrigger, Dialog, Text, useToast } from '@umami/react-zen';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { useMessages, useModified } from '@/components/hooks';
|
||||
import { TeamJoinForm } from './TeamJoinForm';
|
||||
|
||||
export function TeamsJoinButton() {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { showToast } = useToasts();
|
||||
const { toast } = useToast();
|
||||
const { touch } = useModified();
|
||||
|
||||
const handleJoin = () => {
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
toast(formatMessage(messages.saved));
|
||||
touch('teams');
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalTrigger>
|
||||
<DialogTrigger>
|
||||
<Button variant="secondary">
|
||||
<Icon>
|
||||
<Icons.AddUser />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.joinTeam)}</Text>
|
||||
</Button>
|
||||
<Modal title={formatMessage(labels.joinTeam)}>
|
||||
{close => <TeamJoinForm onSave={handleJoin} onClose={close} />}
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.joinTeam)}>
|
||||
{({ close }) => <TeamJoinForm onSave={handleJoin} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
</DialogTrigger>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GridColumn, GridTable, Icon, Text } from 'react-basics';
|
||||
import { DataColumn, DataTable, Icon, Text } from '@umami/react-zen';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
|
|
@ -15,33 +15,33 @@ export function TeamsTable({
|
|||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<GridTable data={data}>
|
||||
<GridColumn name="name" label={formatMessage(labels.name)} />
|
||||
<GridColumn name="owner" label={formatMessage(labels.owner)}>
|
||||
{row => row.teamUser.find(({ role }) => role === ROLES.teamOwner)?.user?.username}
|
||||
</GridColumn>
|
||||
<GridColumn name="websites" label={formatMessage(labels.websites)}>
|
||||
{row => row._count.website}
|
||||
</GridColumn>
|
||||
<GridColumn name="members" label={formatMessage(labels.members)}>
|
||||
{row => row._count.teamUser}
|
||||
</GridColumn>
|
||||
<DataTable data={data}>
|
||||
<DataColumn id="name" label={formatMessage(labels.name)} />
|
||||
<DataColumn id="owner" label={formatMessage(labels.owner)}>
|
||||
{(row: any) => row.teamUser.find(({ role }) => role === ROLES.teamOwner)?.user?.username}
|
||||
</DataColumn>
|
||||
<DataColumn id="websites" label={formatMessage(labels.websites)}>
|
||||
{(row: any) => row._count.website}
|
||||
</DataColumn>
|
||||
<DataColumn id="members" label={formatMessage(labels.members)}>
|
||||
{(row: any) => row._count.teamUser}
|
||||
</DataColumn>
|
||||
{showActions && (
|
||||
<GridColumn name="action" label=" " alignment="end">
|
||||
{row => {
|
||||
<DataColumn id="action" label=" " align="end">
|
||||
{(row: any) => {
|
||||
const { id } = row;
|
||||
|
||||
return (
|
||||
<LinkButton href={`/teams/${id}/settings`}>
|
||||
<Icon>
|
||||
<Icons.ArrowRight />
|
||||
<Icons.Arrow />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.view)}</Text>
|
||||
</LinkButton>
|
||||
);
|
||||
}}
|
||||
</GridColumn>
|
||||
</DataColumn>
|
||||
)}
|
||||
</GridTable>
|
||||
</DataTable>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Button, Icon, Icons, Text } from 'react-basics';
|
||||
import { Button, Icon, Icons, Text } from '@umami/react-zen';
|
||||
import styles from './WebsiteTags.module.css';
|
||||
|
||||
export function WebsiteTags({
|
||||
|
|
@ -21,7 +21,7 @@ export function WebsiteTags({
|
|||
|
||||
return (
|
||||
<div key={websiteId} className={styles.tag}>
|
||||
<Button onClick={() => onClick(websiteId)} variant="primary" size="sm">
|
||||
<Button onPress={() => onClick(websiteId)} variant="primary" size="sm">
|
||||
<Text>
|
||||
<b>{`${website.name}`}</b>
|
||||
</Text>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
import {
|
||||
Dropdown,
|
||||
Item,
|
||||
Select,
|
||||
ListItem,
|
||||
Form,
|
||||
FormRow,
|
||||
FormField,
|
||||
FormButtons,
|
||||
FormInput,
|
||||
FormSubmitButton,
|
||||
TextField,
|
||||
PasswordField,
|
||||
SubmitButton,
|
||||
Button,
|
||||
} from 'react-basics';
|
||||
} from '@umami/react-zen';
|
||||
import { useApi, useMessages } from '@/components/hooks';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
|
||||
|
|
@ -29,44 +28,38 @@ export function UserAddForm({ onSave, onClose }) {
|
|||
});
|
||||
};
|
||||
|
||||
const renderValue = (value: string) => {
|
||||
if (value === ROLES.user) {
|
||||
return formatMessage(labels.user);
|
||||
}
|
||||
if (value === ROLES.admin) {
|
||||
return formatMessage(labels.admin);
|
||||
}
|
||||
if (value === ROLES.viewOnly) {
|
||||
return formatMessage(labels.viewOnly);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} error={error}>
|
||||
<FormRow label={formatMessage(labels.username)}>
|
||||
<FormInput name="username" rules={{ required: formatMessage(labels.required) }}>
|
||||
<TextField autoComplete="new-username" />
|
||||
</FormInput>
|
||||
</FormRow>
|
||||
<FormRow label={formatMessage(labels.password)}>
|
||||
<FormInput name="password" rules={{ required: formatMessage(labels.required) }}>
|
||||
<PasswordField autoComplete="new-password" />
|
||||
</FormInput>
|
||||
</FormRow>
|
||||
<FormRow label={formatMessage(labels.role)}>
|
||||
<FormInput name="role" rules={{ required: formatMessage(labels.required) }}>
|
||||
<Dropdown renderValue={renderValue}>
|
||||
<Item key={ROLES.viewOnly}>{formatMessage(labels.viewOnly)}</Item>
|
||||
<Item key={ROLES.user}>{formatMessage(labels.user)}</Item>
|
||||
<Item key={ROLES.admin}>{formatMessage(labels.admin)}</Item>
|
||||
</Dropdown>
|
||||
</FormInput>
|
||||
</FormRow>
|
||||
<FormButtons flex>
|
||||
<SubmitButton variant="primary" disabled={false}>
|
||||
<FormField
|
||||
label={formatMessage(labels.username)}
|
||||
name="username"
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<TextField autoComplete="new-username" />
|
||||
</FormField>
|
||||
<FormField
|
||||
label={formatMessage(labels.password)}
|
||||
name="password"
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<PasswordField autoComplete="new-password" />
|
||||
</FormField>
|
||||
<FormField
|
||||
label={formatMessage(labels.role)}
|
||||
name="role"
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<Select>
|
||||
<ListItem id={ROLES.viewOnly}>{formatMessage(labels.viewOnly)}</ListItem>
|
||||
<ListItem id={ROLES.user}>{formatMessage(labels.user)}</ListItem>
|
||||
<ListItem id={ROLES.admin}>{formatMessage(labels.admin)}</ListItem>
|
||||
</Select>
|
||||
</FormField>
|
||||
<FormButtons>
|
||||
<FormSubmitButton variant="primary" disabled={false}>
|
||||
{formatMessage(labels.save)}
|
||||
</SubmitButton>
|
||||
<Button disabled={isPending} onClick={onClose}>
|
||||
</FormSubmitButton>
|
||||
<Button isDisabled={isPending} onPress={onClose}>
|
||||
{formatMessage(labels.cancel)}
|
||||
</Button>
|
||||
</FormButtons>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import {
|
||||
Form,
|
||||
FormRow,
|
||||
FormField,
|
||||
FormButtons,
|
||||
Flexbox,
|
||||
TextField,
|
||||
Button,
|
||||
Toggle,
|
||||
LoadingButton,
|
||||
} from 'react-basics';
|
||||
Switch,
|
||||
FormSubmitButton,
|
||||
Box,
|
||||
useToast,
|
||||
} from '@umami/react-zen';
|
||||
import { useContext, useState } from 'react';
|
||||
import { getRandomChars } from '@/lib/crypto';
|
||||
import { useApi, useMessages, useModified } from '@/components/hooks';
|
||||
|
|
@ -25,6 +26,7 @@ export function ShareUrl({ hostUrl, onSave }: { hostUrl?: string; onSave?: () =>
|
|||
mutationFn: (data: any) => post(`/websites/${website.id}`, data),
|
||||
});
|
||||
const { touch } = useModified();
|
||||
const { toast } = useToast();
|
||||
|
||||
const url = `${hostUrl || window?.location.origin || ''}${
|
||||
process.env.basePath || ''
|
||||
|
|
@ -34,7 +36,8 @@ export function ShareUrl({ hostUrl, onSave }: { hostUrl?: string; onSave?: () =>
|
|||
setId(generateId());
|
||||
};
|
||||
|
||||
const handleCheck = (checked: boolean) => {
|
||||
const handleSwitch = (checked: boolean) => {
|
||||
console.log({ checked });
|
||||
const data = {
|
||||
name: website.name,
|
||||
domain: website.domain,
|
||||
|
|
@ -42,6 +45,7 @@ export function ShareUrl({ hostUrl, onSave }: { hostUrl?: string; onSave?: () =>
|
|||
};
|
||||
mutate(data, {
|
||||
onSuccess: async () => {
|
||||
toast(formatMessage(messages.saved));
|
||||
touch(`website:${website.id}`);
|
||||
onSave?.();
|
||||
},
|
||||
|
|
@ -54,6 +58,7 @@ export function ShareUrl({ hostUrl, onSave }: { hostUrl?: string; onSave?: () =>
|
|||
{ name: website.name, domain: website.domain, shareId: id },
|
||||
{
|
||||
onSuccess: async () => {
|
||||
toast(formatMessage(messages.saved));
|
||||
touch(`website:${website.id}`);
|
||||
onSave?.();
|
||||
},
|
||||
|
|
@ -63,27 +68,21 @@ export function ShareUrl({ hostUrl, onSave }: { hostUrl?: string; onSave?: () =>
|
|||
|
||||
return (
|
||||
<>
|
||||
<Toggle checked={Boolean(id)} onChecked={handleCheck} style={{ marginBottom: 30 }}>
|
||||
{formatMessage(labels.enableShareUrl)}
|
||||
</Toggle>
|
||||
<Box marginBottom="6">
|
||||
<Switch defaultSelected={!!id} isSelected={!!id} onChange={handleSwitch}>
|
||||
{formatMessage(labels.enableShareUrl)}
|
||||
</Switch>
|
||||
</Box>
|
||||
{id && (
|
||||
<Form error={error}>
|
||||
<FormRow>
|
||||
<p>{formatMessage(messages.shareUrl)}</p>
|
||||
<Flexbox gap={10}>
|
||||
<TextField value={url} readOnly allowCopy />
|
||||
<Button onClick={handleGenerate}>{formatMessage(labels.regenerate)}</Button>
|
||||
</Flexbox>
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
<LoadingButton
|
||||
variant="primary"
|
||||
disabled={id === shareId}
|
||||
isLoading={isPending}
|
||||
onClick={handleSave}
|
||||
>
|
||||
<Form onSubmit={handleSave} error={error} values={{ id, url }}>
|
||||
<FormField label={formatMessage(messages.shareUrl)} name="url">
|
||||
<TextField isReadOnly allowCopy />
|
||||
</FormField>
|
||||
<FormButtons justifyContent="space-between">
|
||||
<Button onPress={handleGenerate}>{formatMessage(labels.regenerate)}</Button>
|
||||
<FormSubmitButton variant="primary" isDisabled={id === shareId} isLoading={isPending}>
|
||||
{formatMessage(labels.save)}
|
||||
</LoadingButton>
|
||||
</FormSubmitButton>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { TextArea } from 'react-basics';
|
||||
import { TextArea } from '@umami/react-zen';
|
||||
import { useMessages, useConfig } from '@/components/hooks';
|
||||
|
||||
const SCRIPT_NAME = 'script.js';
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { Button, Modal, ModalTrigger, ActionForm } from 'react-basics';
|
||||
import { Button, Modal, DialogTrigger, Dialog, Column } from '@umami/react-zen';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useLogin, useMessages, useModified, useTeams, useTeamUrl } from '@/components/hooks';
|
||||
import { WebsiteDeleteForm } from './WebsiteDeleteForm';
|
||||
import { WebsiteResetForm } from './WebsiteResetForm';
|
||||
import { WebsiteTransferForm } from './WebsiteTransferForm';
|
||||
import { ActionForm } from '@/components/layout/ActionForm';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
|
||||
export function WebsiteData({ websiteId, onSave }: { websiteId: string; onSave?: () => void }) {
|
||||
|
|
@ -39,50 +40,58 @@ export function WebsiteData({ websiteId, onSave }: { websiteId: string; onSave?:
|
|||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Column gap="6">
|
||||
<ActionForm
|
||||
label={formatMessage(labels.transferWebsite)}
|
||||
description={formatMessage(messages.transferWebsite)}
|
||||
>
|
||||
<ModalTrigger disabled={!canTransferWebsite}>
|
||||
<Button variant="secondary" disabled={!canTransferWebsite}>
|
||||
<DialogTrigger>
|
||||
<Button variant="secondary" isDisabled={!canTransferWebsite}>
|
||||
{formatMessage(labels.transfer)}
|
||||
</Button>
|
||||
<Modal title={formatMessage(labels.transferWebsite)}>
|
||||
{(close: () => void) => (
|
||||
<WebsiteTransferForm websiteId={websiteId} onSave={handleSave} onClose={close} />
|
||||
)}
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.transferWebsite)}>
|
||||
{({ close }) => (
|
||||
<WebsiteTransferForm websiteId={websiteId} onSave={handleSave} onClose={close} />
|
||||
)}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
</DialogTrigger>
|
||||
</ActionForm>
|
||||
|
||||
<ActionForm
|
||||
label={formatMessage(labels.resetWebsite)}
|
||||
description={formatMessage(messages.resetWebsiteWarning)}
|
||||
>
|
||||
<ModalTrigger>
|
||||
<DialogTrigger>
|
||||
<Button variant="secondary">{formatMessage(labels.reset)}</Button>
|
||||
<Modal title={formatMessage(labels.resetWebsite)}>
|
||||
{(close: () => void) => (
|
||||
<WebsiteResetForm websiteId={websiteId} onSave={handleReset} onClose={close} />
|
||||
)}
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.resetWebsite)}>
|
||||
{({ close }) => (
|
||||
<WebsiteResetForm websiteId={websiteId} onSave={handleReset} onClose={close} />
|
||||
)}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
</DialogTrigger>
|
||||
</ActionForm>
|
||||
|
||||
<ActionForm
|
||||
label={formatMessage(labels.deleteWebsite)}
|
||||
description={formatMessage(messages.deleteWebsiteWarning)}
|
||||
>
|
||||
<ModalTrigger>
|
||||
<DialogTrigger>
|
||||
<Button data-test="button-delete" variant="danger">
|
||||
{formatMessage(labels.delete)}
|
||||
</Button>
|
||||
<Modal title={formatMessage(labels.deleteWebsite)}>
|
||||
{(close: () => void) => (
|
||||
<WebsiteDeleteForm websiteId={websiteId} onSave={handleSave} onClose={close} />
|
||||
)}
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.deleteWebsite)}>
|
||||
{({ close }) => (
|
||||
<WebsiteDeleteForm websiteId={websiteId} onSave={handleSave} onClose={close} />
|
||||
)}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
</DialogTrigger>
|
||||
</ActionForm>
|
||||
</>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export function WebsiteSettings({
|
|||
<Link href={`/websites/${websiteId}`} target={openExternal ? '_blank' : null}>
|
||||
<Button variant="primary">
|
||||
<Icon>
|
||||
<Icons.ArrowRight />
|
||||
<Icons.Arrow />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.view)}</Text>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ import {
|
|||
Button,
|
||||
Form,
|
||||
FormButtons,
|
||||
FormRow,
|
||||
LoadingButton,
|
||||
FormField,
|
||||
FormSubmitButton,
|
||||
Loading,
|
||||
Dropdown,
|
||||
Item,
|
||||
Flexbox,
|
||||
} from 'react-basics';
|
||||
Select,
|
||||
ListItem,
|
||||
Text,
|
||||
} from '@umami/react-zen';
|
||||
import { useApi, useLogin, useMessages, useTeams } from '@/components/hooks';
|
||||
import { WebsiteContext } from '@/app/(main)/websites/[websiteId]/WebsiteProvider';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
|
|
@ -28,12 +28,19 @@ export function WebsiteTransferForm({
|
|||
const [teamId, setTeamId] = useState<string>(null);
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { post, useMutation } = useApi();
|
||||
const { mutate, isPending, error } = useMutation({
|
||||
const { mutate, error } = useMutation({
|
||||
mutationFn: (data: any) => post(`/websites/${websiteId}/transfer`, data),
|
||||
});
|
||||
const { result, query } = useTeams(user.id);
|
||||
const isTeamWebsite = !!website?.teamId;
|
||||
|
||||
const items = result.data.filter(({ teamUser }) =>
|
||||
teamUser.find(
|
||||
({ role, userId }) =>
|
||||
[ROLES.teamOwner, ROLES.teamManager].includes(role) && userId === user.id,
|
||||
),
|
||||
);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
mutate(
|
||||
{
|
||||
|
|
@ -53,45 +60,35 @@ export function WebsiteTransferForm({
|
|||
setTeamId(key as string);
|
||||
};
|
||||
|
||||
const renderValue = (teamId: string) => result?.data?.find(({ id }) => id === teamId)?.name;
|
||||
|
||||
if (query.isLoading) {
|
||||
return <Loading icon="dots" position="center" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Form error={error}>
|
||||
<FormRow>
|
||||
<Flexbox direction="column" gap={20}>
|
||||
{formatMessage(
|
||||
isTeamWebsite ? messages.transferTeamWebsiteToUser : messages.transferUserWebsiteToTeam,
|
||||
)}
|
||||
{!isTeamWebsite && (
|
||||
<Dropdown onChange={handleChange} value={teamId} renderValue={renderValue}>
|
||||
{result.data
|
||||
.filter(({ teamUser }) =>
|
||||
teamUser.find(
|
||||
({ role, userId }) =>
|
||||
[ROLES.teamOwner, ROLES.teamManager].includes(role) && userId === user.id,
|
||||
),
|
||||
)
|
||||
.map(({ id, name }) => {
|
||||
return <Item key={id}>{name}</Item>;
|
||||
})}
|
||||
</Dropdown>
|
||||
)}
|
||||
</Flexbox>
|
||||
</FormRow>
|
||||
<FormButtons flex>
|
||||
<LoadingButton
|
||||
variant="primary"
|
||||
isLoading={isPending}
|
||||
disabled={!isTeamWebsite && !teamId}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
<Form onSubmit={handleSubmit} error={error} values={{ teamId }}>
|
||||
<Text>
|
||||
{formatMessage(
|
||||
isTeamWebsite ? messages.transferTeamWebsiteToUser : messages.transferUserWebsiteToTeam,
|
||||
)}
|
||||
</Text>
|
||||
<FormField name="teamId">
|
||||
{!isTeamWebsite && (
|
||||
<Select onSelectionChange={handleChange} value={teamId}>
|
||||
{items.map(({ id, name }) => {
|
||||
return (
|
||||
<ListItem key={id} id={id}>
|
||||
{name}
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
)}
|
||||
</FormField>
|
||||
<FormButtons>
|
||||
<Button onPress={onClose}>{formatMessage(labels.cancel)}</Button>
|
||||
<FormSubmitButton variant="primary" isDisabled={!isTeamWebsite && !teamId}>
|
||||
{formatMessage(labels.transfer)}
|
||||
</LoadingButton>
|
||||
<Button onClick={onClose}>{formatMessage(labels.cancel)}</Button>
|
||||
</FormSubmitButton>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
import { createContext, ReactNode, useEffect } from 'react';
|
||||
import { useTeam, useModified } from '@/components/hooks';
|
||||
import { Loading } from 'react-basics';
|
||||
import { Loading } from '@umami/react-zen';
|
||||
|
||||
export const TeamContext = createContext(null);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,14 @@
|
|||
import { useMessages, useModified } from '@/components/hooks';
|
||||
import { Button, Icon, Icons, Modal, ModalTrigger, Text, useToasts } from 'react-basics';
|
||||
import {
|
||||
Button,
|
||||
Icon,
|
||||
Icons,
|
||||
Modal,
|
||||
DialogTrigger,
|
||||
Dialog,
|
||||
Text,
|
||||
useToast,
|
||||
} from '@umami/react-zen';
|
||||
import { TeamMemberEditForm } from './TeamMemberEditForm';
|
||||
|
||||
export function TeamMemberEditButton({
|
||||
|
|
@ -14,34 +23,36 @@ export function TeamMemberEditButton({
|
|||
onSave?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { showToast } = useToasts();
|
||||
const { toast } = useToast();
|
||||
const { touch } = useModified();
|
||||
|
||||
const handleSave = () => {
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
toast(formatMessage(messages.saved));
|
||||
touch('teams:members');
|
||||
onSave?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalTrigger>
|
||||
<DialogTrigger>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.Edit />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.edit)}</Text>
|
||||
</Button>
|
||||
<Modal title={formatMessage(labels.editMember)}>
|
||||
{(close: () => void) => (
|
||||
<TeamMemberEditForm
|
||||
teamId={teamId}
|
||||
userId={userId}
|
||||
role={role}
|
||||
onSave={handleSave}
|
||||
onClose={close}
|
||||
/>
|
||||
)}
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.editMember)}>
|
||||
{({ close }) => (
|
||||
<TeamMemberEditForm
|
||||
teamId={teamId}
|
||||
userId={userId}
|
||||
role={role}
|
||||
onSave={handleSave}
|
||||
onClose={close}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
</DialogTrigger>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,13 @@ import { useApi, useMessages } from '@/components/hooks';
|
|||
import { ROLES } from '@/lib/constants';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Select,
|
||||
Form,
|
||||
FormButtons,
|
||||
FormInput,
|
||||
FormRow,
|
||||
Item,
|
||||
SubmitButton,
|
||||
} from 'react-basics';
|
||||
FormField,
|
||||
ListItem,
|
||||
FormSubmitButton,
|
||||
} from '@umami/react-zen';
|
||||
|
||||
export function TeamMemberEditForm({
|
||||
teamId,
|
||||
|
|
@ -39,39 +38,25 @@ export function TeamMemberEditForm({
|
|||
});
|
||||
};
|
||||
|
||||
const renderValue = (value: string) => {
|
||||
if (value === ROLES.teamManager) {
|
||||
return formatMessage(labels.manager);
|
||||
}
|
||||
if (value === ROLES.teamMember) {
|
||||
return formatMessage(labels.member);
|
||||
}
|
||||
if (value === ROLES.teamViewOnly) {
|
||||
return formatMessage(labels.viewOnly);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit} error={error} values={{ role }}>
|
||||
<FormRow label={formatMessage(labels.role)}>
|
||||
<FormInput name="role" rules={{ required: formatMessage(labels.required) }}>
|
||||
<Dropdown
|
||||
renderValue={renderValue}
|
||||
style={{
|
||||
minWidth: '250px',
|
||||
}}
|
||||
>
|
||||
<Item key={ROLES.teamManager}>{formatMessage(labels.manager)}</Item>
|
||||
<Item key={ROLES.teamMember}>{formatMessage(labels.member)}</Item>
|
||||
<Item key={ROLES.teamViewOnly}>{formatMessage(labels.viewOnly)}</Item>
|
||||
</Dropdown>
|
||||
</FormInput>
|
||||
</FormRow>
|
||||
<FormButtons flex>
|
||||
<SubmitButton variant="primary" disabled={false}>
|
||||
<FormField
|
||||
name="role"
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
label={formatMessage(labels.role)}
|
||||
>
|
||||
<Select>
|
||||
<ListItem id={ROLES.teamManager}>{formatMessage(labels.manager)}</ListItem>
|
||||
<ListItem id={ROLES.teamMember}>{formatMessage(labels.member)}</ListItem>
|
||||
<ListItem id={ROLES.teamViewOnly}>{formatMessage(labels.viewOnly)}</ListItem>
|
||||
</Select>
|
||||
</FormField>
|
||||
|
||||
<FormButtons>
|
||||
<FormSubmitButton variant="primary" disabled={false}>
|
||||
{formatMessage(labels.save)}
|
||||
</SubmitButton>
|
||||
<Button disabled={isPending} onClick={onClose}>
|
||||
</FormSubmitButton>
|
||||
<Button isDisabled={isPending} onPress={onClose}>
|
||||
{formatMessage(labels.cancel)}
|
||||
</Button>
|
||||
</FormButtons>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { ConfirmationForm } from '@/components/common/ConfirmationForm';
|
||||
import { useApi, useMessages, useModified } from '@/components/hooks';
|
||||
import { messages } from '@/components/messages';
|
||||
import { Button, Icon, Icons, Modal, ModalTrigger, Text } from 'react-basics';
|
||||
import { Button, Icon, Icons, Modal, DialogTrigger, Dialog, Text } from '@umami/react-zen';
|
||||
|
||||
export function TeamMemberRemoveButton({
|
||||
teamId,
|
||||
|
|
@ -33,25 +33,29 @@ export function TeamMemberRemoveButton({
|
|||
};
|
||||
|
||||
return (
|
||||
<ModalTrigger>
|
||||
<DialogTrigger>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.Close />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.remove)}</Text>
|
||||
</Button>
|
||||
<Modal title={formatMessage(labels.removeMember)}>
|
||||
{(close: () => void) => (
|
||||
<ConfirmationForm
|
||||
message={formatMessage(messages.confirmRemove, { target: <b>{userName}</b> })}
|
||||
isLoading={isPending}
|
||||
error={error}
|
||||
onConfirm={handleConfirm.bind(null, close)}
|
||||
onClose={close}
|
||||
buttonLabel={formatMessage(labels.remove)}
|
||||
/>
|
||||
)}
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.removeMember)}>
|
||||
{({ close }) => (
|
||||
<ConfirmationForm
|
||||
message={formatMessage(messages.confirmRemove, {
|
||||
target: <b key="username">{userName}</b>,
|
||||
})}
|
||||
isLoading={isPending}
|
||||
error={error}
|
||||
onConfirm={handleConfirm.bind(null, close)}
|
||||
onClose={close}
|
||||
buttonLabel={formatMessage(labels.remove)}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
</DialogTrigger>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GridColumn, GridTable } from 'react-basics';
|
||||
import { DataColumn, DataTable } from '@umami/react-zen';
|
||||
import { useMessages, useLogin } from '@/components/hooks';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
import { TeamMemberRemoveButton } from './TeamMemberRemoveButton';
|
||||
|
|
@ -24,15 +24,15 @@ export function TeamMembersTable({
|
|||
};
|
||||
|
||||
return (
|
||||
<GridTable data={data}>
|
||||
<GridColumn name="username" label={formatMessage(labels.username)}>
|
||||
{row => row?.user?.username}
|
||||
</GridColumn>
|
||||
<GridColumn name="role" label={formatMessage(labels.role)}>
|
||||
{row => roles[row?.role]}
|
||||
</GridColumn>
|
||||
<GridColumn name="action" label=" " alignment="end">
|
||||
{row => {
|
||||
<DataTable data={data}>
|
||||
<DataColumn id="username" label={formatMessage(labels.username)}>
|
||||
{(row: any) => row?.user?.username}
|
||||
</DataColumn>
|
||||
<DataColumn id="role" label={formatMessage(labels.role)}>
|
||||
{(row: any) => roles[row?.role]}
|
||||
</DataColumn>
|
||||
<DataColumn id="action" align="end">
|
||||
{(row: any) => {
|
||||
return (
|
||||
allowEdit &&
|
||||
row?.role !== ROLES.teamOwner &&
|
||||
|
|
@ -48,7 +48,7 @@ export function TeamMembersTable({
|
|||
)
|
||||
);
|
||||
}}
|
||||
</GridColumn>
|
||||
</GridTable>
|
||||
</DataColumn>
|
||||
</DataTable>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { Icons } from '@/components/icons';
|
|||
import { PageHeader } from '@/components/layout/PageHeader';
|
||||
import { ROLES } from '@/lib/constants';
|
||||
import { useContext, useState } from 'react';
|
||||
import { Flexbox, Item, Tabs } from 'react-basics';
|
||||
import { Column, Tabs, TabList, Tab, TabPanel } from '@umami/react-zen';
|
||||
import { TeamLeaveButton } from '@/app/(main)/settings/teams/TeamLeaveButton';
|
||||
import { TeamManage } from './TeamManage';
|
||||
import { TeamEditForm } from './TeamEditForm';
|
||||
|
|
@ -26,16 +26,22 @@ export function TeamDetails({ teamId }: { teamId: string }) {
|
|||
) && user.role !== ROLES.viewOnly;
|
||||
|
||||
return (
|
||||
<Flexbox direction="column">
|
||||
<Column>
|
||||
<PageHeader title={team?.name} icon={<Icons.Users />}>
|
||||
{!isTeamOwner && <TeamLeaveButton teamId={team.id} teamName={team.name} />}
|
||||
</PageHeader>
|
||||
<Tabs selectedKey={tab} onSelect={(value: any) => setTab(value)} style={{ marginBottom: 30 }}>
|
||||
<Item key="details">{formatMessage(labels.details)}</Item>
|
||||
{isTeamOwner && <Item key="manage">{formatMessage(labels.manage)}</Item>}
|
||||
<Tabs selectedKey={tab} onSelectionChange={(value: any) => setTab(value)}>
|
||||
<TabList>
|
||||
<Tab id="details">{formatMessage(labels.details)}</Tab>
|
||||
{isTeamOwner && <Tab id="manage">{formatMessage(labels.manage)}</Tab>}
|
||||
</TabList>
|
||||
<TabPanel id="details">
|
||||
<TeamEditForm teamId={teamId} allowEdit={canEdit} />
|
||||
</TabPanel>
|
||||
<TabPanel id="manage">
|
||||
<TeamManage teamId={teamId} />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
{tab === 'details' && <TeamEditForm teamId={teamId} allowEdit={canEdit} />}
|
||||
{tab === 'manage' && <TeamManage teamId={teamId} />}
|
||||
</Flexbox>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,18 @@
|
|||
import {
|
||||
SubmitButton,
|
||||
Form,
|
||||
FormInput,
|
||||
FormRow,
|
||||
FormField,
|
||||
FormButtons,
|
||||
FormSubmitButton,
|
||||
TextField,
|
||||
Button,
|
||||
Flexbox,
|
||||
useToasts,
|
||||
} from 'react-basics';
|
||||
useToast,
|
||||
} from '@umami/react-zen';
|
||||
import { getRandomChars } from '@/lib/crypto';
|
||||
import { useContext, useRef, useState } from 'react';
|
||||
import { useContext, useState } from 'react';
|
||||
import { useApi, useMessages, useModified } from '@/components/hooks';
|
||||
import { TeamContext } from '@/app/(main)/teams/[teamId]/TeamProvider';
|
||||
|
||||
const generateId = () => getRandomChars(16);
|
||||
const generateId = () => `team_${getRandomChars(16)}`;
|
||||
|
||||
export function TeamEditForm({ teamId, allowEdit }: { teamId: string; allowEdit?: boolean }) {
|
||||
const team = useContext(TeamContext);
|
||||
|
|
@ -23,57 +21,48 @@ export function TeamEditForm({ teamId, allowEdit }: { teamId: string; allowEdit?
|
|||
const { mutate, error } = useMutation({
|
||||
mutationFn: (data: any) => post(`/teams/${teamId}`, data),
|
||||
});
|
||||
const ref = useRef(null);
|
||||
const [accessCode, setAccessCode] = useState(team.accessCode);
|
||||
const { showToast } = useToasts();
|
||||
const { toast } = useToast();
|
||||
const { touch } = useModified();
|
||||
const cloudMode = !!process.env.cloudMode;
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
mutate(data, {
|
||||
onSuccess: async () => {
|
||||
ref.current.reset(data);
|
||||
touch('teams');
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
toast(formatMessage(messages.saved));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleRegenerate = () => {
|
||||
const code = generateId();
|
||||
ref.current.setValue('accessCode', code, {
|
||||
shouldValidate: true,
|
||||
shouldDirty: true,
|
||||
});
|
||||
setAccessCode(code);
|
||||
setAccessCode(generateId());
|
||||
};
|
||||
|
||||
return (
|
||||
<Form ref={ref} onSubmit={handleSubmit} error={error} values={team}>
|
||||
<FormRow label={formatMessage(labels.teamId)}>
|
||||
<TextField value={teamId} readOnly allowCopy />
|
||||
</FormRow>
|
||||
<FormRow label={formatMessage(labels.name)}>
|
||||
{allowEdit && (
|
||||
<FormInput name="name" rules={{ required: formatMessage(labels.required) }}>
|
||||
<TextField />
|
||||
</FormInput>
|
||||
)}
|
||||
<Form onSubmit={handleSubmit} error={error} values={{ ...team, accessCode }}>
|
||||
<FormField name="id" label={formatMessage(labels.teamId)}>
|
||||
<TextField isReadOnly allowCopy />
|
||||
</FormField>
|
||||
<FormField
|
||||
name="name"
|
||||
label={formatMessage(labels.name)}
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
{allowEdit && <TextField />}
|
||||
{!allowEdit && team.name}
|
||||
</FormRow>
|
||||
</FormField>
|
||||
{!cloudMode && allowEdit && (
|
||||
<FormRow label={formatMessage(labels.accessCode)}>
|
||||
<Flexbox gap={10}>
|
||||
<TextField value={accessCode} readOnly allowCopy />
|
||||
{allowEdit && (
|
||||
<Button onClick={handleRegenerate}>{formatMessage(labels.regenerate)}</Button>
|
||||
)}
|
||||
</Flexbox>
|
||||
</FormRow>
|
||||
<FormField name="accessCode" label={formatMessage(labels.accessCode)}>
|
||||
<TextField isReadOnly allowCopy />
|
||||
</FormField>
|
||||
)}
|
||||
{allowEdit && (
|
||||
<FormButtons>
|
||||
<SubmitButton variant="primary">{formatMessage(labels.save)}</SubmitButton>
|
||||
<FormButtons justifyContent="space-between">
|
||||
{allowEdit && (
|
||||
<Button onPress={handleRegenerate}>{formatMessage(labels.regenerate)}</Button>
|
||||
)}
|
||||
<FormSubmitButton variant="primary">{formatMessage(labels.save)}</FormSubmitButton>
|
||||
</FormButtons>
|
||||
)}
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useMessages, useModified } from '@/components/hooks';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { ActionForm, Button, Modal, ModalTrigger } from 'react-basics';
|
||||
import { Button, Modal, DialogTrigger, Dialog } from '@umami/react-zen';
|
||||
import { ActionForm } from '@/components/layout/ActionForm';
|
||||
import { TeamDeleteForm } from './TeamDeleteForm';
|
||||
|
||||
export function TeamManage({ teamId }: { teamId: string }) {
|
||||
|
|
@ -18,14 +19,14 @@ export function TeamManage({ teamId }: { teamId: string }) {
|
|||
label={formatMessage(labels.deleteTeam)}
|
||||
description={formatMessage(messages.deleteTeamWarning)}
|
||||
>
|
||||
<ModalTrigger>
|
||||
<DialogTrigger>
|
||||
<Button variant="danger">{formatMessage(labels.delete)}</Button>
|
||||
<Modal title={formatMessage(labels.deleteTeam)}>
|
||||
{(close: () => void) => (
|
||||
<TeamDeleteForm teamId={teamId} onSave={handleLeave} onClose={close} />
|
||||
)}
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.deleteTeam)}>
|
||||
{({ close }) => <TeamDeleteForm teamId={teamId} onSave={handleLeave} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</ModalTrigger>
|
||||
</DialogTrigger>
|
||||
</ActionForm>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useApi, useMessages } from '@/components/hooks';
|
||||
import { Icon, Icons, LoadingButton, Text } from 'react-basics';
|
||||
import { Icon, Icons, LoadingButton, Text } from '@umami/react-zen';
|
||||
|
||||
export function TeamWebsiteRemoveButton({ teamId, websiteId, onSave }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GridColumn, GridTable, Icon, Text } from 'react-basics';
|
||||
import { DataColumn, DataTable, Icon, Text } from '@umami/react-zen';
|
||||
import { useLogin, useMessages } from '@/components/hooks';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { LinkButton } from '@/components/common/LinkButton';
|
||||
|
|
@ -16,14 +16,14 @@ export function TeamWebsitesTable({
|
|||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<GridTable data={data}>
|
||||
<GridColumn name="name" label={formatMessage(labels.name)} />
|
||||
<GridColumn name="domain" label={formatMessage(labels.domain)} />
|
||||
<GridColumn name="createdBy" label={formatMessage(labels.createdBy)}>
|
||||
{row => row?.createUser?.username}
|
||||
</GridColumn>
|
||||
<GridColumn name="action" label=" " alignment="end">
|
||||
{row => {
|
||||
<DataTable data={data}>
|
||||
<DataColumn id="name" label={formatMessage(labels.name)} />
|
||||
<DataColumn id="domain" label={formatMessage(labels.domain)} />
|
||||
<DataColumn id="createdBy" label={formatMessage(labels.createdBy)}>
|
||||
{(row: any) => row?.createUser?.username}
|
||||
</DataColumn>
|
||||
<DataColumn id="action" label=" " align="end">
|
||||
{(row: any) => {
|
||||
const { id: websiteId } = row;
|
||||
return (
|
||||
<>
|
||||
|
|
@ -37,14 +37,14 @@ export function TeamWebsitesTable({
|
|||
)}
|
||||
<LinkButton href={`/teams/${teamId}/websites/${websiteId}`}>
|
||||
<Icon>
|
||||
<Icons.ArrowRight />
|
||||
<Icons.Arrow />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.view)}</Text>
|
||||
</LinkButton>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</GridColumn>
|
||||
</GridTable>
|
||||
</DataColumn>
|
||||
</DataTable>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Page } from '@/app/(main)/settings/websites/[websiteId]/page';
|
||||
import Page from '@/app/(main)/settings/websites/[websiteId]/page';
|
||||
|
||||
export default function ({ params }) {
|
||||
return <Page params={params} />;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { Button, Text, Icon, Icons } from 'react-basics';
|
||||
import { Text, Icon, Icons } from '@umami/react-zen';
|
||||
import { useMemo } from 'react';
|
||||
import { firstBy } from 'thenby';
|
||||
import Link from 'next/link';
|
||||
import { WebsiteChart } from './WebsiteChart';
|
||||
import { useDashboard } from '@/store/dashboard';
|
||||
import { WebsiteHeader } from './WebsiteHeader';
|
||||
import { WebsiteMetricsBar } from './WebsiteMetricsBar';
|
||||
import { useMessages, useLocale, useTeamUrl } from '@/components/hooks';
|
||||
import { useMessages, useTeamUrl } from '@/components/hooks';
|
||||
import { LinkButton } from '@/components/common/LinkButton';
|
||||
|
||||
export function WebsiteChartList({
|
||||
websites,
|
||||
|
|
@ -20,7 +20,6 @@ export function WebsiteChartList({
|
|||
const { formatMessage, labels } = useMessages();
|
||||
const { websiteOrder, websiteActive } = useDashboard();
|
||||
const { renderTeamUrl } = useTeamUrl();
|
||||
const { dir } = useLocale();
|
||||
|
||||
const ordered = useMemo(() => {
|
||||
return websites
|
||||
|
|
@ -35,16 +34,14 @@ export function WebsiteChartList({
|
|||
return index < limit ? (
|
||||
<div key={id}>
|
||||
<WebsiteHeader websiteId={id} showLinks={false}>
|
||||
<Link href={renderTeamUrl(`/websites/${id}`)}>
|
||||
<Button variant="primary">
|
||||
<Text>{formatMessage(labels.viewDetails)}</Text>
|
||||
<LinkButton href={renderTeamUrl(`/websites/${id}`)} variant="primary">
|
||||
<Text>{formatMessage(labels.viewDetails)}</Text>
|
||||
<Icon>
|
||||
<Icon>
|
||||
<Icon rotate={dir === 'rtl' ? 180 : 0}>
|
||||
<Icons.ArrowRight />
|
||||
</Icon>
|
||||
<Icons.Arrow />
|
||||
</Icon>
|
||||
</Button>
|
||||
</Link>
|
||||
</Icon>
|
||||
</LinkButton>
|
||||
</WebsiteHeader>
|
||||
<WebsiteMetricsBar websiteId={id} showChange={true} />
|
||||
{showCharts && <WebsiteChart websiteId={id} />}
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: 300px 1fr;
|
||||
border-top: 1px solid var(--base300);
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
padding: 20px 20px 20px 0;
|
||||
}
|
||||
|
||||
.back {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
align-self: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.content {
|
||||
min-height: 800px;
|
||||
padding: 20px 0 20px 20px;
|
||||
border-left: 1px solid var(--base300);
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 992px) {
|
||||
.layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.content {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.back {
|
||||
align-self: flex-start;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
display: flex;
|
||||
width: 200px;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-inline-end: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { Dropdown, Icon, Icons, Item, Text } from 'react-basics';
|
||||
import { Select, Icon, Icons, ListItem, Text, Grid, Column } from '@umami/react-zen';
|
||||
import { LinkButton } from '@/components/common/LinkButton';
|
||||
import { useLocale, useMessages, useNavigation } from '@/components/hooks';
|
||||
import { useMessages, useNavigation } from '@/components/hooks';
|
||||
import { MenuNav } from '@/components/layout/MenuNav';
|
||||
import { BrowsersTable } from '@/components/metrics/BrowsersTable';
|
||||
import { CitiesTable } from '@/components/metrics/CitiesTable';
|
||||
|
|
@ -17,7 +17,7 @@ import { RegionsTable } from '@/components/metrics/RegionsTable';
|
|||
import { ScreenTable } from '@/components/metrics/ScreenTable';
|
||||
import { TagsTable } from '@/components/metrics/TagsTable';
|
||||
import { ChannelsTable } from '@/components/metrics/ChannelsTable';
|
||||
import styles from './WebsiteExpandedView.module.css';
|
||||
import Link from 'next/link';
|
||||
|
||||
const views = {
|
||||
url: PagesTable,
|
||||
|
|
@ -48,7 +48,6 @@ export function WebsiteExpandedView({
|
|||
websiteId: string;
|
||||
domainName?: string;
|
||||
}) {
|
||||
const { dir } = useLocale();
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const {
|
||||
router,
|
||||
|
|
@ -136,39 +135,25 @@ export function WebsiteExpandedView({
|
|||
|
||||
const DetailsComponent = views[view] || (() => null);
|
||||
|
||||
/*
|
||||
const handleChange = (view: any) => {
|
||||
router.push(renderUrl({ view }));
|
||||
};
|
||||
|
||||
const renderValue = (value: string) => items.find(({ key }) => key === value)?.label;
|
||||
|
||||
*/
|
||||
return (
|
||||
<div className={styles.layout}>
|
||||
<div className={styles.menu}>
|
||||
<LinkButton
|
||||
href={renderUrl({ view: undefined })}
|
||||
className={styles.back}
|
||||
variant="quiet"
|
||||
scroll={false}
|
||||
>
|
||||
<Icon rotate={dir === 'rtl' ? 0 : 180}>
|
||||
<Icons.ArrowRight />
|
||||
<Grid columns="auto 1fr" gap="6">
|
||||
<Column gap="6" width="200px">
|
||||
<LinkButton href={renderUrl({ view: undefined })} variant="quiet" scroll={false}>
|
||||
<Icon rotate={180}>
|
||||
<Icons.Arrow />
|
||||
</Icon>
|
||||
<Text>{formatMessage(labels.back)}</Text>
|
||||
</LinkButton>
|
||||
<MenuNav className={styles.nav} items={items} selectedKey={view} shallow={true} />
|
||||
<Dropdown
|
||||
className={styles.dropdown}
|
||||
items={items}
|
||||
value={view}
|
||||
renderValue={renderValue}
|
||||
onChange={handleChange}
|
||||
alignment="end"
|
||||
>
|
||||
{({ key, label }) => <Item key={key}>{label}</Item>}
|
||||
</Dropdown>
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<MenuNav items={items} selectedKey={view} />
|
||||
</Column>
|
||||
<Column>
|
||||
<DetailsComponent
|
||||
websiteId={websiteId}
|
||||
domainName={domainName}
|
||||
|
|
@ -178,7 +163,7 @@ export function WebsiteExpandedView({
|
|||
allowFilter={true}
|
||||
allowSearch={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Column>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Button, Icon, Icons, Popup, PopupTrigger, Text } from 'react-basics';
|
||||
import { Button, Icon, Icons, Box, MenuTrigger, Popover, Text } from '@umami/react-zen';
|
||||
import { PopupForm } from '@/app/(main)/reports/[reportId]/PopupForm';
|
||||
import { FilterSelectForm } from '@/app/(main)/reports/[reportId]/FilterSelectForm';
|
||||
import { useFields, useMessages, useNavigation, useDateRange } from '@/components/hooks';
|
||||
|
|
@ -7,13 +7,9 @@ import styles from './WebsiteFilterButton.module.css';
|
|||
|
||||
export function WebsiteFilterButton({
|
||||
websiteId,
|
||||
className,
|
||||
position = 'bottom',
|
||||
alignment = 'end',
|
||||
showText = true,
|
||||
}: {
|
||||
websiteId: string;
|
||||
className?: string;
|
||||
position?: 'bottom' | 'top' | 'left' | 'right';
|
||||
alignment?: 'end' | 'center' | 'start';
|
||||
showText?: boolean;
|
||||
|
|
@ -32,17 +28,17 @@ export function WebsiteFilterButton({
|
|||
};
|
||||
|
||||
return (
|
||||
<PopupTrigger className={className}>
|
||||
<MenuTrigger>
|
||||
<Button className={styles.button} variant="quiet">
|
||||
<Icon>
|
||||
<Icons.Plus />
|
||||
</Icon>
|
||||
{showText && <Text>{formatMessage(labels.filter)}</Text>}
|
||||
</Button>
|
||||
<Popup position={position} alignment={alignment}>
|
||||
{(close: () => void) => {
|
||||
<Popover placement="bottom end">
|
||||
{({ close }: any) => {
|
||||
return (
|
||||
<PopupForm>
|
||||
<Box padding="3" backgroundColor="1">
|
||||
<FilterSelectForm
|
||||
websiteId={websiteId}
|
||||
fields={fields}
|
||||
|
|
@ -53,10 +49,10 @@ export function WebsiteFilterButton({
|
|||
close();
|
||||
}}
|
||||
/>
|
||||
</PopupForm>
|
||||
</Box>
|
||||
);
|
||||
}}
|
||||
</Popup>
|
||||
</PopupTrigger>
|
||||
</Popover>
|
||||
</MenuTrigger>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { ReactNode } from 'react';
|
||||
import { Button, Icon, Text } from 'react-basics';
|
||||
import { Button, Icon, Text } from '@umami/react-zen';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import classNames from 'classnames';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Dropdown, Item } from 'react-basics';
|
||||
import { Select, ListItem } from '@umami/react-zen';
|
||||
import classNames from 'classnames';
|
||||
import { useDateRange, useMessages, useSticky } from '@/components/hooks';
|
||||
import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
|
||||
|
|
@ -114,18 +114,16 @@ export function WebsiteMetricsBar({
|
|||
{compareMode && (
|
||||
<div className={styles.vs}>
|
||||
<b>VS</b>
|
||||
<Dropdown
|
||||
<Select
|
||||
className={styles.dropdown}
|
||||
items={items}
|
||||
value={dateCompare || 'prev'}
|
||||
renderValue={value => items.find(i => i.value === value)?.label}
|
||||
alignment="end"
|
||||
onChange={(value: any) => setWebsiteDateCompare(websiteId, value)}
|
||||
>
|
||||
{items.map(({ label, value }) => (
|
||||
<Item key={value}>{label}</Item>
|
||||
<ListItem key={value}>{label}</ListItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
'use client';
|
||||
import { createContext, ReactNode, useEffect } from 'react';
|
||||
import { useModified, useWebsite } from '@/components/hooks';
|
||||
import { Loading } from 'react-basics';
|
||||
import { Loading } from '@umami/react-zen';
|
||||
|
||||
export const WebsiteContext = createContext(null);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GridColumn, GridTable } from 'react-basics';
|
||||
import { GridColumn, GridTable } from '@umami/react-zen';
|
||||
import { useEventDataProperties, useEventDataValues, useMessages } from '@/components/hooks';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
import { PieChart } from '@/components/charts/PieChart';
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
|
|||
import { MetricCard } from '@/components/metrics/MetricCard';
|
||||
import { MetricsBar } from '@/components/metrics/MetricsBar';
|
||||
import { formatLongNumber } from '@/lib/format';
|
||||
import { Flexbox } from 'react-basics';
|
||||
import { Flexbox } from '@umami/react-zen';
|
||||
|
||||
export function EventsMetricsBar({ websiteId }: { websiteId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { EventsChart } from '@/components/metrics/EventsChart';
|
|||
import { GridRow } from '@/components/layout/Grid';
|
||||
import { MetricsTable } from '@/components/metrics/MetricsTable';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { Item, Tabs } from 'react-basics';
|
||||
import { Item, Tabs } from '@umami/react-zen';
|
||||
import { useState } from 'react';
|
||||
import { EventProperties } from './EventProperties';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GridTable, GridColumn, Icon } from 'react-basics';
|
||||
import { GridTable, GridColumn, Icon } from '@umami/react-zen';
|
||||
import { useMessages, useTeamUrl, useTimezone } from '@/components/hooks';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
import { Avatar } from '@/components/common/Avatar';
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { BROWSERS, OS_NAMES } from '@/lib/constants';
|
|||
import { stringToColor } from '@/lib/format';
|
||||
import { RealtimeData } from '@/lib/types';
|
||||
import { useContext, useMemo, useState } from 'react';
|
||||
import { Icon, SearchField, StatusLight, Text } from 'react-basics';
|
||||
import { Icon, SearchField, StatusLight, Text } from '@umami/react-zen';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import { WebsiteContext } from '../WebsiteProvider';
|
||||
import styles from './RealtimeLog.module.css';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Key, useContext, useState } from 'react';
|
||||
import { ButtonGroup, Button, Flexbox } from 'react-basics';
|
||||
import { ButtonGroup, Button, Flexbox } from '@umami/react-zen';
|
||||
import thenby from 'thenby';
|
||||
import { percentFilter } from '@/lib/filters';
|
||||
import { ListTable } from '@/components/metrics/ListTable';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
import Link from 'next/link';
|
||||
import { Button, Flexbox, Icon, Icons, Text } from 'react-basics';
|
||||
import { Button, Flexbox, Icon, Icons, Text } from '@umami/react-zen';
|
||||
import { useMessages, useTeamUrl } from '@/components/hooks';
|
||||
import { WebsiteHeader } from '../WebsiteHeader';
|
||||
import { ReportsDataTable } from '@/app/(main)/reports/ReportsDataTable';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { GridColumn, GridTable } from 'react-basics';
|
||||
import { GridColumn, GridTable } from '@umami/react-zen';
|
||||
import { useSessionDataProperties, useSessionDataValues, useMessages } from '@/components/hooks';
|
||||
import { LoadingPanel } from '@/components/common/LoadingPanel';
|
||||
import { PieChart } from '@/components/charts/PieChart';
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { WebsiteDateFilter } from '@/components/input/WebsiteDateFilter';
|
|||
import { MetricCard } from '@/components/metrics/MetricCard';
|
||||
import { MetricsBar } from '@/components/metrics/MetricsBar';
|
||||
import { formatLongNumber } from '@/lib/format';
|
||||
import { Flexbox } from 'react-basics';
|
||||
import { Flexbox } from '@umami/react-zen';
|
||||
|
||||
export function SessionsMetricsBar({ websiteId }: { websiteId: string }) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { SessionsMetricsBar } from './SessionsMetricsBar';
|
|||
import { SessionProperties } from './SessionProperties';
|
||||
import { WorldMap } from '@/components/metrics/WorldMap';
|
||||
import { GridRow } from '@/components/layout/Grid';
|
||||
import { Item, Tabs } from 'react-basics';
|
||||
import { Item, Tabs } from '@umami/react-zen';
|
||||
import { useState } from 'react';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { SessionsWeekly } from './SessionsWeekly';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Link from 'next/link';
|
||||
import { GridColumn, GridTable } from 'react-basics';
|
||||
import { GridColumn, GridTable } from '@umami/react-zen';
|
||||
import { useFormat, useMessages, useTimezone } from '@/components/hooks';
|
||||
import { Avatar } from '@/components/common/Avatar';
|
||||
import styles from './SessionsTable.module.css';
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { LoadingPanel } from '@/components/common/LoadingPanel';
|
|||
import { getDayOfWeekAsDate } from '@/lib/date';
|
||||
import styles from './SessionsWeekly.module.css';
|
||||
import classNames from 'classnames';
|
||||
import { TooltipPopup } from 'react-basics';
|
||||
import { TooltipPopup } from '@umami/react-zen';
|
||||
|
||||
export function SessionsWeekly({ websiteId }: { websiteId: string }) {
|
||||
const { data, ...props } = useWebsiteSessionsWeekly(websiteId);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { isSameDay } from 'date-fns';
|
||||
import { Loading, Icon, StatusLight } from 'react-basics';
|
||||
import { Loading, Icon, StatusLight } from '@umami/react-zen';
|
||||
import { Icons } from '@/components/icons';
|
||||
import { useSessionActivity, useTimezone } from '@/components/hooks';
|
||||
import styles from './SessionActivity.module.css';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { TextOverflow } from 'react-basics';
|
||||
import { TextOverflow } from '@umami/react-zen';
|
||||
import { useMessages, useSessionData } from '@/components/hooks';
|
||||
import { Empty } from '@/components/common/Empty';
|
||||
import { DATA_TYPES } from '@/lib/constants';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useFormat, useLocale, useMessages, useRegionNames, useTimezone } from '@/components/hooks';
|
||||
import { TypeIcon } from '@/components/common/TypeIcon';
|
||||
import { Icon, CopyIcon } from 'react-basics';
|
||||
import { Icon, CopyIcon } from '@umami/react-zen';
|
||||
import { Icons } from '@/components/icons';
|
||||
import styles from './SessionInfo.module.css';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
'use client';
|
||||
import { useEffect } from 'react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ZenProvider } from '@umami/react-zen';
|
||||
import 'chartjs-adapter-date-fns';
|
||||
import { useEffect } from 'react';
|
||||
import { ZenProvider, RouterProvider } from '@umami/react-zen';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { RouterProvider } from 'react-aria-components';
|
||||
import { ErrorBoundary } from '@/components/common/ErrorBoundary';
|
||||
import { useLocale } from '@/components/hooks';
|
||||
import 'chartjs-adapter-date-fns';
|
||||
|
||||
const client = new QueryClient({
|
||||
defaultOptions: {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { z } from 'zod';
|
|||
import { unauthorized, json, badRequest } from '@/lib/response';
|
||||
import { canAddUserToTeam, canViewTeam } from '@/lib/auth';
|
||||
import { parseRequest } from '@/lib/request';
|
||||
import { pagingParams, roleParam } from '@/lib/schema';
|
||||
import { pagingParams, teamRoleParam } from '@/lib/schema';
|
||||
import { createTeamUser, getTeamUser, getTeamUsers } from '@/queries';
|
||||
|
||||
export async function GET(request: Request, { params }: { params: Promise<{ teamId: string }> }) {
|
||||
|
|
@ -48,7 +48,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ team
|
|||
export async function POST(request: Request, { params }: { params: Promise<{ teamId: string }> }) {
|
||||
const schema = z.object({
|
||||
userId: z.string().uuid(),
|
||||
role: roleParam,
|
||||
role: teamRoleParam,
|
||||
});
|
||||
|
||||
const { auth, body, error } = await parseRequest(request, schema);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { getUser, getUserByUsername, updateUser, deleteUser } from '@/queries';
|
|||
import { json, unauthorized, badRequest, ok } from '@/lib/response';
|
||||
import { hashPassword } from '@/lib/auth';
|
||||
import { parseRequest } from '@/lib/request';
|
||||
import { userRoleParam } from '@/lib/schema';
|
||||
|
||||
export async function GET(request: Request, { params }: { params: Promise<{ userId: string }> }) {
|
||||
const { auth, error } = await parseRequest(request);
|
||||
|
|
@ -26,8 +27,8 @@ export async function GET(request: Request, { params }: { params: Promise<{ user
|
|||
export async function POST(request: Request, { params }: { params: Promise<{ userId: string }> }) {
|
||||
const schema = z.object({
|
||||
username: z.string().max(255),
|
||||
password: z.string().max(255),
|
||||
role: z.enum(['admin', 'user', 'view-only']),
|
||||
password: z.string().max(255).optional(),
|
||||
role: userRoleParam,
|
||||
});
|
||||
|
||||
const { auth, body, error } = await parseRequest(request, schema);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export default function ({ children }) {
|
|||
<meta name="theme-color" content="#2f2f2f" media="(prefers-color-scheme: dark)" />
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
</head>
|
||||
<body>
|
||||
<body suppressHydrationWarning>
|
||||
<Providers>{children}</Providers>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
'use client';
|
||||
import { Flexbox } from 'react-basics';
|
||||
import { Flexbox } from '@umami/react-zen';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
|
||||
export default function () {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<Flexbox alignItems="center" justifyContent="center" flex={1} style={{ minHeight: 600 }}>
|
||||
<Flexbox alignItems="center" justifyContent="center" flexGrow="1" minHeight="600px">
|
||||
<h1>{formatMessage(labels.pageNotFound)}</h1>
|
||||
</Flexbox>
|
||||
);
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue