mirror of
https://github.com/kremalicious/umami.git
synced 2025-02-01 12:29:35 +01:00
Added SettingsContext.
This commit is contained in:
parent
3f657d97b2
commit
4fca98d25d
@ -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',
|
||||
|
@ -38,7 +38,7 @@ export function Dashboard() {
|
||||
const { page } = params;
|
||||
|
||||
if (query.isLoading) {
|
||||
return <Loading size="lg" />;
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
5
src/app/(main)/settings/SettingsContext.tsx
Normal file
5
src/app/(main)/settings/SettingsContext.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export const SettingsContext = createContext(null);
|
||||
|
||||
export default SettingsContext;
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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 (
|
@ -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>
|
||||
);
|
||||
|
@ -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) => {
|
||||
|
@ -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} />}
|
||||
</>
|
||||
);
|
@ -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}>
|
||||
|
@ -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 />
|
||||
|
@ -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],
|
||||
);
|
||||
|
||||
|
@ -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>`;
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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) => {
|
||||
|
@ -18,5 +18,5 @@ export default function SSOPage() {
|
||||
}
|
||||
}, [router, url, token]);
|
||||
|
||||
return <Loading size="xl" />;
|
||||
return <Loading />;
|
||||
}
|
||||
|
@ -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>;
|
||||
|
@ -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';
|
||||
|
Loading…
Reference in New Issue
Block a user