Added website and team providers.

This commit is contained in:
Mike Cao 2024-02-04 19:53:06 -08:00
parent dbb3801e66
commit cc273092d5
25 changed files with 123 additions and 98 deletions

View File

@ -1,7 +1,7 @@
'use client';
import { Button, Icon, Icons, Modal, ModalTrigger, Text } from 'react-basics';
import { useApi, useMessages } from 'components/hooks';
import { touch } from 'store/cache';
import { touch } from 'store/modified';
import ConfirmationForm from 'components/common/ConfirmationForm';
export function ReportDeleteButton({

View File

@ -8,7 +8,7 @@ import {
Button,
SubmitButton,
} from 'react-basics';
import { touch } from 'store/cache';
import { touch } from 'store/modified';
import { useApi, useMessages } from 'components/hooks';
export function TeamAddForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) {

View File

@ -1,6 +1,6 @@
'use client';
import { useApi, useMessages } from 'components/hooks';
import { touch } from 'store/cache';
import { touch } from 'store/modified';
import TypeConfirmationForm from 'components/common/TypeConfirmationForm';
const CONFIRM_VALUE = 'DELETE';

View File

@ -10,7 +10,7 @@ import {
SubmitButton,
} from 'react-basics';
import { useApi, useMessages } from 'components/hooks';
import { touch } from 'store/cache';
import { touch } from 'store/modified';
export function TeamJoinForm({ onSave, onClose }: { onSave: () => void; onClose: () => void }) {
const { formatMessage, labels, getMessage } = useMessages();

View File

@ -1,6 +1,6 @@
'use client';
import { useApi, useMessages } from 'components/hooks';
import { touch } from 'store/cache';
import { touch } from 'store/modified';
import ConfirmationForm from 'components/common/ConfirmationForm';
export function TeamLeaveForm({

View File

@ -1,7 +1,7 @@
'use client';
import { useApi, useMessages } from 'components/hooks';
import { Icon, Icons, LoadingButton, Text } from 'react-basics';
import { touch } from 'store/cache';
import { touch } from 'store/modified';
export function TeamMemberRemoveButton({
teamId,

View File

@ -2,7 +2,7 @@
import { Button, Icon, Text, Modal, Icons, ModalTrigger, useToasts } from 'react-basics';
import UserAddForm from './UserAddForm';
import { useMessages } from 'components/hooks';
import { touch } from 'store/cache';
import { touch } from 'store/modified';
export function UserAddButton({ onSave }: { onSave?: () => void }) {
const { formatMessage, labels, messages } = useMessages();

View File

@ -1,7 +1,7 @@
'use client';
import { useApi, useMessages } from 'components/hooks';
import ConfirmationForm from 'components/common/ConfirmationForm';
import { touch } from 'store/cache';
import { touch } from 'store/modified';
export function UserDeleteForm({ userId, username, onSave, onClose }) {
const { FormattedMessage, messages, labels, formatMessage } = useMessages();

View File

@ -2,7 +2,7 @@
import { Button, Icon, Icons, Modal, ModalTrigger, Text, useToasts } from 'react-basics';
import WebsiteAddForm from './WebsiteAddForm';
import { useMessages } from 'components/hooks';
import { touch } from 'store/cache';
import { touch } from 'store/modified';
export function WebsiteAddButton({ teamId, onSave }: { teamId: string; onSave?: () => void }) {
const { formatMessage, labels, messages } = useMessages();

View File

@ -1,5 +1,4 @@
'use client';
import { Website } from '@prisma/client';
import {
Form,
FormRow,
@ -11,21 +10,22 @@ import {
LoadingButton,
useToasts,
} from 'react-basics';
import { useState } from 'react';
import { useContext, useState } from 'react';
import { getRandomChars } from 'next-basics';
import { useApi, useMessages } from 'components/hooks';
import { WebsiteContext } from 'app/(main)/websites/[websiteId]/WebsiteProvider';
import { touch } from 'store/modified';
const generateId = () => getRandomChars(16);
export function ShareUrl({
website,
hostUrl,
onSave,
}: {
website: Website;
websiteId: string;
hostUrl?: string;
onSave?: () => void;
}) {
const website = useContext(WebsiteContext);
const { domain, shareId } = website;
const { formatMessage, labels, messages } = useMessages();
const [id, setId] = useState(shareId);
@ -47,7 +47,6 @@ export function ShareUrl({
const data = { shareId: checked ? generateId() : null };
mutate(data, {
onSuccess: async () => {
onSave?.();
showToast({ message: formatMessage(messages.saved), variant: 'success' });
},
});
@ -60,7 +59,7 @@ export function ShareUrl({
{
onSuccess: async () => {
showToast({ message: formatMessage(messages.saved), variant: 'success' });
onSave?.();
touch(`website:${website?.id}`);
},
},
);

View File

@ -4,7 +4,7 @@ import { useRouter } from 'next/navigation';
import { useMessages } from 'components/hooks';
import WebsiteDeleteForm from './WebsiteDeleteForm';
import WebsiteResetForm from './WebsiteResetForm';
import { touch } from 'store/cache';
import { touch } from 'store/modified';
export function WebsiteData({ websiteId, onSave }: { websiteId: string; onSave?: () => void }) {
const { formatMessage, labels, messages } = useMessages();

View File

@ -1,6 +1,5 @@
'use client';
import { Website } from '@prisma/client';
import { useRef } from 'react';
import { useContext, useRef } from 'react';
import {
SubmitButton,
Form,
@ -12,18 +11,20 @@ import {
} from 'react-basics';
import { useApi, useMessages } from 'components/hooks';
import { DOMAIN_REGEX } from 'lib/constants';
import { touch } from 'store/modified';
import { WebsiteContext } from 'app/(main)/websites/[websiteId]/WebsiteProvider';
export function WebsiteEditForm({
website,
onSave,
websiteId,
}: {
website: Website;
websiteId: string;
onSave?: (data: any) => void;
}) {
const website = useContext(WebsiteContext);
const { formatMessage, labels, messages } = useMessages();
const { post, useMutation } = useApi();
const { mutate, error } = useMutation({
mutationFn: (data: any) => post(`/websites/${website.id}`, data),
mutationFn: (data: any) => post(`/websites/${websiteId}`, data),
});
const ref = useRef(null);
const { showToast } = useToasts();
@ -33,7 +34,7 @@ export function WebsiteEditForm({
onSuccess: async () => {
showToast({ message: formatMessage(messages.saved), variant: 'success' });
ref.current.reset(data);
onSave?.(data);
touch(`website:${website?.id}`);
},
});
};
@ -41,7 +42,7 @@ export function WebsiteEditForm({
return (
<Form ref={ref} onSubmit={handleSubmit} error={error} values={website}>
<FormRow label={formatMessage(labels.websiteId)}>
<TextField value={website.id} readOnly allowCopy />
<TextField value={website?.id} readOnly allowCopy />
</FormRow>
<FormRow label={formatMessage(labels.name)}>
<FormInput name="name" rules={{ required: formatMessage(labels.required) }}>

View File

@ -1,31 +1,23 @@
'use client';
import { useState, Key } from 'react';
import { Item, Tabs, Button, Text, Icon, Loading } from 'react-basics';
import { useState, Key, useContext } from 'react';
import { Item, Tabs, Button, Text, Icon } from 'react-basics';
import Link from 'next/link';
import Icons from 'components/icons';
import PageHeader from 'components/layout/PageHeader';
import WebsiteContext from 'app/(main)/websites/[websiteId]/WebsiteContext';
import WebsiteEditForm from './[websiteId]/WebsiteEditForm';
import WebsiteData from './[websiteId]/WebsiteData';
import TrackingCode from './[websiteId]/TrackingCode';
import ShareUrl from './[websiteId]/ShareUrl';
import { useWebsite, useMessages } from 'components/hooks';
import WebsiteEditForm from './WebsiteEditForm';
import WebsiteData from './WebsiteData';
import TrackingCode from './TrackingCode';
import ShareUrl from './ShareUrl';
import { useMessages } from 'components/hooks';
import { WebsiteContext } from 'app/(main)/websites/[websiteId]/WebsiteProvider';
export function WebsiteSettings({ websiteId, openExternal = false }) {
const website = useContext(WebsiteContext);
const { formatMessage, labels } = useMessages();
const { data: website, isLoading, refetch } = useWebsite(websiteId);
const [tab, setTab] = useState<Key>('details');
const handleSave = () => {
refetch();
};
if (isLoading) {
return <Loading position="page" />;
}
return (
<WebsiteContext.Provider value={website}>
<>
<PageHeader title={website?.name} icon={<Icons.Globe />}>
<Link href={`/websites/${websiteId}`} target={openExternal ? '_blank' : null}>
<Button variant="primary">
@ -42,11 +34,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 website={website} onSave={handleSave} />}
{tab === 'details' && <WebsiteEditForm websiteId={websiteId} />}
{tab === 'tracking' && <TrackingCode websiteId={websiteId} />}
{tab === 'share' && <ShareUrl website={website} onSave={handleSave} />}
{tab === 'share' && <ShareUrl websiteId={websiteId} />}
{tab === 'data' && <WebsiteData websiteId={websiteId} />}
</WebsiteContext.Provider>
</>
);
}

View File

@ -1,5 +1,10 @@
import WebsiteSettings from '../WebsiteSettings';
import WebsiteProvider from 'app/(main)/websites/[websiteId]/WebsiteProvider';
import WebsiteSettings from './WebsiteSettings';
export default async function WebsiteSettingsPage({ params: { websiteId } }) {
return <WebsiteSettings websiteId={websiteId} />;
return (
<WebsiteProvider websiteId={websiteId}>
<WebsiteSettings websiteId={websiteId} />
</WebsiteProvider>
);
}

View File

@ -1,21 +0,0 @@
'use client';
import { useTeam, useTeamUrl } from 'components/hooks';
import { Loading } from 'react-basics';
import TeamContext from './TeamContext';
export function Team({ children }) {
const { teamId } = useTeamUrl();
const { data: team, isLoading } = useTeam(teamId);
if (isLoading) {
return <Loading />;
}
if (!team) {
return null;
}
return <TeamContext.Provider value={team}>{children}</TeamContext.Provider>;
}
export default Team;

View File

@ -1,6 +0,0 @@
'use client';
import { createContext } from 'react';
export const TeamContext = createContext(null);
export default TeamContext;

View File

@ -0,0 +1,30 @@
'use client';
import { createContext, ReactNode, useEffect } from 'react';
import { useTeam } from 'components/hooks';
import { Loading } from 'react-basics';
import useModified from 'store/modified';
export const TeamContext = createContext(null);
export function TeamProvider({ teamId, children }: { teamId: string; children: ReactNode }) {
const modified = useModified(state => state?.[`team:${teamId}`]);
const { data: team, isLoading, isFetching, refetch } = useTeam(teamId);
useEffect(() => {
if (modified) {
refetch();
}
}, [modified]);
if (isFetching && isLoading) {
return <Loading />;
}
if (!team) {
return null;
}
return <TeamContext.Provider value={team}>{children}</TeamContext.Provider>;
}
export default TeamProvider;

View File

@ -1,5 +1,5 @@
import Team from './Team';
import TeamProvider from './TeamProvider';
export default function ({ children }) {
return <Team>{children}</Team>;
return <TeamProvider>{children}</TeamProvider>;
}

View File

@ -1,6 +0,0 @@
'use client';
import { createContext } from 'react';
export const WebsiteContext = createContext(null);
export default WebsiteContext;

View File

@ -0,0 +1,32 @@
'use client';
import { createContext, ReactNode, useEffect } from 'react';
import { useWebsite } from 'components/hooks';
import { Loading } from 'react-basics';
import useModified from 'store/modified';
export const WebsiteContext = createContext(null);
export function WebsiteProvider({
websiteId,
children,
}: {
websiteId: string;
children: ReactNode;
}) {
const modified = useModified(state => state?.[`website:${websiteId}`]);
const { data: website, isFetching, isLoading, refetch } = useWebsite(websiteId);
useEffect(() => {
if (modified) {
refetch();
}
}, [modified]);
if (isFetching && isLoading) {
return <Loading position="page" />;
}
return <WebsiteContext.Provider value={website}>{children}</WebsiteContext.Provider>;
}
export default WebsiteProvider;

View File

@ -5,7 +5,7 @@ export function useWebsite(websiteId: string, options?: { [key: string]: any })
const { get, useQuery } = useApi();
return useQuery({
queryKey: ['websites', { websiteId }],
queryKey: ['website', { websiteId }],
queryFn: () => get(`/websites/${websiteId}`),
enabled: !!websiteId,
...options,

View File

@ -22,13 +22,16 @@ export * from 'app/(main)/settings/websites/[websiteId]/TrackingCode';
export * from 'app/(main)/settings/websites/[websiteId]/WebsiteDeleteForm';
export * from 'app/(main)/settings/websites/[websiteId]/WebsiteEditForm';
export * from 'app/(main)/settings/websites/[websiteId]/WebsiteResetForm';
export * from 'app/(main)/settings/websites/[websiteId]/WebsiteSettings';
export * from 'app/(main)/settings/websites/WebsiteAddForm';
export * from 'app/(main)/settings/websites/WebsitesHeader';
export * from 'app/(main)/settings/websites/WebsiteSettings';
export * from 'app/(main)/settings/websites/WebsitesDataTable';
export * from 'app/(main)/settings/websites/WebsitesTable';
export * from 'app/(main)/teams/[teamId]/TeamProvider';
export * from 'app/(main)/websites/[websiteId]/WebsiteProvider';
export * from 'components/common/TypeConfirmationForm';
export * from 'components/common/DataTable';
export * from 'components/common/Empty';

View File

@ -57,17 +57,17 @@ export default async (
const { name, domain, shareId } = req.body;
let website;
try {
website = await updateWebsite(websiteId, { name, domain, shareId });
const website = await updateWebsite(websiteId, { name, domain, shareId });
return ok(res, website);
} catch (e: any) {
if (e.message.includes('Unique constraint') && e.message.includes('share_id')) {
return serverError(res, 'That share ID is already taken.');
}
}
return ok(res, website);
return serverError(res, e);
}
}
if (req.method === 'DELETE') {

View File

@ -124,7 +124,7 @@ export async function updateWebsite(
): Promise<Website> {
return prisma.client.website.update({
where: {
websiteId,
id: websiteId,
},
data,
});
@ -146,7 +146,7 @@ export async function resetWebsite(
where: { websiteId },
}),
client.website.update({
where: { websiteId },
where: { id: websiteId },
data: {
resetAt: new Date(),
},
@ -186,10 +186,10 @@ export async function deleteWebsite(
data: {
deletedAt: new Date(),
},
where: { websiteId },
where: { id: websiteId },
})
: client.website.delete({
where: { websiteId },
where: { id: websiteId },
}),
]).then(async data => {
if (cache.enabled) {

View File

@ -6,8 +6,4 @@ export function setValue(key: string, value: any) {
store.setState({ [key]: value });
}
export function touch(key: string) {
setValue(key, Date.now());
}
export default store;