feat: inject dynamic env to next (#392)

* fix: auto disable template entry

* feat: inject dynamic env to next
This commit is contained in:
tea artist 2024-03-04 00:21:30 +08:00 committed by GitHub
parent b6e533b9fa
commit ed056d63ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 107 additions and 97 deletions

View File

@ -15,9 +15,9 @@ NEXT_BUILD_ENV_SENTRY_TRACING=false
NEXTJS_DISABLE_SENTRY=true NEXTJS_DISABLE_SENTRY=true
NEXTJS_SENTRY_UPLOAD_DRY_RUN=true NEXTJS_SENTRY_UPLOAD_DRY_RUN=true
# set metrics id # set metrics id
NEXT_PUBLIC_MICROSOFT_CLARITY=your-metrics-id MICROSOFT_CLARITY_ID=your-metrics-id
# help site link # help site link
NEXT_PUBLIC_HELP_SITE_LINK=https://help.teable.io HELP_SITE_LINK=https://help.teable.io
# ↓↓↓↓↓↓↓↓ backendnestjs env ↓↓↓↓↓↓↓↓ # ↓↓↓↓↓↓↓↓ backendnestjs env ↓↓↓↓↓↓↓↓
NEXTJS_DIR=../nextjs-app NEXTJS_DIR=../nextjs-app
@ -48,7 +48,7 @@ BACKEND_MAIL_AUTH_PASS=usertoken
# The spaceId where your template base is located, it is the basic info of template center operation # The spaceId where your template base is located, it is the basic info of template center operation
TEMPLATE_SPACE_ID=your-template-space-id TEMPLATE_SPACE_ID=your-template-space-id
# template site link, you need to set the current value to enable create from template # template site link, you need to set the current value to enable create from template
NEXT_PUBLIC_TEMPLATE_SITE_LINK=https://template.teable.io TEMPLATE_SITE_LINK=https://template.teable.io
# ↓↓↓↓↓↓↓↓ limitaions, time unit is ms ↓↓↓↓↓↓↓↓ # ↓↓↓↓↓↓↓↓ limitaions, time unit is ms ↓↓↓↓↓↓↓↓
MAX_COPY_CELLS=50000 MAX_COPY_CELLS=50000

View File

@ -1,16 +1,19 @@
import { Toaster as SoonerToaster } from '@teable/ui-lib/shadcn/ui/sonner'; import { Toaster as SoonerToaster } from '@teable/ui-lib/shadcn/ui/sonner';
import { Toaster } from '@teable/ui-lib/shadcn/ui/toaster'; import { Toaster } from '@teable/ui-lib/shadcn/ui/toaster';
import type { FC, PropsWithChildren } from 'react'; import type { FC, PropsWithChildren } from 'react';
import type { IServerEnv } from './lib/server-env';
import { EnvContext } from './lib/server-env';
type Props = PropsWithChildren; type Props = PropsWithChildren;
export const AppProviders: FC<Props> = (props) => { export const AppProviders: FC<Props & { env: IServerEnv }> = (props) => {
const { children } = props; const { children, env } = props;
return ( return (
<> <EnvContext.Provider value={env}>
{children} {children}
<Toaster /> <Toaster />
<SoonerToaster /> <SoonerToaster />
</> </EnvContext.Provider>
); );
}; };

View File

@ -1,8 +1,8 @@
import type { IUserMeVo } from '@teable/openapi'; import type { IUserMeVo } from '@teable/openapi';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useTranslation, Trans } from 'next-i18next';
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation, Trans } from 'react-i18next';
import { ACTIONS, EVENTS, STATUS } from 'react-joyride'; import { ACTIONS, EVENTS, STATUS } from 'react-joyride';
import type { CallBackProps, Step, StoreHelpers } from 'react-joyride'; import type { CallBackProps, Step, StoreHelpers } from 'react-joyride';
import colors from 'tailwindcss/colors'; import colors from 'tailwindcss/colors';

View File

@ -1,7 +1,7 @@
import Script from 'next/script'; import Script from 'next/script';
export const MicrosoftClarity = () => { export const MicrosoftClarity = ({ clarityId }: { clarityId?: string }) => {
if (!process.env.NEXT_PUBLIC_MICROSOFT_CLARITY) { if (!clarityId) {
return null; return null;
} }
@ -15,7 +15,7 @@ export const MicrosoftClarity = () => {
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)}; c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i; t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y); y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "${process.env.NEXT_PUBLIC_MICROSOFT_CLARITY}"); })(window, document, "clarity", "script", "${clarityId}");
`, `,
}} }}
/> />

View File

