Merge branch 'dev' into analytics

This commit is contained in:
Mike Cao 2023-08-27 22:37:05 -07:00
commit 235ab7f410
17 changed files with 51 additions and 84 deletions

View file

@ -76,6 +76,9 @@ if (process.env.CLOUD_MODE && process.env.CLOUD_URL && process.env.DISABLE_LOGIN
const config = { const config = {
env: { env: {
cloudMode: process.env.CLOUD_MODE,
cloudUrl: process.env.CLOUD_URL,
configUrl: '/config',
currentVersion: pkg.version, currentVersion: pkg.version,
defaultLocale: process.env.DEFAULT_LOCALE, defaultLocale: process.env.DEFAULT_LOCALE,
isProduction: process.env.NODE_ENV === 'production', isProduction: process.env.NODE_ENV === 'production',

View file

@ -3,12 +3,11 @@ import { useState } from 'react';
import MobileMenu from './MobileMenu'; import MobileMenu from './MobileMenu';
import Icons from 'components/icons'; import Icons from 'components/icons';
import useMessages from 'components/hooks/useMessages'; import useMessages from 'components/hooks/useMessages';
import useConfig from 'components/hooks/useConfig';
export function HamburgerButton() { export function HamburgerButton() {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const [active, setActive] = useState(false); const [active, setActive] = useState(false);
const { cloudMode } = useConfig(); const cloudMode = Boolean(process.env.cloudMode);
const menuItems = [ const menuItems = [
{ {

View file

@ -7,15 +7,16 @@ let loading = false;
export function useConfig() { export function useConfig() {
const { config } = useStore(); const { config } = useStore();
const { get } = useApi(); const { get } = useApi();
const configUrl = process.env.configUrl;
async function loadConfig() { async function loadConfig() {
const data = await get('/config'); const data = await get(configUrl);
loading = false; loading = false;
setConfig(data); setConfig(data);
} }
useEffect(() => { useEffect(() => {
if (!config && !loading) { if (!config && !loading && configUrl) {
loading = true; loading = true;
loadConfig(); loadConfig();
} }

View file

@ -3,16 +3,15 @@ import { useRouter } from 'next/router';
import Icons from 'components/icons'; import Icons from 'components/icons';
import useMessages from 'components/hooks/useMessages'; import useMessages from 'components/hooks/useMessages';
import useUser from 'components/hooks/useUser'; import useUser from 'components/hooks/useUser';
import useConfig from 'components/hooks/useConfig';
import styles from './ProfileButton.module.css'; import styles from './ProfileButton.module.css';
import useLocale from 'components/hooks/useLocale'; import useLocale from 'components/hooks/useLocale';
export function ProfileButton() { export function ProfileButton() {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { user } = useUser(); const { user } = useUser();
const { cloudMode } = useConfig();
const router = useRouter(); const router = useRouter();
const { dir } = useLocale(); const { dir } = useLocale();
const cloudMode = Boolean(process.env.cloudMode);
const handleSelect = key => { const handleSelect = key => {
if (key === 'profile') { if (key === 'profile') {

View file

@ -9,7 +9,7 @@ export function AppLayout({ title, children }) {
const { user } = useRequireLogin(); const { user } = useRequireLogin();
const config = useConfig(); const config = useConfig();
if (!user || !config) { if (!user || !config || config?.uiDisabled) {
return null; return null;
} }

View file

@ -37,10 +37,6 @@
margin-bottom: 10px; margin-bottom: 10px;
} }
.title {
font-size: 18px;
}
.actions { .actions {
flex-basis: 100%; flex-basis: 100%;
order: -1; order: -1;

View file

@ -3,14 +3,13 @@ import { useRouter } from 'next/router';
import SideNav from './SideNav'; import SideNav from './SideNav';
import useUser from 'components/hooks/useUser'; import useUser from 'components/hooks/useUser';
import useMessages from 'components/hooks/useMessages'; import useMessages from 'components/hooks/useMessages';
import useConfig from 'components/hooks/useConfig';
import styles from './SettingsLayout.module.css'; import styles from './SettingsLayout.module.css';
export function SettingsLayout({ children }) { export function SettingsLayout({ children }) {
const { user } = useUser(); const { user } = useUser();
const { pathname } = useRouter(); const { pathname } = useRouter();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { cloudMode } = useConfig(); const cloudMode = Boolean(process.env.cloudMode);
const items = [ const items = [
{ key: 'websites', label: formatMessage(labels.websites), url: '/settings/websites' }, { key: 'websites', label: formatMessage(labels.websites), url: '/settings/websites' },

View file

@ -13,4 +13,8 @@
.menu { .menu {
display: none; display: none;
} }
.content {
margin-top: 20px;
}
} }

View file

@ -6,13 +6,12 @@ import ThemeSetting from 'components/pages/settings/profile/ThemeSetting';
import PasswordChangeButton from './PasswordChangeButton'; import PasswordChangeButton from './PasswordChangeButton';
import useUser from 'components/hooks/useUser'; import useUser from 'components/hooks/useUser';
import useMessages from 'components/hooks/useMessages'; import useMessages from 'components/hooks/useMessages';
import useConfig from 'components/hooks/useConfig';
import { ROLES } from 'lib/constants'; import { ROLES } from 'lib/constants';
export function ProfileDetails() { export function ProfileDetails() {
const { user } = useUser(); const { user } = useUser();
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { cloudMode } = useConfig(); const cloudMode = Boolean(process.env.cloudMode);
if (!user) { if (!user) {
return null; return null;

View file

@ -1,6 +1,5 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Breadcrumbs, Item, Tabs, useToasts } from 'react-basics'; import { Item, Tabs, useToasts } from 'react-basics';
import Link from 'next/link';
import Page from 'components/layout/Page'; import Page from 'components/layout/Page';
import PageHeader from 'components/layout/PageHeader'; import PageHeader from 'components/layout/PageHeader';
import { ROLES } from 'lib/constants'; import { ROLES } from 'lib/constants';
@ -44,16 +43,7 @@ export function TeamSettings({ teamId }) {
return ( return (
<Page loading={isLoading || !values}> <Page loading={isLoading || !values}>
<PageHeader <PageHeader title={values?.name} />
title={
<Breadcrumbs>
<Item>
<Link href="/settings/teams">{formatMessage(labels.teams)}</Link>
</Item>
<Item>{values?.name}</Item>
</Breadcrumbs>
}
/>
<Tabs selectedKey={tab} onSelect={setTab} style={{ marginBottom: 30 }}> <Tabs selectedKey={tab} onSelect={setTab} style={{ marginBottom: 30 }}>
<Item key="details">{formatMessage(labels.details)}</Item> <Item key="details">{formatMessage(labels.details)}</Item>
<Item key="members">{formatMessage(labels.members)}</Item> <Item key="members">{formatMessage(labels.members)}</Item>

View file

@ -1,6 +1,5 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Breadcrumbs, Item, Tabs, useToasts } from 'react-basics'; import { Item, Tabs, useToasts } from 'react-basics';
import Link from 'next/link';
import UserEditForm from 'components/pages/settings/users/UserEditForm'; import UserEditForm from 'components/pages/settings/users/UserEditForm';
import Page from 'components/layout/Page'; import Page from 'components/layout/Page';
import PageHeader from 'components/layout/PageHeader'; import PageHeader from 'components/layout/PageHeader';
@ -44,16 +43,7 @@ export function UserSettings({ userId }) {
return ( return (
<Page loading={isLoading || !values}> <Page loading={isLoading || !values}>
<PageHeader <PageHeader title={values?.username} />
title={
<Breadcrumbs>
<Item>
<Link href="/settings/users">{formatMessage(labels.users)}</Link>
</Item>
<Item>{values?.username}</Item>
</Breadcrumbs>
}
/>
<Tabs selectedKey={tab} onSelect={setTab} style={{ marginBottom: 30, fontSize: 14 }}> <Tabs selectedKey={tab} onSelect={setTab} style={{ marginBottom: 30, fontSize: 14 }}>
<Item key="details">{formatMessage(labels.details)}</Item> <Item key="details">{formatMessage(labels.details)}</Item>
<Item key="websites">{formatMessage(labels.websites)}</Item> <Item key="websites">{formatMessage(labels.websites)}</Item>

View file

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

View file

@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Breadcrumbs, Item, Tabs, useToasts, Button, Text, Icon, Icons } from 'react-basics'; import { Item, Tabs, useToasts, Button, Text, Icon, Icons } from 'react-basics';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Link from 'next/link'; import Link from 'next/link';
import Page from 'components/layout/Page'; import Page from 'components/layout/Page';
@ -49,16 +49,7 @@ export function WebsiteSettings({ websiteId, openExternal = false }) {
return ( return (
<Page loading={isLoading || !values}> <Page loading={isLoading || !values}>
<PageHeader <PageHeader title={values?.name}>
title={
<Breadcrumbs>
<Item>
<Link href="/settings/websites">{formatMessage(labels.websites)}</Link>
</Item>
<Item>{values?.name}</Item>
</Breadcrumbs>
}
>
<Link href={`/websites/${websiteId}`} target={openExternal ? '_blank' : null}> <Link href={`/websites/${websiteId}`} target={openExternal ? '_blank' : null}>
<Button variant="primary"> <Button variant="primary">
<Icon> <Icon>

View file

@ -4,7 +4,6 @@ import WebsiteAddForm from 'components/pages/settings/websites/WebsiteAddForm';
import WebsiteList from 'components/pages/settings/websites/WebsitesList'; import WebsiteList from 'components/pages/settings/websites/WebsitesList';
import { useMessages } from 'components/hooks'; import { useMessages } from 'components/hooks';
import useUser from 'components/hooks/useUser'; import useUser from 'components/hooks/useUser';
import useConfig from 'components/hooks/useConfig';
import { ROLES } from 'lib/constants'; import { ROLES } from 'lib/constants';
import { useState } from 'react'; import { useState } from 'react';
import { import {
@ -24,8 +23,8 @@ export function WebsitesPage() {
const [tab, setTab] = useState('my-websites'); const [tab, setTab] = useState('my-websites');
const [fetch, setFetch] = useState(1); const [fetch, setFetch] = useState(1);
const { user } = useUser(); const { user } = useUser();
const { cloudMode } = useConfig();
const { showToast } = useToasts(); const { showToast } = useToasts();
const cloudMode = Boolean(process.env.cloudMode);
const handleSave = async () => { const handleSave = async () => {
setFetch(fetch + 1); setFetch(fetch + 1);

View file

@ -65,19 +65,27 @@ export async function getLocation(ip, req) {
// Cloudflare headers // Cloudflare headers
if (req.headers['cf-ipcountry']) { if (req.headers['cf-ipcountry']) {
const country = safeDecodeURIComponent(req.headers['cf-ipcountry']);
const subdivision1 = safeDecodeURIComponent(req.headers['cf-region-code']);
const city = safeDecodeURIComponent(req.headers['cf-ipcity']);
return { return {
country: safeDecodeURIComponent(req.headers['cf-ipcountry']), country,
subdivision1: safeDecodeURIComponent(req.headers['cf-region-code']), subdivision1: subdivision1.includes('-') ? subdivision1 : `${country}-${subdivision1}`,
city: safeDecodeURIComponent(req.headers['cf-ipcity']), city,
}; };
} }
// Vercel headers // Vercel headers
if (req.headers['x-vercel-ip-country']) { if (req.headers['x-vercel-ip-country']) {
const country = safeDecodeURIComponent(req.headers['x-vercel-ip-country']);
const subdivision1 = safeDecodeURIComponent(req.headers['x-vercel-ip-country-region']);
const city = safeDecodeURIComponent(req.headers['x-vercel-ip-city']);
return { return {
country: safeDecodeURIComponent(req.headers['x-vercel-ip-country']), country,
subdivision1: safeDecodeURIComponent(req.headers['x-vercel-ip-country-region']), subdivision1: subdivision1.includes('-') ? subdivision1 : `${country}-${subdivision1}`,
city: safeDecodeURIComponent(req.headers['x-vercel-ip-city']), city,
}; };
} }

View file

@ -6,7 +6,6 @@ import Script from 'next/script';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import ErrorBoundary from 'components/common/ErrorBoundary'; import ErrorBoundary from 'components/common/ErrorBoundary';
import useLocale from 'components/hooks/useLocale'; import useLocale from 'components/hooks/useLocale';
import useConfig from 'components/hooks/useConfig';
import '@fontsource/inter/400.css'; import '@fontsource/inter/400.css';
import '@fontsource/inter/700.css'; import '@fontsource/inter/700.css';
import 'react-basics/dist/styles.css'; import 'react-basics/dist/styles.css';
@ -27,22 +26,10 @@ const client = new QueryClient({
export default function App({ Component, pageProps }) { export default function App({ Component, pageProps }) {
const { locale, messages } = useLocale(); const { locale, messages } = useLocale();
const { basePath, pathname } = useRouter(); const { basePath, pathname } = useRouter();
const config = useConfig();
const Wrapper = ({ children }) => <span className={locale}>{children}</span>;
if (config?.uiDisabled) {
return null;
}
return ( return (
<QueryClientProvider client={client}> <QueryClientProvider client={client}>
<IntlProvider <IntlProvider locale={locale} messages={messages[locale]} onError={() => null}>
locale={locale}
messages={messages[locale]}
textComponent={Wrapper}
onError={() => null}
>
<ReactBasicsProvider> <ReactBasicsProvider>
<ErrorBoundary> <ErrorBoundary>
<Head> <Head>

View file

@ -2,21 +2,19 @@ import { NextApiRequest, NextApiResponse } from 'next';
import { ok, methodNotAllowed } from 'next-basics'; import { ok, methodNotAllowed } from 'next-basics';
export interface ConfigResponse { export interface ConfigResponse {
basePath: string;
trackerScriptName: string;
updatesDisabled: boolean;
telemetryDisabled: boolean; telemetryDisabled: boolean;
cloudMode: boolean; trackerScriptName: string;
uiDisabled: boolean;
updatesDisabled: boolean;
} }
export default async (req: NextApiRequest, res: NextApiResponse<ConfigResponse>) => { export default async (req: NextApiRequest, res: NextApiResponse<ConfigResponse>) => {
if (req.method === 'GET') { if (req.method === 'GET') {
return ok(res, { return ok(res, {
basePath: process.env.BASE_PATH || '',
trackerScriptName: process.env.TRACKER_SCRIPT_NAME,
updatesDisabled: !!process.env.DISABLE_UPDATES,
telemetryDisabled: !!process.env.DISABLE_TELEMETRY, telemetryDisabled: !!process.env.DISABLE_TELEMETRY,
cloudMode: !!process.env.CLOUD_MODE, trackerScriptName: process.env.TRACKER_SCRIPT_NAME,
uiDisabled: !!process.env.DISABLE_UI,
updatesDisabled: !!process.env.DISABLE_UPDATES,
}); });
} }