Added useFetch hook. Updated database check.

This commit is contained in:
Mike Cao 2020-08-30 15:29:31 -07:00
parent 7a81dda7b6
commit d0ca0819c6
14 changed files with 146 additions and 237 deletions

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import WebsiteChart from 'components/metrics/WebsiteChart'; import WebsiteChart from 'components/metrics/WebsiteChart';
import WorldMap from 'components/common/WorldMap'; import WorldMap from 'components/common/WorldMap';
@ -7,7 +7,6 @@ import WebsiteHeader from 'components/metrics/WebsiteHeader';
import MenuLayout from 'components/layout/MenuLayout'; import MenuLayout from 'components/layout/MenuLayout';
import Button from 'components/common/Button'; import Button from 'components/common/Button';
import { getDateRange } from 'lib/date'; import { getDateRange } from 'lib/date';
import { get } from 'lib/web';
import Arrow from 'assets/arrow-right.svg'; import Arrow from 'assets/arrow-right.svg';
import styles from './WebsiteDetails.module.css'; import styles from './WebsiteDetails.module.css';
import PagesTable from './metrics/PagesTable'; import PagesTable from './metrics/PagesTable';
@ -18,15 +17,16 @@ import DevicesTable from './metrics/DevicesTable';
import CountriesTable from './metrics/CountriesTable'; import CountriesTable from './metrics/CountriesTable';
import EventsTable from './metrics/EventsTable'; import EventsTable from './metrics/EventsTable';
import EventsChart from './metrics/EventsChart'; import EventsChart from './metrics/EventsChart';
import useFetch from '../hooks/useFetch';
export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' }) { export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' }) {
const [data, setData] = useState();
const [chartLoaded, setChartLoaded] = useState(false); const [chartLoaded, setChartLoaded] = useState(false);
const [countryData, setCountryData] = useState(); const [countryData, setCountryData] = useState();
const [eventsData, setEventsData] = useState(); const [eventsData, setEventsData] = useState();
const [dateRange, setDateRange] = useState(getDateRange(defaultDateRange));
const [expand, setExpand] = useState(); const [expand, setExpand] = useState();
const [dateRange, setDateRange] = useState(getDateRange(defaultDateRange));
const { startDate, endDate, unit } = dateRange; const { startDate, endDate, unit } = dateRange;
const { data } = useFetch(`/api/website/${websiteId}`, { websiteId });
const BackButton = () => ( const BackButton = () => (
<Button <Button
@ -76,10 +76,6 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
return menuOptions.find(e => e.value === value); return menuOptions.find(e => e.value === value);
} }
async function loadData() {
setData(await get(`/api/website/${websiteId}`));
}
function handleDataLoad() { function handleDataLoad() {
if (!chartLoaded) setTimeout(() => setChartLoaded(true), 300); if (!chartLoaded) setTimeout(() => setChartLoaded(true), 300);
} }
@ -96,12 +92,6 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
setExpand(getSelectedMenuOption(value)); setExpand(getSelectedMenuOption(value));
} }
useEffect(() => {
if (websiteId) {
loadData();
}
}, [websiteId]);
if (!data) { if (!data) {
return null; return null;
} }

View File

