Added SettingsContext.

This commit is contained in:
Mike Cao 2023-12-05 19:22:14 -08:00
parent 3f657d97b2
commit 4fca98d25d
22 changed files with 106 additions and 83 deletions

View File

@ -87,6 +87,7 @@ const config = {
defaultLocale: process.env.DEFAULT_LOCALE || '',
disableLogin: process.env.DISABLE_LOGIN || '',
disableUI: process.env.DISABLE_UI || '',
hostUrl: process.env.HOST_URL || '',
},
basePath,
output: 'standalone',

View File

@ -38,7 +38,7 @@ export function Dashboard() {
const { page } = params;
if (query.isLoading) {
return <Loading size="lg" />;
return <Loading />;
}
return (

View File

@ -0,0 +1,5 @@
import { createContext } from 'react';
export const SettingsContext = createContext(null);
export default SettingsContext;

View File

@ -4,6 +4,7 @@ import useUser from 'components/hooks/useUser';
import useMessages from 'components/hooks/useMessages';
import SideNav from 'components/layout/SideNav';
import styles from './layout.module.css';
import SettingsContext from './SettingsContext';
export default function SettingsLayout({ children }) {
const { user } = useUser();
@ -24,14 +25,26 @@ export default function SettingsLayout({ children }) {
return null;
}
const hostUrl = process.env.hostUrl || location.origin;
const config = {
settingsUrl: '/settings/websites',
hostUrl,
shareUrl: hostUrl,
trackingCodeUrl: hostUrl,
websitesUrl: `/websites`,
};
return (
<div className={styles.layout}>
{!cloudMode && (
<div className={styles.menu}>
<SideNav items={items} shallow={true} selectedKey={getKey()} />
</div>
)}
<div className={styles.content}>{children}</div>
</div>
<SettingsContext.Provider value={config}>
<div className={styles.layout}>
{!cloudMode && (
<div className={styles.menu}>
<SideNav items={items} shallow={true} selectedKey={getKey()} />
</div>
)}
<div className={styles.content}>{children}</div>
</div>
</SettingsContext.Provider>
);
}

View File

@ -22,7 +22,7 @@ export function UserAddButton({ onSave }: { onSave?: () => void }) {
<Text>{formatMessage(labels.createUser)}</Text>
</Button>
<Modal title={formatMessage(labels.createUser)}>
{close => <UserAddForm onSave={handleSave} onClose={close} />}
{(close: () => void) => <UserAddForm onSave={handleSave} onClose={close} />}
</Modal>
</ModalTrigger>
);

View File

@ -21,7 +21,7 @@ export function UserAddForm({ onSave, onClose }) {
});
const { formatMessage, labels } = useMessages();
const handleSubmit = async data => {
const handleSubmit = async (data: any) => {
mutate(data, {
onSuccess: async () => {
onSave(data);

View File

@ -24,7 +24,7 @@ export function UserDeleteButton({
<Text>{formatMessage(labels.delete)}</Text>
</Button>
<Modal title={formatMessage(labels.deleteUser)}>
{close => (
{(close: () => void) => (
<UserDeleteForm userId={userId} username={username} onSave={onDelete} onClose={close} />
)}
</Modal>

View File

@ -1,17 +1,17 @@
'use client';
import { useEffect, useState } from 'react';
import { Key, useEffect, useState } from 'react';
import { Item, Loading, Tabs, useToasts } from 'react-basics';
import UserEditForm from '../UserEditForm';
import PageHeader from 'components/layout/PageHeader';
import useApi from 'components/hooks/useApi';
import UserWebsites from '../UserWebsites';
import useMessages from 'components/hooks/useMessages';
import UserWebsites from '../UserWebsites';
export function UserSettings({ userId }) {
const { formatMessage, labels, messages } = useMessages();
const [edit, setEdit] = useState(false);
const [values, setValues] = useState(null);
const [tab, setTab] = useState('details');
const [tab, setTab] = useState<Key>('details');
const { get, useQuery } = useApi();
const { showToast } = useToasts();
const { data, isLoading } = useQuery({
@ -24,7 +24,7 @@ export function UserSettings({ userId }) {
gcTime: 0,
});
const handleSave = data => {
const handleSave = (data: any) => {
showToast({ message: formatMessage(messages.saved), variant: 'success' });
if (data) {
setValues(state => ({ ...state, ...data }));
@ -42,7 +42,7 @@ export function UserSettings({ userId }) {
}, [data]);
if (isLoading || !values) {
return <Loading size="lg" />;
return <Loading />;
}
return (

View File

@ -22,7 +22,7 @@ export function WebsiteAddButton({ onSave }: { onSave?: () => void }) {
<Text>{formatMessage(labels.addWebsite)}</Text>
</Button>
<Modal title={formatMessage(labels.addWebsite)}>
{close => <WebsiteAddForm onSave={handleSave} onClose={close} />}
{(close: () => void) => <WebsiteAddForm onSave={handleSave} onClose={close} />}
</Modal>
</ModalTrigger>
);

View File

@ -10,12 +10,15 @@ import {
import useApi from 'components/hooks/useApi';
import { DOMAIN_REGEX } from 'lib/constants';
import useMessages from 'components/hooks/useMessages';
import { useContext } from 'react';
import SettingsContext from '../SettingsContext';
export function WebsiteAddForm({ onSave, onClose }: { onSave?: () => void; onClose?: () => void }) {
const { formatMessage, labels, messages } = useMessages();
const { websitesUrl } = useContext(SettingsContext);
const { post, useMutation } = useApi();
const { mutate, error, isPending } = useMutation({
mutationFn: (data: any) => post('/websites', data),
mutationFn: (data: any) => post(websitesUrl, data),
});
const handleSubmit = async (data: any) => {

View File

@ -1,5 +1,5 @@
'use client';
import { useEffect, useState } from 'react';
import { useContext, useEffect, useState, Key } from 'react';
import { Item, Tabs, useToasts, Button, Text, Icon, Icons, Loading } from 'react-basics';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
@ -10,33 +10,35 @@ import TrackingCode from './[id]/TrackingCode';
import ShareUrl from './[id]/ShareUrl';
import useApi from 'components/hooks/useApi';
import useMessages from 'components/hooks/useMessages';
import SettingsContext from '../SettingsContext';
export function WebsiteSettings({ websiteId, openExternal = false, analyticsUrl }) {
export function WebsiteSettings({ websiteId, openExternal = false }) {
const router = useRouter();
const { formatMessage, labels, messages } = useMessages();
const { get, useQuery } = useApi();
const { showToast } = useToasts();
const { websitesUrl, settingsUrl } = useContext(SettingsContext);
const { data, isLoading } = useQuery({
queryKey: ['website', websiteId],
queryFn: () => get(`/websites/${websiteId}`),
queryFn: () => get(`${websitesUrl}/${websiteId}`),
enabled: !!websiteId,
gcTime: 0,
});
const [values, setValues] = useState(null);
const [tab, setTab] = useState('details');
const [tab, setTab] = useState<Key>('details');
const showSuccess = () => {
showToast({ message: formatMessage(messages.saved), variant: 'success' });
};
const handleSave = data => {
const handleSave = (data: any) => {
showSuccess();
setValues(state => ({ ...state, ...data }));
setValues((state: any) => ({ ...state, ...data }));
};
const handleReset = async value => {
const handleReset = async (value: string) => {
if (value === 'delete') {
router.push('/settings/websites');
router.push(settingsUrl);
} else if (value === 'reset') {
showSuccess();
}
@ -55,7 +57,7 @@ export function WebsiteSettings({ websiteId, openExternal = false, analyticsUrl
return (
<>
<PageHeader title={values?.name}>
<Link href={`/websites/${websiteId}`} target={openExternal ? '_blank' : null}>
<Link href={`${websitesUrl}/${websiteId}`} target={openExternal ? '_blank' : null}>
<Button variant="primary">
<Icon>
<Icons.External />
@ -73,15 +75,8 @@ export function WebsiteSettings({ websiteId, openExternal = false, analyticsUrl
{tab === 'details' && (
<WebsiteEditForm websiteId={websiteId} data={values} onSave={handleSave} />
)}
{tab === 'tracking' && <TrackingCode websiteId={websiteId} analyticsUrl={analyticsUrl} />}
{tab === 'share' && (
<ShareUrl
websiteId={websiteId}
data={values}
analyticsUrl={analyticsUrl}
onSave={handleSave}
/>
)}
{tab === 'tracking' && <TrackingCode websiteId={websiteId} />}
{tab === 'share' && <ShareUrl websiteId={websiteId} data={values} onSave={handleSave} />}
{tab === 'data' && <WebsiteData websiteId={websiteId} onSave={handleReset} />}
</>
);

View File

@ -1,10 +1,11 @@
'use client';
import { ReactNode } from 'react';
import { ReactNode, useContext } from 'react';
import WebsitesTable from 'app/(main)/settings/websites/WebsitesTable';
import useApi from 'components/hooks/useApi';
import DataTable from 'components/common/DataTable';
import useFilterQuery from 'components/hooks/useFilterQuery';
import useCache from 'store/cache';
import SettingsContext from '../SettingsContext';
export interface WebsitesDataTableProps {
userId: string;
@ -17,23 +18,6 @@ export interface WebsitesDataTableProps {
children?: ReactNode;
}
function useWebsites(userId: string, { includeTeams, onlyTeams }) {
const { get } = useApi();
const modified = useCache((state: any) => state?.websites);
return useFilterQuery({
queryKey: ['websites', { includeTeams, onlyTeams, modified }],
queryFn: (params: any) => {
return get(`/users/${userId}/websites`, {
includeTeams,
onlyTeams,
...params,
});
},
enabled: !!userId,
});
}
export function WebsitesDataTable({
userId,
allowEdit = true,
@ -44,7 +28,21 @@ export function WebsitesDataTable({
onlyTeams,
children,
}: WebsitesDataTableProps) {
const queryResult = useWebsites(userId, { includeTeams, onlyTeams });
const { get } = useApi();
const modified = useCache((state: any) => state?.websites);
const { websitesUrl } = useContext(SettingsContext);
const queryResult = useFilterQuery({
queryKey: ['websites', { includeTeams, onlyTeams, modified }],
queryFn: (params: any) => {
return get(websitesUrl, {
includeTeams,
onlyTeams,
...params,
});
},
enabled: !!userId,
});
return (
<DataTable queryResult={queryResult}>

View File

@ -1,8 +1,9 @@
import { ReactNode } from 'react';
import { ReactNode, useContext } from 'react';
import Link from 'next/link';
import { Button, Text, Icon, Icons, GridTable, GridColumn, useBreakpoint } from 'react-basics';
import useMessages from 'components/hooks/useMessages';
import useUser from 'components/hooks/useUser';
import SettingsContext from '../SettingsContext';
export interface WebsitesTableProps {
data: any[];
@ -24,6 +25,7 @@ export function WebsitesTable({
const { formatMessage, labels } = useMessages();
const { user } = useUser();
const breakpoint = useBreakpoint();
const { settingsUrl, websitesUrl } = useContext(SettingsContext);
return (
<GridTable data={data} cardMode={['xs', 'sm', 'md'].includes(breakpoint)}>
@ -50,7 +52,7 @@ export function WebsitesTable({
return (
<>
{allowEdit && (!showTeam || ownerId === user.id) && (
<Link href={`/settings/websites/${id}`}>
<Link href={`${settingsUrl}/${id}`}>
<Button>
<Icon>
<Icons.Edit />
@ -60,7 +62,7 @@ export function WebsitesTable({
</Link>
)}
{allowView && (
<Link href={`/websites/${id}`}>
<Link href={`${websitesUrl}/${id}`}>
<Button>
<Icon>
<Icons.External />

View File

@ -8,14 +8,15 @@ import {
Button,
Toggle,
} from 'react-basics';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
import { getRandomChars } from 'next-basics';
import useApi from 'components/hooks/useApi';
import useMessages from 'components/hooks/useMessages';
import SettingsContext from '../../SettingsContext';
const generateId = () => getRandomChars(16);
export function ShareUrl({ websiteId, data, analyticsUrl, onSave }) {
export function ShareUrl({ websiteId, data, onSave }) {
const { formatMessage, labels, messages } = useMessages();
const { name, shareId } = data;
const [id, setId] = useState(shareId);
@ -24,11 +25,9 @@ export function ShareUrl({ websiteId, data, analyticsUrl, onSave }) {
mutationFn: (data: any) => post(`/websites/${websiteId}`, data),
});
const ref = useRef(null);
const { shareUrl } = useContext(SettingsContext);
const url = useMemo(
() =>
`${analyticsUrl || location.origin}${process.env.basePath}/share/${id}/${encodeURIComponent(
name,
)}`,
() => `${shareUrl}${process.env.basePath}/share/${id}/${encodeURIComponent(name)}`,
[id, name],
);

View File

@ -1,23 +1,20 @@
import { TextArea } from 'react-basics';
import useMessages from 'components/hooks/useMessages';
import useConfig from 'components/hooks/useConfig';
import { useContext } from 'react';
import SettingsContext from '../../SettingsContext';
export function TrackingCode({
websiteId,
analyticsUrl,
}: {
websiteId: string;
analyticsUrl: string;
}) {
export function TrackingCode({ websiteId }: { websiteId: string }) {
const { formatMessage, messages } = useMessages();
const config = useConfig();
const { trackingCodeUrl } = useContext(SettingsContext);
const trackerScriptName =
config?.trackerScriptName?.split(',')?.map(n => n.trim())?.[0] || 'script.js';
const url = trackerScriptName?.startsWith('http')
? trackerScriptName
: `${analyticsUrl || location.origin}${process.env.basePath}/${trackerScriptName}`;
: `${trackingCodeUrl}${process.env.basePath}/${trackerScriptName}`;
const code = `<script async src="${url}" data-website-id="${websiteId}"></script>`;

View File

@ -29,7 +29,7 @@ export function WebsiteData({
<ModalTrigger>
<Button variant="secondary">{formatMessage(labels.reset)}</Button>
<Modal title={formatMessage(labels.resetWebsite)}>
{close => (
{(close: () => void) => (
<WebsiteResetForm websiteId={websiteId} onSave={handleReset} onClose={close} />
)}
</Modal>
@ -42,7 +42,7 @@ export function WebsiteData({
<ModalTrigger>
<Button variant="danger">{formatMessage(labels.delete)}</Button>
<Modal title={formatMessage(labels.deleteWebsite)}>
{close => (
{(close: () => void) => (
<WebsiteDeleteForm websiteId={websiteId} onSave={handleDelete} onClose={close} />
)}
</Modal>

View File

@ -9,6 +9,8 @@ import {
} from 'react-basics';
import useApi from 'components/hooks/useApi';
import useMessages from 'components/hooks/useMessages';
import { useContext } from 'react';
import SettingsContext from '../../SettingsContext';
const CONFIRM_VALUE = 'DELETE';
@ -22,12 +24,13 @@ export function WebsiteDeleteForm({
onClose?: () => void;
}) {
const { formatMessage, labels, messages, FormattedMessage } = useMessages();
const { websitesUrl } = useContext(SettingsContext);
const { del, useMutation } = useApi();
const { mutate, error } = useMutation({
mutationFn: (data: any) => del(`/websites/${websiteId}`, data),
mutationFn: (data: any) => del(`${websitesUrl}/${websiteId}`, data),
});
const handleSubmit = async data => {
const handleSubmit = async (data: any) => {
mutate(data, {
onSuccess: async () => {
onSave();

View File

@ -1,8 +1,9 @@
import { SubmitButton, Form, FormInput, FormRow, FormButtons, TextField } from 'react-basics';
import { useRef } from 'react';
import { useContext, useRef } from 'react';
import useApi from 'components/hooks/useApi';
import { DOMAIN_REGEX } from 'lib/constants';
import useMessages from 'components/hooks/useMessages';
import SettingsContext from '../../SettingsContext';
export function WebsiteEditForm({
websiteId,
@ -14,13 +15,14 @@ export function WebsiteEditForm({
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(`/websites/${websiteId}`, data),
mutationFn: (data: any) => post(`${websitesUrl}/${websiteId}`, data),
});
const ref = useRef(null);
const handleSubmit = async data => {
const handleSubmit = async (data: any) => {
mutate(data, {
onSuccess: async () => {
ref.current.reset(data);

View File

@ -9,6 +9,8 @@ import {
} from 'react-basics';
import useApi from 'components/hooks/useApi';
import useMessages from 'components/hooks/useMessages';
import { useContext } from 'react';
import SettingsContext from '../../SettingsContext';
const CONFIRM_VALUE = 'RESET';
@ -22,9 +24,10 @@ export function WebsiteResetForm({
onClose?: () => void;
}) {
const { formatMessage, labels, messages, FormattedMessage } = useMessages();
const { websitesUrl } = useContext(SettingsContext);
const { post, useMutation } = useApi();
const { mutate, error } = useMutation({
mutationFn: (data: any) => post(`/websites/${websiteId}/reset`, data),
mutationFn: (data: any) => post(`${websitesUrl}/${websiteId}/reset`, data),
});
const handleSubmit = async (data: any) => {

View File

@ -18,5 +18,5 @@ export default function SSOPage() {
}
}, [router, url, token]);
return <Loading size="xl" />;
return <Loading />;
}

View File

@ -23,7 +23,7 @@ export function Page({
}
if (isLoading) {
return <Loading icon="spinner" size="xl" position="page" />;
return <Loading position="page" />;
}
return <div className={classNames(styles.page, className)}>{children}</div>;

View File

@ -48,6 +48,8 @@ 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)/settings/SettingsContext';
export * from 'components/common/ConfirmDeleteForm';
export * from 'components/common/DataTable';
export * from 'components/common/Empty';