@ -1,3 +1,4 @@
import type { DriverClient } from '@teable/core';
import type { ShareViewGetVo } from '@teable/openapi'; import type { ShareViewGetVo } from '@teable/openapi';
import { AnchorContext, AppProvider, FieldProvider, ViewProvider } from '@teable/sdk/context'; import { AnchorContext, AppProvider, FieldProvider, ViewProvider } from '@teable/sdk/context';
import { getWsPath } from '@teable/sdk/context/app/useConnection'; import { getWsPath } from '@teable/sdk/context/app/useConnection';
@ -12,6 +13,7 @@ import { ViewProxy } from './ViewProxy';
export interface IShareViewPageProps { export interface IShareViewPageProps {
shareViewData: ShareViewGetVo; shareViewData: ShareViewGetVo;
driver: DriverClient;
} }
export const ShareViewPage = (props: IShareViewPageProps) => { export const ShareViewPage = (props: IShareViewPageProps) => {
@ -30,7 +32,7 @@ export const ShareViewPage = (props: IShareViewPageProps) => {
return ( return (
<ShareViewPageContext.Provider value={props.shareViewData}> <ShareViewPageContext.Provider value={props.shareViewData}>
<AppLayout> <AppLayout>
<AppProvider wsPath={wsPath} locale={sdkLocale}> <AppProvider wsPath={wsPath} locale={sdkLocale} driver={props.driver}>
<AnchorContext.Provider <AnchorContext.Provider
value={{ value={{
tableId, tableId,

View File

@ -2,10 +2,10 @@ import { HelpCircle, Settings, UserPlus } from '@teable/icons';
import { useBase, useTableId } from '@teable/sdk/hooks'; import { useBase, useTableId } from '@teable/sdk/hooks';
import { Button } from '@teable/ui-lib/shadcn'; import { Button } from '@teable/ui-lib/shadcn';
import Link from 'next/link'; import Link from 'next/link';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'next-i18next';
import { SpaceCollaboratorModalTrigger } from '@/features/app/components/collaborator-manage/space/SpaceCollaboratorModalTrigger'; import { SpaceCollaboratorModalTrigger } from '@/features/app/components/collaborator-manage/space/SpaceCollaboratorModalTrigger';
import { useEnv } from '@/features/app/hooks/useEnv';
import { spaceConfig } from '@/features/i18n/space.config'; import { spaceConfig } from '@/features/i18n/space.config';
import { getHelpLink } from '@/lib/off-site-link';
import { ExpandViewList } from '../../view/list/ExpandViewList'; import { ExpandViewList } from '../../view/list/ExpandViewList';
import { ViewList } from '../../view/list/ViewList'; import { ViewList } from '../../view/list/ViewList';
@ -16,6 +16,7 @@ import { TableInfo } from './TableInfo';
export const TableHeader: React.FC = () => { export const TableHeader: React.FC = () => {
const base = useBase(); const base = useBase();
const tableId = useTableId(); const tableId = useTableId();
const { helpSiteLink } = useEnv();
const { t } = useTranslation(spaceConfig.i18nNamespaces); const { t } = useTranslation(spaceConfig.i18nNamespaces);
return ( return (
@ -42,7 +43,7 @@ export const TableHeader: React.FC = () => {
</Link> </Link>
</Button> </Button>
<Button asChild variant="ghost" size="xs" className="hidden sm:flex"> <Button asChild variant="ghost" size="xs" className="hidden sm:flex">
<a href={getHelpLink()} title="Help" target="_blank" rel="noreferrer"> <a href={helpSiteLink} title="Help" target="_blank" rel="noreferrer">
<HelpCircle className="size-4" /> <HelpCircle className="size-4" />
</a> </a>
</Button> </Button>

View File

@ -1,6 +1,6 @@
/* eslint-disable @next/next/no-html-link-for-pages */ /* eslint-disable @next/next/no-html-link-for-pages */
import { TeableNew } from '@teable/icons'; import { TeableNew } from '@teable/icons';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'next-i18next';
import { tableConfig } from '@/features/i18n/table.config'; import { tableConfig } from '@/features/i18n/table.config';
export const BrandFooter = () => { export const BrandFooter = () => {

View File

@ -17,8 +17,8 @@ import {
Textarea, Textarea,
cn, cn,
} from '@teable/ui-lib/shadcn'; } from '@teable/ui-lib/shadcn';
import { useTranslation } from 'next-i18next';
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FieldOperator } from '@/features/app/components/field-setting'; import { FieldOperator } from '@/features/app/components/field-setting';
import { tableConfig } from '@/features/i18n/table.config'; import { tableConfig } from '@/features/i18n/table.config';
import { useFieldSettingStore } from '../../field/useFieldSettingStore'; import { useFieldSettingStore } from '../../field/useFieldSettingStore';

View File

@ -1,7 +1,7 @@
import { useFieldStaticGetter, useView } from '@teable/sdk/hooks'; import { useFieldStaticGetter, useView } from '@teable/sdk/hooks';
import type { FormView, IFieldInstance } from '@teable/sdk/model'; import type { FormView, IFieldInstance } from '@teable/sdk/model';
import { useTranslation } from 'next-i18next';
import type { FC } from 'react'; import type { FC } from 'react';
import { useTranslation } from 'react-i18next';
import { tableConfig } from '@/features/i18n/table.config'; import { tableConfig } from '@/features/i18n/table.config';
import { FormCellEditor } from './FormCellEditor'; import { FormCellEditor } from './FormCellEditor';

View File

@ -11,8 +11,8 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger, TooltipTrigger,
} from '@teable/ui-lib/shadcn'; } from '@teable/ui-lib/shadcn';
import { useTranslation } from 'next-i18next';
import type { FC } from 'react'; import type { FC } from 'react';
import { useTranslation } from 'react-i18next';
import { tableConfig } from '@/features/i18n/table.config'; import { tableConfig } from '@/features/i18n/table.config';
interface IFormFieldEditorProps { interface IFormFieldEditorProps {

View File

@ -4,8 +4,8 @@ import { useFields, useTableId, useView } from '@teable/sdk/hooks';
import { type FormView } from '@teable/sdk/model'; import { type FormView } from '@teable/sdk/model';
import { Button, cn, useToast } from '@teable/ui-lib/shadcn'; import { Button, cn, useToast } from '@teable/ui-lib/shadcn';
import { omit } from 'lodash'; import { omit } from 'lodash';
import { useTranslation } from 'next-i18next';
import { useMemo, useRef, useState } from 'react'; import { useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocalStorage, useMap, useSet } from 'react-use'; import { useLocalStorage, useMap, useSet } from 'react-use';
import { tableConfig } from '@/features/i18n/table.config'; import { tableConfig } from '@/features/i18n/table.config';
import { generateUniqLocalKey } from '../util'; import { generateUniqLocalKey } from '../util';

View File

@ -13,9 +13,9 @@ import {
TooltipTrigger, TooltipTrigger,
cn, cn,
} from '@teable/ui-lib/shadcn'; } from '@teable/ui-lib/shadcn';
import { useTranslation } from 'next-i18next';
import type { FC } from 'react'; import type { FC } from 'react';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { FieldOperator } from '@/features/app/components/field-setting'; import { FieldOperator } from '@/features/app/components/field-setting';
import { tableConfig } from '@/features/i18n/table.config'; import { tableConfig } from '@/features/i18n/table.config';
import { useFieldSettingStore } from '../../field/useFieldSettingStore'; import { useFieldSettingStore } from '../../field/useFieldSettingStore';

View File

@ -3,8 +3,8 @@ import { Plus } from '@teable/icons';
import { LinkCard } from '@teable/sdk/components'; import { LinkCard } from '@teable/sdk/components';
import type { LinkField } from '@teable/sdk/model'; import type { LinkField } from '@teable/sdk/model';
import { Button, Popover, PopoverContent, PopoverTrigger } from '@teable/ui-lib/shadcn'; import { Button, Popover, PopoverContent, PopoverTrigger } from '@teable/ui-lib/shadcn';
import { useTranslation } from 'next-i18next';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { tableConfig } from '@/features/i18n/table.config'; import { tableConfig } from '@/features/i18n/table.config';
import { LinkRecordList } from './LinkRecordList'; import { LinkRecordList } from './LinkRecordList';

View File

@ -14,8 +14,8 @@ import {
cn, cn,
} from '@teable/ui-lib/shadcn'; } from '@teable/ui-lib/shadcn';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { useTranslation } from 'next-i18next';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useUnmount } from 'react-use'; import { useUnmount } from 'react-use';
import { tableConfig } from '@/features/i18n/table.config'; import { tableConfig } from '@/features/i18n/table.config';

View File

@ -1,8 +1,8 @@
import { ArrowUpRight, Settings as Edit, Edit as Fill } from '@teable/icons'; import { ArrowUpRight, Settings as Edit, Edit as Fill } from '@teable/icons';
import { useTableId, useTablePermission, useViewId } from '@teable/sdk/hooks'; import { useTableId, useTablePermission, useViewId } from '@teable/sdk/hooks';
import { Button } from '@teable/ui-lib/shadcn'; import { Button } from '@teable/ui-lib/shadcn';
import { useTranslation } from 'next-i18next';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { tableConfig } from '@/features/i18n/table.config'; import { tableConfig } from '@/features/i18n/table.config';
import { generateUniqLocalKey } from '../form/util'; import { generateUniqLocalKey } from '../form/util';
import { SharePopover } from './SharePopover'; import { SharePopover } from './SharePopover';

View File

@ -13,7 +13,7 @@ import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { spaceConfig } from '@/features/i18n/space.config'; import { spaceConfig } from '@/features/i18n/space.config';
import { getTemplateCenterLink } from '@/lib/off-site-link'; import { useEnv } from '../../hooks/useEnv';
export const CreateBaseModalTrigger = ({ export const CreateBaseModalTrigger = ({
spaceId, spaceId,
@ -33,7 +33,7 @@ export const CreateBaseModalTrigger = ({
}); });
}, },
}); });
const templateCenterLink = getTemplateCenterLink(); const { templateSiteLink } = useEnv();
return ( return (
<Dialog> <Dialog>
@ -53,15 +53,13 @@ export const CreateBaseModalTrigger = ({
{t('space:baseModal.fromScratch')} {t('space:baseModal.fromScratch')}
</Button> </Button>
<Button <Button
asChild
className="flex h-auto grow flex-col items-center gap-4" className="flex h-auto grow flex-col items-center gap-4"
variant="ghost" variant="ghost"
disabled={!templateCenterLink} disabled={!templateSiteLink}
onClick={() => (window.location.href = templateSiteLink as string)}
> >
<a href={templateCenterLink}> <LayoutTemplate className="size-8" />
<LayoutTemplate className="size-8" /> {t('space:baseModal.fromTemplate')}
{t('space:baseModal.fromTemplate')}
</a>
</Button> </Button>
</div> </div>
</DialogContent> </DialogContent>

View File

@ -4,7 +4,8 @@ import { useTranslation } from 'next-i18next';
import { useState } from 'react'; import { useState } from 'react';
import { useLocalStorage } from 'react-use'; import { useLocalStorage } from 'react-use';
import { dashboardConfig } from '@/features/i18n/dashboard.config'; import { dashboardConfig } from '@/features/i18n/dashboard.config';
import { getHelpLink } from '@/lib/off-site-link';
import { useEnv } from '../hooks/useEnv';
import { Pickers } from './components/Pickers'; import { Pickers } from './components/Pickers';
import { GridContent } from './GridContent'; import { GridContent } from './GridContent';
@ -13,7 +14,7 @@ export function DashboardPage() {
const [anchor, setAnchor] = useState<{ tableId?: string; viewId?: string }>({}); const [anchor, setAnchor] = useState<{ tableId?: string; viewId?: string }>({});
const { viewId, tableId } = anchor; const { viewId, tableId } = anchor;
const [showDashboard, setShowDashboard] = useLocalStorage('showDashboard', false); const [showDashboard, setShowDashboard] = useLocalStorage('showDashboard', false);
const { helpSiteLink } = useEnv();
return ( return (
<AnchorProvider viewId={viewId} tableId={tableId}> <AnchorProvider viewId={viewId} tableId={tableId}>
<div className="h-full flex-col md:flex"> <div className="h-full flex-col md:flex">
@ -28,7 +29,7 @@ export function DashboardPage() {
<li> <li>
Visit the{' '} Visit the{' '}
<a <a
href={getHelpLink()} href={helpSiteLink}
className="text-blue-500 hover:text-blue-700" className="text-blue-500 hover:text-blue-700"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"

View File

@ -1,7 +1,6 @@
import type { ISelectFieldOptions } from '@teable/core'; import type { ISelectFieldOptions } from '@teable/core';
import { Colors, ColorUtils, CellValueType, FieldType } from '@teable/core'; import { Colors, ColorUtils, CellValueType, FieldType } from '@teable/core';
import { useBase, useFields, useTable, useView } from '@teable/sdk/hooks'; import { useBase, useFields, useTable, useView } from '@teable/sdk/hooks';
import { Base } from '@teable/sdk/model';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
interface IData { interface IData {
@ -35,7 +34,8 @@ export function useChartData() {
return; return;
} }
const nativeSql = Base.knex(table.dbTableName) const nativeSql = base
.knex(table.dbTableName)
.select(`${groupingField.dbFieldName} as name`) .select(`${groupingField.dbFieldName} as name`)
.sum(`${numberField.dbFieldName} as total`) .sum(`${numberField.dbFieldName} as total`)
.groupBy(groupingField.dbFieldName) .groupBy(groupingField.dbFieldName)

View File

@ -1,6 +1,5 @@
import { CellValueType, FieldType } from '@teable/core'; import { CellValueType, FieldType } from '@teable/core';
import { useBase, useFields, useTable, useViewId } from '@teable/sdk/hooks'; import { useBase, useFields, useTable, useViewId } from '@teable/sdk/hooks';
import { Base } from '@teable/sdk/model';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
interface IData { interface IData {
@ -35,7 +34,8 @@ export function useLineChartData() {
const nameColumn = selectField.dbFieldName; const nameColumn = selectField.dbFieldName;
const numberColumn = numberField.dbFieldName; const numberColumn = numberField.dbFieldName;
const nativeSql = Base.knex(table.dbTableName) const nativeSql = base
.knex(table.dbTableName)
.select(nameColumn) .select(nameColumn)
.min(numberColumn + ' as total') .min(numberColumn + ' as total')
.avg(numberColumn + ' as average') .avg(numberColumn + ' as average')

View File

@ -0,0 +1,6 @@
import { useContext } from 'react';
import { EnvContext } from '@/lib/server-env';
export function useEnv() {
return useContext(EnvContext);
}

View File

@ -1,4 +1,4 @@
import type { ITableVo } from '@teable/core'; import type { DriverClient, ITableVo } from '@teable/core';
import type { IGetBaseVo } from '@teable/openapi'; import type { IGetBaseVo } from '@teable/openapi';
import { NotificationProvider, SessionProvider } from '@teable/sdk'; import { NotificationProvider, SessionProvider } from '@teable/sdk';
import type { IUser } from '@teable/sdk'; import type { IUser } from '@teable/sdk';
@ -14,15 +14,16 @@ export const BaseLayout: React.FC<{
children: React.ReactNode; children: React.ReactNode;
tableServerData: ITableVo[]; tableServerData: ITableVo[];
baseServerData: IGetBaseVo; baseServerData: IGetBaseVo;
driver?: DriverClient;
user?: IUser; user?: IUser;
}> = ({ children, tableServerData, baseServerData, user }) => { }> = ({ children, tableServerData, baseServerData, driver, user }) => {
const router = useRouter(); const router = useRouter();
const { baseId, tableId, viewId } = router.query; const { baseId, tableId, viewId } = router.query;
const sdkLocale = useSdkLocale(); const sdkLocale = useSdkLocale();
return ( return (
<AppLayout> <AppLayout>
<AppProvider locale={sdkLocale}> <AppProvider locale={sdkLocale} driver={driver as DriverClient}>
<SessionProvider user={user}> <SessionProvider user={user}>
<NotificationProvider> <NotificationProvider>
<AnchorContext.Provider <AnchorContext.Provider

View File

@ -1,3 +1,4 @@
import type { DriverClient } from '@teable/core';
import type { IUser } from '@teable/sdk'; import type { IUser } from '@teable/sdk';
import { AppProvider, SessionProvider } from '@teable/sdk'; import { AppProvider, SessionProvider } from '@teable/sdk';
import React from 'react'; import React from 'react';
@ -7,12 +8,13 @@ import { useSdkLocale } from '../hooks/useSdkLocale';
export const SettingLayout: React.FC<{ export const SettingLayout: React.FC<{
children: React.ReactNode; children: React.ReactNode;
user?: IUser; user?: IUser;
driver: DriverClient;
dehydratedState?: unknown; dehydratedState?: unknown;
}> = ({ children, user, dehydratedState }) => { }> = ({ children, user, driver, dehydratedState }) => {
const sdkLocale = useSdkLocale(); const sdkLocale = useSdkLocale();
return ( return (
<AppLayout> <AppLayout>
<AppProvider locale={sdkLocale} dehydratedState={dehydratedState}> <AppProvider locale={sdkLocale} dehydratedState={dehydratedState} driver={driver}>
<SessionProvider user={user}> <SessionProvider user={user}>
<div id="portal" className="relative flex h-screen w-full items-start px-5"> <div id="portal" className="relative flex h-screen w-full items-start px-5">
{children} {children}

View File

@ -1,3 +1,4 @@
import type { DriverClient } from '@teable/core';
import type { IUser } from '@teable/sdk'; import type { IUser } from '@teable/sdk';
import { NotificationProvider, SessionProvider } from '@teable/sdk'; import { NotificationProvider, SessionProvider } from '@teable/sdk';
import { AppProvider } from '@teable/sdk/context'; import { AppProvider } from '@teable/sdk/context';
@ -10,12 +11,13 @@ export const SpaceLayout: React.FC<{
children: React.ReactNode; children: React.ReactNode;
user?: IUser; user?: IUser;
dehydratedState?: unknown; dehydratedState?: unknown;
}> = ({ children, user, dehydratedState }) => { driver: DriverClient;
}> = ({ children, user, driver, dehydratedState }) => {
const sdkLocale = useSdkLocale(); const sdkLocale = useSdkLocale();
return ( return (
<AppLayout> <AppLayout>
<AppProvider locale={sdkLocale} dehydratedState={dehydratedState}> <AppProvider locale={sdkLocale} dehydratedState={dehydratedState} driver={driver}>
<SessionProvider user={user}> <SessionProvider user={user}>
<NotificationProvider> <NotificationProvider>
<div id="portal" className="relative flex h-screen w-full items-start"> <div id="portal" className="relative flex h-screen w-full items-start">

View File

@ -1,3 +0,0 @@
export const getHelpLink = () => process.env.NEXT_PUBLIC_HELP_SITE_LINK || 'https://help.teable.io';
export const getTemplateCenterLink = () => process.env.NEXT_PUBLIC_TEMPLATE_SITE_LINK;

View File

@ -0,0 +1,9 @@
import React from 'react';
export interface IServerEnv {
helpSiteLink?: string;
templateSiteLink?: string;
microsoftClarityId?: string;
}
export const EnvContext = React.createContext<IServerEnv>({});

View File

@ -13,6 +13,7 @@ import { getUserMe } from '@/backend/api/rest/get-user';
import { Guide } from '@/components/Guide'; import { Guide } from '@/components/Guide';
import { MicrosoftClarity } from '@/components/Metrics'; import { MicrosoftClarity } from '@/components/Metrics';
import RouterProgressBar from '@/components/RouterProgress'; import RouterProgressBar from '@/components/RouterProgress';
import type { IServerEnv } from '@/lib/server-env';
import type { NextPageWithLayout } from '@/lib/type'; import type { NextPageWithLayout } from '@/lib/type';
import { colors } from '@/themes/colors'; import { colors } from '@/themes/colors';
import { INITIAL_THEME } from '@/themes/initial'; import { INITIAL_THEME } from '@/themes/initial';
@ -42,24 +43,20 @@ type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout; Component: NextPageWithLayout;
user?: IUser; user?: IUser;
driver: string; driver: string;
env: IServerEnv;
}; };
/** /**
* @link https://nextjs.org/docs/advanced-features/custom-app * @link https://nextjs.org/docs/advanced-features/custom-app
*/ */
const MyApp = (appProps: AppPropsWithLayout) => { const MyApp = (appProps: AppPropsWithLayout) => {
const { Component, pageProps, err, user, driver } = appProps; const { Component, pageProps, err, user, driver, env } = appProps;
// Use the layout defined at the page level, if available // Use the layout defined at the page level, if available
const getLayout = Component.getLayout ?? ((page) => page); const getLayout = Component.getLayout ?? ((page) => page);
const serverInfo = {
driver,
user,
};
return ( return (
<> <>
<AppProviders> <AppProviders env={env}>
<Head> <Head>
<meta <meta
name="viewport" name="viewport"
@ -67,18 +64,17 @@ const MyApp = (appProps: AppPropsWithLayout) => {
/> />
<style>{getColorsCssVariablesText(colors)}</style> <style>{getColorsCssVariablesText(colors)}</style>
</Head> </Head>
<MicrosoftClarity /> <MicrosoftClarity clarityId={env?.microsoftClarityId} />
<script dangerouslySetInnerHTML={{ __html: INITIAL_THEME }} /> <script dangerouslySetInnerHTML={{ __html: INITIAL_THEME }} />
<script <script
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: ` __html: `
window.__s = ${JSON.stringify(serverInfo)};
window.clarity && window.clarity("identify", "${user?.email || user?.id}"); window.clarity && window.clarity("identify", "${user?.email || user?.id}");
`, `,
}} }}
/> />
{/* Workaround for https://github.com/vercel/next.js/issues/8592 */} {/* Workaround for https://github.com/vercel/next.js/issues/8592 */}
{getLayout(<Component {...pageProps} err={err} />, { ...pageProps, user })} {getLayout(<Component {...pageProps} err={err} />, { ...pageProps, user, driver })}
</AppProviders> </AppProviders>
<Guide user={user} /> <Guide user={user} />
<RouterProgressBar /> <RouterProgressBar />
@ -106,6 +102,11 @@ MyApp.getInitialProps = async (appContext: AppContext) => {
const initialProps = { const initialProps = {
...appProps, ...appProps,
driver, driver,
env: {
helpSiteLink: process.env.HELP_SITE_LINK,
templateSiteLink: process.env.TEMPLATE_SITE_LINK,
microsoftClarityId: process.env.MICROSOFT_CLARITY_ID,
},
}; };
if (!isLoginPage && !needLoginPage) { if (!isLoginPage && !needLoginPage) {
return initialProps; return initialProps;

View File

@ -1,4 +1,4 @@
import type { IHttpError } from '@teable/core'; import { parseDsn, type DriverClient, type IHttpError } from '@teable/core';
import type { ShareViewGetVo } from '@teable/openapi'; import type { ShareViewGetVo } from '@teable/openapi';
import type { GetServerSideProps } from 'next'; import type { GetServerSideProps } from 'next';
import { SsrApi } from '@/backend/api/rest/table.ssr'; import { SsrApi } from '@/backend/api/rest/table.ssr';
@ -17,9 +17,11 @@ export const getServerSideProps: GetServerSideProps<IShareViewPageProps> = async
res.setHeader('Content-Security-Policy', "frame-ancestors 'self' *;"); res.setHeader('Content-Security-Policy', "frame-ancestors 'self' *;");
ssrApi.axios.defaults.headers['cookie'] = req.headers.cookie || ''; ssrApi.axios.defaults.headers['cookie'] = req.headers.cookie || '';
const shareViewData = await ssrApi.getShareView(shareId as string); const shareViewData = await ssrApi.getShareView(shareId as string);
const driver = parseDsn(process.env.PRISMA_DATABASE_URL as string).driver as DriverClient;
return { return {
props: { props: {
shareViewData, shareViewData,
driver,
...(await getTranslationsProps(context, i18nNamespaces)), ...(await getTranslationsProps(context, i18nNamespaces)),
}, },
}; };
@ -40,6 +42,12 @@ export const getServerSideProps: GetServerSideProps<IShareViewPageProps> = async
} }
}; };
export default function ShareView({ shareViewData }: { shareViewData: ShareViewGetVo }) { export default function ShareView({
return <ShareViewPage shareViewData={shareViewData} />; shareViewData,
driver,
}: {
shareViewData: ShareViewGetVo;
driver: DriverClient;
}) {
return <ShareViewPage shareViewData={shareViewData} driver={driver} />;
} }

View File

@ -32,8 +32,6 @@ FROM deps AS builder
ARG INTEGRATION_TEST ARG INTEGRATION_TEST
ARG NEXT_PUBLIC_MICROSOFT_CLARITY
ENV NEXT_BUILD_ENV_TYPECHECK=false ENV NEXT_BUILD_ENV_TYPECHECK=false
ENV NEXT_BUILD_ENV_LINT=false ENV NEXT_BUILD_ENV_LINT=false
ENV NEXT_BUILD_ENV_OUTPUT=classic ENV NEXT_BUILD_ENV_OUTPUT=classic

View File

@ -1,7 +1,7 @@
import { Hydrate, QueryClientProvider } from '@tanstack/react-query'; import { Hydrate, QueryClientProvider } from '@tanstack/react-query';
import type { DriverClient } from '@teable/core';
import { isObject } from 'lodash'; import { isObject } from 'lodash';
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { getDriver } from '../../utils/driver';
import { AppContext } from '../app/AppContext'; import { AppContext } from '../app/AppContext';
import type { ILocale, ILocalePartial } from './i18n'; import type { ILocale, ILocalePartial } from './i18n';
import { defaultLocale } from './i18n'; import { defaultLocale } from './i18n';
@ -15,11 +15,12 @@ interface IAppProviderProps {
children: React.ReactNode; children: React.ReactNode;
wsPath?: string; wsPath?: string;
locale?: ILocalePartial; locale?: ILocalePartial;
driver: DriverClient;
dehydratedState?: unknown; dehydratedState?: unknown;
} }
export const AppProvider = (props: IAppProviderProps) => { export const AppProvider = (props: IAppProviderProps) => {
const { children, wsPath, locale, dehydratedState } = props; const { children, wsPath, locale, driver, dehydratedState } = props;
const { connected, connection } = useConnection(wsPath); const { connected, connection } = useConnection(wsPath);
const themeProps = useTheme(); const themeProps = useTheme();
@ -34,11 +35,11 @@ export const AppProvider = (props: IAppProviderProps) => {
return { return {
connection, connection,
connected, connected,
driver: getDriver(), driver,
locale: isObject(locale) ? ({ ...defaultLocale, ...locale } as ILocale) : defaultLocale, locale: isObject(locale) ? ({ ...defaultLocale, ...locale } as ILocale) : defaultLocale,
...themeProps, ...themeProps,
}; };
}, [connection, connected, locale, themeProps]); }, [connection, connected, driver, locale, themeProps]);
return ( return (
<AppContext.Provider value={value}> <AppContext.Provider value={value}>

View File

@ -5,6 +5,7 @@ import type { FC, ReactNode } from 'react';
import { useContext, useMemo } from 'react'; import { useContext, useMemo } from 'react';
import { Base } from '../../model'; import { Base } from '../../model';
import { AnchorContext } from '../anchor'; import { AnchorContext } from '../anchor';
import { AppContext } from '../app';
import { BaseContext } from './BaseContext'; import { BaseContext } from './BaseContext';
interface IBaseProviderProps { interface IBaseProviderProps {
@ -14,6 +15,7 @@ interface IBaseProviderProps {
export const BaseProvider: FC<IBaseProviderProps> = ({ children, serverData }) => { export const BaseProvider: FC<IBaseProviderProps> = ({ children, serverData }) => {
const { baseId } = useContext(AnchorContext); const { baseId } = useContext(AnchorContext);
const { driver } = useContext(AppContext);
const { data: baseData, isLoading } = useQuery({ const { data: baseData, isLoading } = useQuery({
queryKey: ['base', baseId], queryKey: ['base', baseId],
queryFn: ({ queryKey }) => (queryKey[1] ? getBaseById(queryKey[1]) : undefined), queryFn: ({ queryKey }) => (queryKey[1] ? getBaseById(queryKey[1]) : undefined),
@ -21,8 +23,8 @@ export const BaseProvider: FC<IBaseProviderProps> = ({ children, serverData }) =
const value = useMemo(() => { const value = useMemo(() => {
const base = isLoading ? serverData : baseData?.data; const base = isLoading ? serverData : baseData?.data;
return { base: base ? new Base(base) : undefined }; return { base: base ? new Base(base, driver) : undefined };
}, [isLoading, baseData, serverData]); }, [isLoading, serverData, baseData?.data, driver]);
return <BaseContext.Provider value={value}>{children}</BaseContext.Provider>; return <BaseContext.Provider value={value}>{children}</BaseContext.Provider>;
}; };

View File

@ -8,15 +8,6 @@ interface ISessionProviderProps {
user?: IUser; user?: IUser;
} }
declare global {
interface Window {
__s: {
user?: IUser;
driver: string;
};
}
}
export const SessionProvider: React.FC<React.PropsWithChildren<ISessionProviderProps>> = ( export const SessionProvider: React.FC<React.PropsWithChildren<ISessionProviderProps>> = (
props props
) => { ) => {
@ -25,9 +16,6 @@ export const SessionProvider: React.FC<React.PropsWithChildren<ISessionProviderP
if (user) { if (user) {
return user; return user;
} }
if (typeof window === 'object') {
return window.__s.user;
}
return undefined; return undefined;
}); });

View File

@ -1,21 +1,18 @@
import type { ICreateTableRo, SpaceRole } from '@teable/core'; import type { DriverClient, ICreateTableRo, SpaceRole } from '@teable/core';
import type { IGetBaseVo } from '@teable/openapi'; import type { IGetBaseVo } from '@teable/openapi';
import knex from 'knex'; import { knex, type Knex } from 'knex';
import { getDriver } from '../utils/driver';
import { Table } from './table/table'; import { Table } from './table/table';
export class Base implements IGetBaseVo { export class Base implements IGetBaseVo {
// eslint-disable-next-line @typescript-eslint/naming-convention
static knex = knex({ client: getDriver() });
id: string; id: string;
name: string; name: string;
spaceId: string; spaceId: string;
order: number; order: number;
icon: string | null; icon: string | null;
role: SpaceRole; role: SpaceRole;
knex: Knex;
constructor(base: IGetBaseVo) { constructor(base: IGetBaseVo, driver: DriverClient) {
const { id, name, order, spaceId, icon, role } = base; const { id, name, order, spaceId, icon, role } = base;
this.id = id; this.id = id;
this.name = name; this.name = name;
@ -23,6 +20,7 @@ export class Base implements IGetBaseVo {
this.order = order; this.order = order;
this.icon = icon; this.icon = icon;
this.role = role; this.role = role;
this.knex = knex({ client: driver });
} }
async sqlQuery(tableId: string, viewId: string, sql: string) { async sqlQuery(tableId: string, viewId: string, sql: string) {

View File

@ -1,8 +0,0 @@
import { DriverClient } from '@teable/core';
export function getDriver(): DriverClient {
if (typeof window === 'object') {
return (window.__s?.driver as DriverClient) || DriverClient.Sqlite;
}
return DriverClient.Sqlite;
}