@ -1,25 +1,17 @@
import React, { useState, useEffect } from 'react'; import React from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import WebsiteHeader from 'components/metrics/WebsiteHeader'; import WebsiteHeader from 'components/metrics/WebsiteHeader';
import WebsiteChart from 'components/metrics/WebsiteChart'; import WebsiteChart from 'components/metrics/WebsiteChart';
import Page from 'components/layout/Page'; import Page from 'components/layout/Page';
import Button from 'components/common/Button'; import Button from 'components/common/Button';
import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
import useFetch from 'hooks/useFetch';
import Arrow from 'assets/arrow-right.svg'; import Arrow from 'assets/arrow-right.svg';
import { get } from 'lib/web';
import styles from './WebsiteList.module.css'; import styles from './WebsiteList.module.css';
export default function WebsiteList() { export default function WebsiteList() {
const [data, setData] = useState();
const router = useRouter(); const router = useRouter();
const { data } = useFetch('/api/websites');
async function loadData() {
setData(await get(`/api/websites`));
}
useEffect(() => {
loadData();
}, []);
if (!data) { if (!data) {
return null; return null;

View File

@ -1,32 +1,20 @@
import React, { useState, useEffect } from 'react'; import React, { useMemo } from 'react';
import { useSpring, animated } from 'react-spring'; import { useSpring, animated } from 'react-spring';
import classNames from 'classnames'; import classNames from 'classnames';
import { get } from 'lib/web'; import useFetch from 'hooks/useFetch';
import styles from './ActiveUsers.module.css'; import styles from './ActiveUsers.module.css';
export default function ActiveUsers({ websiteId, className }) { export default function ActiveUsers({ websiteId, className }) {
const [count, setCount] = useState(0); const { data } = useFetch(`/api/website/${websiteId}/active`, {}, { interval: 60000 });
const count = useMemo(() => {
async function loadData() { return data?.[0]?.x || 0;
const result = await get(`/api/website/${websiteId}/active`); }, [data]);
setCount(result?.[0]?.x);
}
const props = useSpring({ const props = useSpring({
x: count, x: count,
from: { x: 0 }, from: { x: 0 },
}); });
useEffect(() => {
loadData();
const id = setInterval(() => loadData(), 60000);
return () => {
clearInterval(id);
};
}, []);
if (count === 0) { if (count === 0) {
return null; return null;
} }

View File

@ -1,10 +1,8 @@
import React, { useState, useEffect, useMemo } from 'react'; import React, { useMemo } from 'react';
import classNames from 'classnames';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import BarChart from './BarChart'; import BarChart from './BarChart';
import { get } from 'lib/web';
import { getTimezone, getDateArray, getDateLength } from 'lib/date'; import { getTimezone, getDateArray, getDateLength } from 'lib/date';
import styles from './BarChart.module.css'; import useFetch from 'hooks/useFetch';
const COLORS = [ const COLORS = [
'#2680eb', '#2680eb',
@ -18,31 +16,15 @@ const COLORS = [
]; ];
export default function EventsChart({ websiteId, startDate, endDate, unit }) { export default function EventsChart({ websiteId, startDate, endDate, unit }) {
const [data, setData] = useState(); const { data } = useFetch(`/api/website/${websiteId}/events`, {
start_at: +startDate,
end_at: +endDate,
unit,
tz: getTimezone(),
});
const datasets = useMemo(() => { const datasets = useMemo(() => {
if (!data) return []; if (!data) return [];
return Object.keys(data).map((key, index) => {
const color = tinycolor(COLORS[index]);
return {
label: key,
data: data[key],
lineTension: 0,
backgroundColor: color.setAlpha(0.4).toRgbString(),
borderColor: color.setAlpha(0.5).toRgbString(),
borderWidth: 1,
};
});
}, [data]);
async function loadData() {
const data = await get(`/api/website/${websiteId}/events`, {
start_at: +startDate,
end_at: +endDate,
unit,
tz: getTimezone(),
});
const map = data.reduce((obj, { x, t, y }) => { const map = data.reduce((obj, { x, t, y }) => {
if (!obj[x]) { if (!obj[x]) {
obj[x] = []; obj[x] = [];
@ -57,8 +39,18 @@ export default function EventsChart({ websiteId, startDate, endDate, unit }) {
map[key] = getDateArray(map[key], startDate, endDate, unit); map[key] = getDateArray(map[key], startDate, endDate, unit);
}); });
setData(map); return Object.keys(map).map((key, index) => {
} const color = tinycolor(COLORS[index]);
return {
label: key,
data: map[key],
lineTension: 0,
backgroundColor: color.setAlpha(0.4).toRgbString(),
borderColor: color.setAlpha(0.5).toRgbString(),
borderWidth: 1,
};
});
}, [data]);
function handleCreate(options) { function handleCreate(options) {
const legend = { const legend = {
@ -74,10 +66,6 @@ export default function EventsChart({ websiteId, startDate, endDate, unit }) {
chart.update(); chart.update();
} }
useEffect(() => {
loadData();
}, [websiteId, startDate, endDate]);
if (!data) { if (!data) {
return null; return null;
} }

View File

@ -1,33 +1,28 @@
import React, { useState, useEffect } from 'react'; import React, { useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import MetricCard from './MetricCard'; import MetricCard from './MetricCard';
import { get } from 'lib/web';
import { formatShortTime, formatNumber, formatLongNumber } from 'lib/format'; import { formatShortTime, formatNumber, formatLongNumber } from 'lib/format';
import useFetch from 'hooks/useFetch';
import styles from './MetricsBar.module.css'; import styles from './MetricsBar.module.css';
export default function MetricsBar({ websiteId, startDate, endDate, className }) { export default function MetricsBar({ websiteId, startDate, endDate, className }) {
const [data, setData] = useState({}); const { data } = useFetch(`/api/website/${websiteId}/metrics`, {
start_at: +startDate,
end_at: +endDate,
});
const [format, setFormat] = useState(true); const [format, setFormat] = useState(true);
const { pageviews, uniques, bounces, totaltime } = data;
const formatFunc = format ? formatLongNumber : formatNumber; const formatFunc = format ? formatLongNumber : formatNumber;
async function loadData() {
setData(
await get(`/api/website/${websiteId}/metrics`, {
start_at: +startDate,
end_at: +endDate,
}),
);
}
function handleSetFormat() { function handleSetFormat() {
setFormat(state => !state); setFormat(state => !state);
} }
useEffect(() => { if (!data) {
loadData(); return null;
}, [websiteId, startDate, endDate]); }
const { pageviews, uniques, bounces, totaltime } = data;
return ( return (
<div className={classNames(styles.bar, className)} onClick={handleSetFormat}> <div className={classNames(styles.bar, className)} onClick={handleSetFormat}>

View File

@ -1,14 +1,14 @@
import React, { useState, useEffect, useMemo } from 'react'; import React, { useState, useMemo } from 'react';
import { FixedSizeList } from 'react-window'; import { FixedSizeList } from 'react-window';
import { useSpring, animated, config } from 'react-spring'; import { useSpring, animated, config } from 'react-spring';
import classNames from 'classnames'; import classNames from 'classnames';
import Button from 'components/common/Button'; import Button from 'components/common/Button';
import Loading from 'components/common/Loading';
import useFetch from 'hooks/useFetch';
import Arrow from 'assets/arrow-right.svg'; import Arrow from 'assets/arrow-right.svg';
import { get } from 'lib/web';
import { percentFilter } from 'lib/filters'; import { percentFilter } from 'lib/filters';
import { formatNumber, formatLongNumber } from 'lib/format'; import { formatNumber, formatLongNumber } from 'lib/format';
import styles from './MetricsTable.module.css'; import styles from './MetricsTable.module.css';
import Loading from '../common/Loading';
export default function MetricsTable({ export default function MetricsTable({
title, title,
@ -27,7 +27,16 @@ export default function MetricsTable({
onDataLoad = () => {}, onDataLoad = () => {},
onExpand = () => {}, onExpand = () => {},
}) { }) {
const [data, setData] = useState(); const { data } = useFetch(
`/api/website/${websiteId}/rankings`,
{
type,
start_at: +startDate,
end_at: +endDate,
domain: websiteDomain,
},
{ onDataLoad },
);
const [format, setFormat] = useState(true); const [format, setFormat] = useState(true);
const formatFunc = format ? formatLongNumber : formatNumber; const formatFunc = format ? formatLongNumber : formatNumber;
const shouldAnimate = limit > 0; const shouldAnimate = limit > 0;
@ -43,18 +52,6 @@ export default function MetricsTable({
return []; return [];
}, [data, dataFilter, filterOptions]); }, [data, dataFilter, filterOptions]);
async function loadData() {
const data = await get(`/api/website/${websiteId}/rankings`, {
type,
start_at: +startDate,
end_at: +endDate,
domain: websiteDomain,
});
setData(data);
onDataLoad(data);
}
const handleSetFormat = () => setFormat(state => !state); const handleSetFormat = () => setFormat(state => !state);
const getRow = row => { const getRow = row => {
@ -76,12 +73,6 @@ export default function MetricsTable({
return <div style={style}>{getRow(rankings[index])}</div>; return <div style={style}>{getRow(rankings[index])}</div>;
}; };
useEffect(() => {
if (websiteId) {
loadData();
}
}, [websiteId, startDate, endDate, type]);
return ( return (
<div className={classNames(styles.container, className)}> <div className={classNames(styles.container, className)}>
{data ? ( {data ? (

View File

@ -1,11 +1,11 @@
import React, { useState, useEffect, useMemo } from 'react'; import React, { useState, useMemo } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import PageviewsChart from './PageviewsChart'; import PageviewsChart from './PageviewsChart';
import MetricsBar from './MetricsBar'; import MetricsBar from './MetricsBar';
import QuickButtons from './QuickButtons'; import QuickButtons from './QuickButtons';
import DateFilter from '../common/DateFilter'; import DateFilter from 'components/common/DateFilter';
import StickyHeader from '../helpers/StickyHeader'; import StickyHeader from 'components/helpers/StickyHeader';
import { get } from 'lib/web'; import useFetch from 'hooks/useFetch';
import { getDateArray, getDateRange, getTimezone } from 'lib/date'; import { getDateArray, getDateRange, getTimezone } from 'lib/date';
import styles from './WebsiteChart.module.css'; import styles from './WebsiteChart.module.css';
@ -16,9 +16,18 @@ export default function WebsiteChart({
onDataLoad = () => {}, onDataLoad = () => {},
onDateChange = () => {}, onDateChange = () => {},
}) { }) {
const [data, setData] = useState();
const [dateRange, setDateRange] = useState(getDateRange(defaultDateRange)); const [dateRange, setDateRange] = useState(getDateRange(defaultDateRange));
const { startDate, endDate, unit, value } = dateRange; const { startDate, endDate, unit, value } = dateRange;
const { data } = useFetch(
`/api/website/${websiteId}/pageviews`,
{
start_at: +startDate,
end_at: +endDate,
unit,
tz: getTimezone(),
},
{ onDataLoad },
);
const [pageviews, uniques] = useMemo(() => { const [pageviews, uniques] = useMemo(() => {
if (data) { if (data) {
@ -35,22 +44,6 @@ export default function WebsiteChart({
onDateChange(values); onDateChange(values);
} }
async function loadData() {
const data = await get(`/api/website/${websiteId}/pageviews`, {
start_at: +startDate,
end_at: +endDate,
unit,
tz: getTimezone(),
});
setData(data);
onDataLoad(data);
}
useEffect(() => {
loadData();
}, [websiteId, startDate, endDate, unit]);
return ( return (
<> <>
<div className={classNames(styles.header, 'row')}> <div className={classNames(styles.header, 'row')}>

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'; import React, { useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import PageHeader from 'components/layout/PageHeader'; import PageHeader from 'components/layout/PageHeader';
import Button from 'components/common/Button'; import Button from 'components/common/Button';
@ -7,20 +7,20 @@ import Table from 'components/common/Table';
import Modal from 'components/common/Modal'; import Modal from 'components/common/Modal';
import AccountEditForm from 'components/forms/AccountEditForm'; import AccountEditForm from 'components/forms/AccountEditForm';
import ButtonLayout from 'components/layout/ButtonLayout'; import ButtonLayout from 'components/layout/ButtonLayout';
import DeleteForm from 'components/forms/DeleteForm';
import useFetch from 'hooks/useFetch';
import Pen from 'assets/pen.svg'; import Pen from 'assets/pen.svg';
import Plus from 'assets/plus.svg'; import Plus from 'assets/plus.svg';
import Trash from 'assets/trash.svg'; import Trash from 'assets/trash.svg';
import Check from 'assets/check.svg'; import Check from 'assets/check.svg';
import { get } from 'lib/web';
import styles from './AccountSettings.module.css'; import styles from './AccountSettings.module.css';
import DeleteForm from '../forms/DeleteForm';
export default function AccountSettings() { export default function AccountSettings() {
const [data, setData] = useState();
const [addAccount, setAddAccount] = useState(); const [addAccount, setAddAccount] = useState();
const [editAccount, setEditAccount] = useState(); const [editAccount, setEditAccount] = useState();
const [deleteAccount, setDeleteAccount] = useState(); const [deleteAccount, setDeleteAccount] = useState();
const [saved, setSaved] = useState(0); const [saved, setSaved] = useState(0);
const { data } = useFetch(`/api/accounts`, {}, { update: [saved] });
const Checkmark = ({ is_admin }) => (is_admin ? <Icon icon={<Check />} size="medium" /> : null); const Checkmark = ({ is_admin }) => (is_admin ? <Icon icon={<Check />} size="medium" /> : null);
@ -61,14 +61,6 @@ export default function AccountSettings() {
setDeleteAccount(null); setDeleteAccount(null);
} }
async function loadData() {
setData(await get(`/api/accounts`));
}
useEffect(() => {
loadData();
}, [saved]);
if (!data) { if (!data) {
return null; return null;
} }

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'; import React, { useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import Table from 'components/common/Table'; import Table from 'components/common/Table';
import Button from 'components/common/Button'; import Button from 'components/common/Button';
@ -15,17 +15,17 @@ import Trash from 'assets/trash.svg';
import Plus from 'assets/plus.svg'; import Plus from 'assets/plus.svg';
import Code from 'assets/code.svg'; import Code from 'assets/code.svg';
import Link from 'assets/link.svg'; import Link from 'assets/link.svg';
import { get } from 'lib/web';
import styles from './WebsiteSettings.module.css'; import styles from './WebsiteSettings.module.css';
import useFetch from '../../hooks/useFetch';
export default function WebsiteSettings() { export default function WebsiteSettings() {
const [data, setData] = useState();
const [editWebsite, setEditWebsite] = useState(); const [editWebsite, setEditWebsite] = useState();
const [deleteWebsite, setDeleteWebsite] = useState(); const [deleteWebsite, setDeleteWebsite] = useState();
const [addWebsite, setAddWebsite] = useState(); const [addWebsite, setAddWebsite] = useState();
const [showCode, setShowCode] = useState(); const [showCode, setShowCode] = useState();
const [showUrl, setShowUrl] = useState(); const [showUrl, setShowUrl] = useState();
const [saved, setSaved] = useState(0); const [saved, setSaved] = useState(0);
const { data } = useFetch(`/api/websites`, {}, { update: [saved] });
const Buttons = row => ( const Buttons = row => (
<ButtonLayout> <ButtonLayout>
@ -77,14 +77,6 @@ export default function WebsiteSettings() {
setShowUrl(null); setShowUrl(null);
} }
async function loadData() {
setData(await get(`/api/websites`));
}
useEffect(() => {
loadData();
}, [saved]);
if (!data) { if (!data) {
return null; return null;
} }

39
hooks/useFetch.js Normal file
View File

@ -0,0 +1,39 @@
import { useState, useEffect } from 'react';
import { get } from 'lib/web';
export default function useFetch(url, params = {}, options = {}) {
const [data, setData] = useState();
const [error, setError] = useState();
const keys = Object.keys(params)
.sort()
.map(key => params[key]);
const { update = [], onDataLoad = () => {} } = options;
async function loadData() {
try {
setError(null);
const data = await get(url, params);
setData(data);
onDataLoad(data);
} catch (e) {
console.error(e);
setError(e);
}
}
useEffect(() => {
if (url) {
const { interval } = options;
loadData();
const id = interval ? setInterval(() => loadData(), interval) : null;
return () => {
clearInterval(id);
};
}
}, [url, ...keys, ...update]);
return { data, error };
}

View File

@ -1,39 +1,30 @@
export const AUTH_COOKIE_NAME = 'umami.auth'; export const AUTH_COOKIE_NAME = 'umami.auth';
export const POSTGRESQL = 'postgresql';
export const MYSQL = 'mysql';
export const MYSQL_DATE_FORMATS = {
minute: '%Y-%m-%d %H:%i:00',
hour: '%Y-%m-%d %H:00:00',
day: '%Y-%m-%d',
month: '%Y-%m-01',
year: '%Y-01-01',
};
export const POSTGRESQL_DATE_FORMATS = {
minute: 'YYYY-MM-DD HH24:MI:00',
hour: 'YYYY-MM-DD HH24:00:00',
day: 'YYYY-MM-DD',
month: 'YYYY-MM-01',
year: 'YYYY-01-01',
};
export const DOMAIN_REGEX = /((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}/; export const DOMAIN_REGEX = /((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}/;
export const DESKTOP_SCREEN_WIDTH = 1920; export const DESKTOP_SCREEN_WIDTH = 1920;
export const LAPTOP_SCREEN_WIDTH = 1024; export const LAPTOP_SCREEN_WIDTH = 1024;
export const MOBILE_SCREEN_WIDTH = 479; export const MOBILE_SCREEN_WIDTH = 479;
export const OPERATING_SYSTEMS = [
'iOS',
'Android OS',
'BlackBerry OS',
'Windows Mobile',
'Amazon OS',
'Windows 3.11',
'Windows 95',
'Windows 98',
'Windows 2000',
'Windows XP',
'Windows Server 2003',
'Windows Vista',
'Windows 7',
'Windows 8',
'Windows 8.1',
'Windows 10',
'Windows ME',
'Open BSD',
'Sun OS',
'Linux',
'Mac OS',
'QNX',
'BeOS',
'OS/2',
'Chrome OS',
];
export const DESKTOP_OS = [ export const DESKTOP_OS = [
'Windows 3.11', 'Windows 3.11',
'Windows 95', 'Windows 95',

View File

@ -1,25 +1,7 @@
import moment from 'moment-timezone'; import moment from 'moment-timezone';
import prisma, { runQuery } from 'lib/db'; import prisma, { runQuery } from 'lib/db';
import { subMinutes } from 'date-fns'; import { subMinutes } from 'date-fns';
import { MYSQL, POSTGRESQL, MYSQL_DATE_FORMATS, POSTGRESQL_DATE_FORMATS } from 'lib/constants';
const POSTGRESQL = 'postgresql';
const MYSQL = 'mysql';
const MYSQL_DATE_FORMATS = {
minute: '%Y-%m-%d %H:%i:00',
hour: '%Y-%m-%d %H:00:00',
day: '%Y-%m-%d',
month: '%Y-%m-01',
year: '%Y-01-01',
};
const POSTGRESQL_DATE_FORMATS = {
minute: 'YYYY-MM-DD HH24:MI:00',
hour: 'YYYY-MM-DD HH24:00:00',
day: 'YYYY-MM-DD',
month: 'YYYY-MM-01',
year: 'YYYY-01-01',
};
export function getDatabase() { export function getDatabase() {
return ( return (

View File

@ -1,46 +1,22 @@
import React, { useState, useEffect } from 'react'; import React from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Layout from 'components/layout/Layout'; import Layout from 'components/layout/Layout';
import WebsiteDetails from 'components/WebsiteDetails'; import WebsiteDetails from 'components/WebsiteDetails';
import NotFound from 'pages/404'; import useFetch from 'hooks/useFetch';
import { get } from 'lib/web';
export default function SharePage() { export default function SharePage() {
const [loading, setLoading] = useState(true);
const [websiteId, setWebsiteId] = useState();
const [notFound, setNotFound] = useState(false);
const router = useRouter(); const router = useRouter();
const { id } = router.query; const { id } = router.query;
const shareId = id?.[0];
const { data } = useFetch(shareId ? `/api/share/${shareId}` : null);
async function loadData() { if (!data) {
const website = await get(`/api/share/${id?.[0]}`); return null;
if (website) {
setWebsiteId(website.website_id);
} else if (typeof window !== 'undefined') {
setNotFound(true);
}
}
useEffect(() => {
if (id) {
loadData().finally(() => {
setLoading(false);
});
} else {
setLoading(false);
}
}, [id]);
if (loading) return null;
if (!id || notFound) {
return <NotFound />;
} }
return ( return (
<Layout> <Layout>
<WebsiteDetails websiteId={websiteId} /> <WebsiteDetails websiteId={data.website_id} />
</Layout> </Layout>
); );
} }

View File

@ -5,8 +5,8 @@ const path = require('path');
const databaseType = const databaseType =
process.env.DATABASE_TYPE || (process.env.DATABASE_URL && process.env.DATABASE_URL.split(':')[0]); process.env.DATABASE_TYPE || (process.env.DATABASE_URL && process.env.DATABASE_URL.split(':')[0]);
if (!databaseType) { if (!databaseType || !['mysql', 'postgresql'].includes(databaseType)) {
throw new Error('Database schema not specified'); throw new Error('Missing or invalid database');
} }
console.log(`Database schema detected: ${databaseType}`); console.log(`Database schema detected: ${databaseType}`);