mirror of
https://github.com/kremalicious/umami.git
synced 2025-02-01 12:29:35 +01:00
Updated website, team and user save.
This commit is contained in:
parent
2fa50892d8
commit
fec81695e8
@ -4,6 +4,7 @@ import { Item, Loading, Tabs, Flexbox } from 'react-basics';
|
||||
import TeamsContext from 'app/(main)/teams/TeamsContext';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import { ROLES } from 'lib/constants';
|
||||
import Icons from 'components/icons';
|
||||
import { useLogin, useTeam, useMessages } from 'components/hooks';
|
||||
import TeamEditForm from './TeamEditForm';
|
||||
import TeamMembers from './TeamMembers';
|
||||
@ -27,7 +28,7 @@ export function TeamSettings({ teamId }: { teamId: string }) {
|
||||
return (
|
||||
<TeamsContext.Provider value={team}>
|
||||
<Flexbox direction="column">
|
||||
<PageHeader title={team?.name} />
|
||||
<PageHeader title={team?.name} icon={<Icons.Users />} />
|
||||
<Tabs
|
||||
selectedKey={tab}
|
||||
onSelect={(value: any) => setTab(value)}
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
import DataTable from 'components/common/DataTable';
|
||||
import { useUsers } from 'components/hooks';
|
||||
import UsersTable from './UsersTable';
|
||||
import useUsers from 'components/hooks/queries/useUsers';
|
||||
|
||||
export function UsersDataTable({ showActions }: { showActions?: boolean }) {
|
||||
const queryResult = useUsers();
|
||||
|
@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
import { Key, useState } from 'react';
|
||||
import { Item, Loading, Tabs } from 'react-basics';
|
||||
import Icons from 'components/icons';
|
||||
import UserEditForm from '../UserEditForm';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import { useMessages, useUser } from 'components/hooks';
|
||||
@ -17,7 +18,7 @@ export function UserSettings({ userId }: { userId: string }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title={user?.username} />
|
||||
<PageHeader title={user?.username} icon={<Icons.User />} />
|
||||
<Tabs selectedKey={tab} onSelect={setTab} style={{ marginBottom: 30, fontSize: 14 }}>
|
||||
<Item key="details">{formatMessage(labels.details)}</Item>
|
||||
<Item key="websites">{formatMessage(labels.websites)}</Item>
|
||||
|
@ -10,8 +10,6 @@ import {
|
||||
import { useApi } from 'components/hooks';
|
||||
import { DOMAIN_REGEX } from 'lib/constants';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { useContext } from 'react';
|
||||
import SettingsContext from '../SettingsContext';
|
||||
|
||||
export function WebsiteAddForm({
|
||||
teamId,
|
||||
@ -23,10 +21,9 @@ export function WebsiteAddForm({
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { websitesUrl } = useContext(SettingsContext);
|
||||
const { post, useMutation } = useApi();
|
||||
const { mutate, error, isPending } = useMutation({
|
||||
mutationFn: (data: any) => post(websitesUrl, { ...data, teamId }),
|
||||
mutationFn: (data: any) => post('/websites', { ...data, teamId }),
|
||||
});
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
|
@ -1,39 +1,23 @@
|
||||
'use client';
|
||||
import { useState, Key } from 'react';
|
||||
import { Item, Tabs, useToasts, Button, Text, Icon, Icons, Loading } from 'react-basics';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Item, Tabs, Button, Text, Icon, Loading } from 'react-basics';
|
||||
import Link from 'next/link';
|
||||
import Icons from 'components/icons';
|
||||
import PageHeader from 'components/layout/PageHeader';
|
||||
import WebsiteEditForm from './[id]/WebsiteEditForm';
|
||||
import WebsiteData from './[id]/WebsiteData';
|
||||
import TrackingCode from './[id]/TrackingCode';
|
||||
import ShareUrl from './[id]/ShareUrl';
|
||||
import { useWebsite, useMessages } from 'components/hooks';
|
||||
import { touch } from 'store/cache';
|
||||
import WebsiteContext from 'app/(main)/websites/[id]/WebsiteContext';
|
||||
|
||||
export function WebsiteSettings({ websiteId, openExternal = false }) {
|
||||
const router = useRouter();
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { showToast } = useToasts();
|
||||
|
||||
const { data: website, isLoading } = useWebsite(websiteId, { gcTime: 0 });
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { data: website, isLoading, refetch } = useWebsite(websiteId, { gcTime: 0 });
|
||||
const [tab, setTab] = useState<Key>('details');
|
||||
|
||||
const showSuccess = () => {
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
showSuccess();
|
||||
touch('websites');
|
||||
};
|
||||
|
||||
const handleReset = async (value: string) => {
|
||||
if (value === 'delete') {
|
||||
router.push('/settings/websites');
|
||||
} else if (value === 'reset') {
|
||||
showSuccess();
|
||||
}
|
||||
refetch();
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
@ -41,8 +25,8 @@ export function WebsiteSettings({ websiteId, openExternal = false }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title={website?.name}>
|
||||
<WebsiteContext.Provider value={website}>
|
||||
<PageHeader title={website?.name} icon={<Icons.Globe />}>
|
||||
<Link href={`/websites/${websiteId}`} target={openExternal ? '_blank' : null}>
|
||||
<Button variant="primary">
|
||||
<Icon>
|
||||
@ -58,13 +42,11 @@ export function WebsiteSettings({ websiteId, openExternal = false }) {
|
||||
<Item key="share">{formatMessage(labels.shareUrl)}</Item>
|
||||
<Item key="data">{formatMessage(labels.data)}</Item>
|
||||
</Tabs>
|
||||
{tab === 'details' && (
|
||||
<WebsiteEditForm websiteId={websiteId} data={website} onSave={handleSave} />
|
||||
)}
|
||||
{tab === 'details' && <WebsiteEditForm website={website} onSave={handleSave} />}
|
||||
{tab === 'tracking' && <TrackingCode websiteId={websiteId} />}
|
||||
{tab === 'share' && <ShareUrl websiteId={websiteId} data={website} onSave={handleSave} />}
|
||||
{tab === 'data' && <WebsiteData websiteId={websiteId} onSave={handleReset} />}
|
||||
</>
|
||||
{tab === 'share' && <ShareUrl website={website} onSave={handleSave} />}
|
||||
{tab === 'data' && <WebsiteData websiteId={websiteId} />}
|
||||
</WebsiteContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2,16 +2,7 @@
|
||||
import { ReactNode } from 'react';
|
||||
import WebsitesTable from 'app/(main)/settings/websites/WebsitesTable';
|
||||
import DataTable from 'components/common/DataTable';
|
||||
import useWebsites from 'components/hooks/queries/useWebsites';
|
||||
|
||||
export interface WebsitesDataTableProps {
|
||||
userId?: string;
|
||||
teamId?: string;
|
||||
allowEdit?: boolean;
|
||||
allowView?: boolean;
|
||||
showActions?: boolean;
|
||||
children?: ReactNode;
|
||||
}
|
||||
import { useWebsites } from 'components/hooks';
|
||||
|
||||
export function WebsitesDataTable({
|
||||
userId,
|
||||
@ -20,13 +11,21 @@ export function WebsitesDataTable({
|
||||
allowView = true,
|
||||
showActions = true,
|
||||
children,
|
||||
}: WebsitesDataTableProps) {
|
||||
}: {
|
||||
userId?: string;
|
||||
teamId?: string;
|
||||
allowEdit?: boolean;
|
||||
allowView?: boolean;
|
||||
showActions?: boolean;
|
||||
children?: ReactNode;
|
||||
}) {
|
||||
const queryResult = useWebsites({ userId, teamId });
|
||||
|
||||
return (
|
||||
<DataTable queryResult={queryResult}>
|
||||
{({ data }) => (
|
||||
<WebsitesTable
|
||||
teamId={teamId}
|
||||
data={data}
|
||||
showActions={showActions}
|
||||
allowEdit={allowEdit}
|
||||
|
@ -46,7 +46,7 @@ export function WebsitesTable({
|
||||
</Link>
|
||||
)}
|
||||
{allowView && (
|
||||
<Link href={teamId ? `/team/${teamId}/websites/${id}` : `/websites/${id}`}>
|
||||
<Link href={teamId ? `/teams/${teamId}/websites/${id}` : `/websites/${id}`}>
|
||||
<Button>
|
||||
<Icon>
|
||||
<Icons.External />
|
||||
|
@ -1,68 +1,63 @@
|
||||
import { Website } from '@prisma/client';
|
||||
import {
|
||||
Form,
|
||||
FormRow,
|
||||
FormButtons,
|
||||
Flexbox,
|
||||
TextField,
|
||||
SubmitButton,
|
||||
Button,
|
||||
Toggle,
|
||||
LoadingButton,
|
||||
useToasts,
|
||||
} from 'react-basics';
|
||||
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useContext, useState } from 'react';
|
||||
import { getRandomChars } from 'next-basics';
|
||||
import { useApi, useMessages } from 'components/hooks';
|
||||
import SettingsContext from '../../SettingsContext';
|
||||
import SettingsContext from 'app/(main)/settings/SettingsContext';
|
||||
|
||||
const generateId = () => getRandomChars(16);
|
||||
|
||||
export function ShareUrl({ websiteId, data, onSave }) {
|
||||
const ref = useRef(null);
|
||||
const { shareUrl } = useContext(SettingsContext);
|
||||
export function ShareUrl({ website, onSave }: { website: Website; onSave?: () => void }) {
|
||||
const { domain, shareId } = website;
|
||||
const { hostUrl } = useContext(SettingsContext);
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { name, shareId } = data;
|
||||
const [id, setId] = useState(shareId);
|
||||
const { showToast } = useToasts();
|
||||
const { post, useMutation } = useApi();
|
||||
const { mutate, error } = useMutation({
|
||||
mutationFn: (data: any) => post(`/websites/${websiteId}`, data),
|
||||
const { mutate, error, isPending } = useMutation({
|
||||
mutationFn: (data: any) => post(`/websites/${website.id}`, data),
|
||||
});
|
||||
const url = useMemo(
|
||||
() => `${shareUrl}${process.env.basePath}/share/${id}/${encodeURIComponent(name)}`,
|
||||
[id, name],
|
||||
);
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
mutate(data, {
|
||||
onSuccess: async () => {
|
||||
onSave(data);
|
||||
ref.current.reset(data);
|
||||
},
|
||||
});
|
||||
};
|
||||
const url = `${hostUrl || location.origin}${
|
||||
process.env.basePath
|
||||
}/share/${id}/${encodeURIComponent(domain)}`;
|
||||
|
||||
const handleGenerate = () => {
|
||||
const id = generateId();
|
||||
ref.current.setValue('shareId', id, {
|
||||
shouldValidate: true,
|
||||
shouldDirty: true,
|
||||
});
|
||||
setId(id);
|
||||
setId(generateId());
|
||||
};
|
||||
|
||||
const handleCheck = (checked: boolean) => {
|
||||
const data = { shareId: checked ? generateId() : null };
|
||||
mutate(data, {
|
||||
onSuccess: async () => {
|
||||
onSave(data);
|
||||
onSave?.();
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
},
|
||||
});
|
||||
setId(data.shareId);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (id && id !== shareId) {
|
||||
ref.current.setValue('shareId', id);
|
||||
}
|
||||
}, [id, shareId]);
|
||||
const handleSave = () => {
|
||||
mutate(
|
||||
{ shareId: id },
|
||||
{
|
||||
onSuccess: async () => {
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
onSave?.();
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -70,7 +65,7 @@ export function ShareUrl({ websiteId, data, onSave }) {
|
||||
{formatMessage(labels.enableShareUrl)}
|
||||
</Toggle>
|
||||
{id && (
|
||||
<Form key={websiteId} ref={ref} onSubmit={handleSubmit} error={error} values={data}>
|
||||
<Form error={error}>
|
||||
<FormRow>
|
||||
<p>{formatMessage(messages.shareUrl)}</p>
|
||||
<Flexbox gap={10}>
|
||||
@ -79,7 +74,14 @@ export function ShareUrl({ websiteId, data, onSave }) {
|
||||
</Flexbox>
|
||||
</FormRow>
|
||||
<FormButtons>
|
||||
<SubmitButton variant="primary">{formatMessage(labels.save)}</SubmitButton>
|
||||
<LoadingButton
|
||||
variant="primary"
|
||||
disabled={id === shareId}
|
||||
isLoading={isPending}
|
||||
onClick={handleSave}
|
||||
>
|
||||
{formatMessage(labels.save)}
|
||||
</LoadingButton>
|
||||
</FormButtons>
|
||||
</Form>
|
||||
)}
|
||||
|
@ -1,23 +1,23 @@
|
||||
import { Button, Modal, ModalTrigger, ActionForm } from 'react-basics';
|
||||
import { Button, Modal, ModalTrigger, ActionForm, useToasts } from 'react-basics';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import WebsiteDeleteForm from './WebsiteDeleteForm';
|
||||
import WebsiteResetForm from './WebsiteResetForm';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import { touch } from 'store/cache';
|
||||
|
||||
export function WebsiteData({
|
||||
websiteId,
|
||||
onSave,
|
||||
}: {
|
||||
websiteId: string;
|
||||
onSave?: (value: string) => void;
|
||||
}) {
|
||||
export function WebsiteData({ websiteId, onSave }: { websiteId: string; onSave?: () => void }) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const router = useRouter();
|
||||
const { showToast } = useToasts();
|
||||
|
||||
const handleReset = async () => {
|
||||
onSave('reset');
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
onSave?.();
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
onSave('delete');
|
||||
touch('websites');
|
||||
router.push('/settings/websites');
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -1,6 +1,4 @@
|
||||
import { useApi, useMessages } from 'components/hooks';
|
||||
import { useContext } from 'react';
|
||||
import SettingsContext from '../../SettingsContext';
|
||||
import TypeConfirmationForm from 'components/common/TypeConfirmationForm';
|
||||
|
||||
const CONFIRM_VALUE = 'DELETE';
|
||||
@ -15,10 +13,9 @@ export function WebsiteDeleteForm({
|
||||
onClose?: () => void;
|
||||
}) {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { websitesUrl } = useContext(SettingsContext);
|
||||
const { del, useMutation } = useApi();
|
||||
const { mutate, isPending, error } = useMutation({
|
||||
mutationFn: (data: any) => del(`${websitesUrl}/${websiteId}`, data),
|
||||
mutationFn: (data: any) => del(`/websites/${websiteId}`, data),
|
||||
});
|
||||
|
||||
const handleConfirm = async () => {
|
||||
|
@ -1,40 +1,46 @@
|
||||
import { SubmitButton, Form, FormInput, FormRow, FormButtons, TextField } from 'react-basics';
|
||||
import { useContext, useRef } from 'react';
|
||||
import { useApi } from 'components/hooks';
|
||||
import { Website } from '@prisma/client';
|
||||
import { useRef } from 'react';
|
||||
import {
|
||||
SubmitButton,
|
||||
Form,
|
||||
FormInput,
|
||||
FormRow,
|
||||
FormButtons,
|
||||
TextField,
|
||||
useToasts,
|
||||
} from 'react-basics';
|
||||
import { useApi, useMessages } from 'components/hooks';
|
||||
import { DOMAIN_REGEX } from 'lib/constants';
|
||||
import { useMessages } from 'components/hooks';
|
||||
import SettingsContext from '../../SettingsContext';
|
||||
|
||||
export function WebsiteEditForm({
|
||||
websiteId,
|
||||
data,
|
||||
website,
|
||||
onSave,
|
||||
}: {
|
||||
websiteId: string;
|
||||
data: any[];
|
||||
website: Website;
|
||||
onSave?: (data: any) => void;
|
||||
}) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { websitesUrl } = useContext(SettingsContext);
|
||||
const { post, useMutation } = useApi();
|
||||
const { mutate, error } = useMutation({
|
||||
mutationFn: (data: any) => post(`${websitesUrl}/${websiteId}`, data),
|
||||
mutationFn: (data: any) => post(`/websites/${website.id}`, data),
|
||||
});
|
||||
const ref = useRef(null);
|
||||
const { showToast } = useToasts();
|
||||
|
||||
const handleSubmit = async (data: any) => {
|
||||
mutate(data, {
|
||||
onSuccess: async () => {
|
||||
showToast({ message: formatMessage(messages.saved), variant: 'success' });
|
||||
ref.current.reset(data);
|
||||
onSave(data);
|
||||
onSave?.(data);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Form ref={ref} onSubmit={handleSubmit} error={error} values={data}>
|
||||
<Form ref={ref} onSubmit={handleSubmit} error={error} values={website}>
|
||||
<FormRow label={formatMessage(labels.websiteId)}>
|
||||
<TextField value={websiteId} readOnly allowCopy />
|
||||
<TextField value={website.id} readOnly allowCopy />
|
||||
</FormRow>
|
||||
<FormRow label={formatMessage(labels.name)}>
|
||||
<FormInput name="name" rules={{ required: formatMessage(labels.required) }}>
|
||||
|
5
src/app/(main)/teams/[id]/websites/[websiteId]/page.tsx
Normal file
5
src/app/(main)/teams/[id]/websites/[websiteId]/page.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import WebsiteDetails from 'app/(main)/websites/[id]/WebsiteDetails';
|
||||
|
||||
export default function TeamWebsitePage({ params: { websiteId } }) {
|
||||
return <WebsiteDetails websiteId={websiteId} />;
|
||||
}
|
6
src/app/(main)/websites/[id]/WebsiteContext.tsx
Normal file
6
src/app/(main)/websites/[id]/WebsiteContext.tsx
Normal file
@ -0,0 +1,6 @@
|
||||
'use client';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export const WebsiteContext = createContext(null);
|
||||
|
||||
export default WebsiteContext;
|
@ -27,6 +27,11 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: var(--base700);
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
@ -1,16 +1,26 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Icon } from 'react-basics';
|
||||
import styles from './PageHeader.module.css';
|
||||
|
||||
export interface PageHeaderProps {
|
||||
export function PageHeader({
|
||||
title,
|
||||
icon,
|
||||
className,
|
||||
children,
|
||||
}: {
|
||||
title?: ReactNode;
|
||||
icon?: ReactNode;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export function PageHeader({ title, className, children }: PageHeaderProps) {
|
||||
}) {
|
||||
return (
|
||||
<div className={classNames(styles.header, className)}>
|
||||
{icon && (
|
||||
<Icon size="lg" className={styles.icon}>
|
||||
{icon}
|
||||
</Icon>
|
||||
)}
|
||||
{title && <div className={styles.title}>{title}</div>}
|
||||
<div className={styles.actions}>{children}</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user