Skip to content

fix: hide signup buttons when disallowSignUp is configured #1498

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions apps/nextjs-app/src/features/auth/components/SignForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import type { ZodIssue } from 'zod';
import { fromZodError } from 'zod-validation-error';
import { authConfig } from '../../i18n/auth.config';
import { useDisallowSignUp } from '../useDisallowSignUp';
import { SendVerificationButton } from './SendVerificationButton';

export interface ISignForm {
Expand All @@ -33,6 +34,8 @@ export const SignForm: FC<ISignForm> = (props) => {
const [signupVerificationCode, setSignupVerificationCode] = useState<string>();
const router = useRouter();

const disallowSignUp = useDisallowSignUp();

const [isLoading, setIsLoading] = useState<boolean>(false);
const [error, setError] = useState<string>();

Expand Down Expand Up @@ -278,18 +281,20 @@ export const SignForm: FC<ISignForm> = (props) => {
{isLoading && <Spin />}
{buttonText}
</Button>
<div className="flex justify-end py-2">
<Link
href={{
pathname: type === 'signin' ? '/auth/signup' : '/auth/login',
query: { ...router.query },
}}
shallow
className="text-xs text-muted-foreground underline-offset-4 hover:underline"
>
{type === 'signin' ? t('auth:button.signup') : t('auth:button.signin')}
</Link>
</div>
{!disallowSignUp && (
<div className="flex justify-end py-2">
<Link
href={{
pathname: type === 'signin' ? '/auth/signup' : '/auth/login',
query: { ...router.query },
}}
shallow
className="text-xs text-muted-foreground underline-offset-4 hover:underline"
>
{type === 'signin' ? t('auth:button.signup') : t('auth:button.signin')}
</Link>
</div>
)}
<ErrorCom error={error} />
</div>
</div>
Expand Down
10 changes: 7 additions & 3 deletions apps/nextjs-app/src/features/auth/pages/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { DescContent } from '../components/DescContent';
import { SignForm } from '../components/SignForm';
import { SocialAuth } from '../components/SocialAuth';
import { Terms } from '../components/Terms';
import { useDisallowSignUp } from '../useDisallowSignUp';

export const LoginPage = (props: { children?: React.ReactNode | React.ReactNode[] }) => {
const { children } = props;
Expand All @@ -25,6 +26,7 @@ export const LoginPage = (props: { children?: React.ReactNode | React.ReactNode[
const redirect = decodeURIComponent((router.query.redirect as string) || '');
const signType = router.pathname.endsWith('/signup') ? 'signup' : 'signin';
const { passwordLoginDisabled } = useEnv();
const disallowSignUp = useDisallowSignUp();

const onSuccess = useCallback(() => {
if (redirect && redirect.startsWith('/')) {
Expand Down Expand Up @@ -53,9 +55,11 @@ export const LoginPage = (props: { children?: React.ReactNode | React.ReactNode[
<Link href={{ pathname: '/auth/login', query: { ...router.query } }} shallow>
<TabsTrigger value="signin">{t('auth:button.signin')}</TabsTrigger>
</Link>
<Link href={{ pathname: '/auth/signup', query: { ...router.query } }} shallow>
<TabsTrigger value="signup">{t('auth:button.signup')}</TabsTrigger>
</Link>
{!disallowSignUp && (
<Link href={{ pathname: '/auth/signup', query: { ...router.query } }} shallow>
<TabsTrigger value="signup">{t('auth:button.signup')}</TabsTrigger>
</Link>
)}
</TabsList>
</Tabs>
</div>
Expand Down
12 changes: 12 additions & 0 deletions apps/nextjs-app/src/features/auth/useDisallowSignUp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useQuery } from '@tanstack/react-query';
import { getPublicSetting } from '@teable/openapi';
import { ReactQueryKeys } from '@teable/sdk/config';

export const useDisallowSignUp = () => {
const { data: setting } = useQuery({
queryKey: ReactQueryKeys.getPublicSetting(),
queryFn: () => getPublicSetting().then(({ data }) => data),
});
const { disallowSignUp } = setting ?? {};
return disallowSignUp;
Copy link
Preview

Copilot AI Apr 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider returning a default boolean value (e.g., false) when 'setting' is undefined to ensure a consistent evaluation of the disallowSignUp flag.

Suggested change
return disallowSignUp;
return disallowSignUp ?? false;

Copilot uses AI. Check for mistakes.

};
24 changes: 21 additions & 3 deletions apps/nextjs-app/src/pages/auth/login.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { QueryClientProvider } from '@tanstack/react-query';
import type { DehydratedState } from '@tanstack/react-query';
import { dehydrate, Hydrate, QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryKeys } from '@teable/sdk/config';
import { createQueryClient } from '@teable/sdk/context';
import type { GetServerSideProps, InferGetServerSidePropsType } from 'next';
import { useState } from 'react';
import { SsrApi } from '@/backend/api/rest/ssr-api';
import { LoginPage } from '@/features/auth/pages/LoginPage';
import { authConfig } from '@/features/i18n/auth.config';
import ensureLogin from '@/lib/ensureLogin';
Expand All @@ -12,22 +15,37 @@ type Props = {
/** Add props here */
};

export default function LoginRoute(_props: InferGetServerSidePropsType<typeof getServerSideProps>) {
export default function LoginRoute(
props: InferGetServerSidePropsType<typeof getServerSideProps> & {
dehydratedState: DehydratedState;
}
) {
const [queryClient] = useState(() => createQueryClient());

return (
<QueryClientProvider client={queryClient}>
<LoginPage />
<Hydrate state={props.dehydratedState}>
<LoginPage />
</Hydrate>
</QueryClientProvider>
);
}

export const getServerSideProps: GetServerSideProps<Props> = withEnv(
ensureLogin(async (context) => {
const { i18nNamespaces } = authConfig;
const queryClient = new QueryClient();
const ssrApi = new SsrApi();
await Promise.all([
queryClient.fetchQuery({
queryKey: ReactQueryKeys.getPublicSetting(),
queryFn: () => ssrApi.getPublicSetting(),
}),
]);
Comment on lines +39 to +44
Copy link
Preview

Copilot AI Apr 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider adding error handling around the API call to fetch public settings to gracefully manage potential service failures on the server side.

Suggested change
await Promise.all([
queryClient.fetchQuery({
queryKey: ReactQueryKeys.getPublicSetting(),
queryFn: () => ssrApi.getPublicSetting(),
}),
]);
try {
await Promise.all([
queryClient.fetchQuery({
queryKey: ReactQueryKeys.getPublicSetting(),
queryFn: () => ssrApi.getPublicSetting(),
}),
]);
} catch (error) {
console.error("Failed to fetch public settings:", error);
// Optionally, handle fallback logic here if needed
}

Copilot uses AI. Check for mistakes.

return {
props: {
...(await getTranslationsProps(context, i18nNamespaces)),
dehydratedState: dehydrate(queryClient),
},
};
}, true)
Expand Down