mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 13:47:19 +08:00
feat: require email verification for new signups (#12123)
This commit is contained in:
@@ -77,7 +77,7 @@ describe("AuthModal", () => {
|
||||
);
|
||||
|
||||
// Find the terms of service section using data-testid
|
||||
const termsSection = screen.getByTestId("auth-modal-terms-of-service");
|
||||
const termsSection = screen.getByTestId("terms-and-privacy-notice");
|
||||
expect(termsSection).toBeInTheDocument();
|
||||
|
||||
// Check that all text content is present in the paragraph
|
||||
@@ -114,6 +114,38 @@ describe("AuthModal", () => {
|
||||
expect(termsSection).toContainElement(privacyLink);
|
||||
});
|
||||
|
||||
it("should display email verified message when emailVerified prop is true", () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<AuthModal
|
||||
githubAuthUrl="mock-url"
|
||||
appMode="saas"
|
||||
emailVerified={true}
|
||||
/>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText("AUTH$EMAIL_VERIFIED_PLEASE_LOGIN"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not display email verified message when emailVerified prop is false", () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<AuthModal
|
||||
githubAuthUrl="mock-url"
|
||||
appMode="saas"
|
||||
emailVerified={false}
|
||||
/>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByText("AUTH$EMAIL_VERIFIED_PLEASE_LOGIN"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should open Terms of Service link in new tab", () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
@@ -142,12 +174,17 @@ describe("AuthModal", () => {
|
||||
|
||||
describe("Duplicate email error message", () => {
|
||||
const renderAuthModalWithRouter = (initialEntries: string[]) => {
|
||||
const hasDuplicatedEmail = initialEntries.includes(
|
||||
"/?duplicated_email=true",
|
||||
);
|
||||
|
||||
return render(
|
||||
<MemoryRouter initialEntries={initialEntries}>
|
||||
<AuthModal
|
||||
githubAuthUrl="mock-url"
|
||||
appMode="saas"
|
||||
providersConfigured={["github"]}
|
||||
hasDuplicatedEmail={hasDuplicatedEmail}
|
||||
/>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { it, describe, expect, vi, beforeEach } from "vitest";
|
||||
import { EmailVerificationModal } from "#/components/features/waitlist/email-verification-modal";
|
||||
|
||||
describe("EmailVerificationModal", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should render the email verification message", () => {
|
||||
// Arrange & Act
|
||||
render(<EmailVerificationModal onClose={vi.fn()} />);
|
||||
|
||||
// Assert
|
||||
expect(
|
||||
screen.getByText("AUTH$PLEASE_CHECK_EMAIL_TO_VERIFY"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render the TermsAndPrivacyNotice component", () => {
|
||||
// Arrange & Act
|
||||
render(<EmailVerificationModal onClose={vi.fn()} />);
|
||||
|
||||
// Assert
|
||||
const termsSection = screen.getByTestId("terms-and-privacy-notice");
|
||||
expect(termsSection).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { it, describe, expect } from "vitest";
|
||||
import { TermsAndPrivacyNotice } from "#/components/shared/terms-and-privacy-notice";
|
||||
|
||||
describe("TermsAndPrivacyNotice", () => {
|
||||
it("should render Terms of Service and Privacy Policy links", () => {
|
||||
// Arrange & Act
|
||||
render(<TermsAndPrivacyNotice />);
|
||||
|
||||
// Assert
|
||||
const termsSection = screen.getByTestId("terms-and-privacy-notice");
|
||||
expect(termsSection).toBeInTheDocument();
|
||||
|
||||
const tosLink = screen.getByRole("link", {
|
||||
name: "COMMON$TERMS_OF_SERVICE",
|
||||
});
|
||||
const privacyLink = screen.getByRole("link", {
|
||||
name: "COMMON$PRIVACY_POLICY",
|
||||
});
|
||||
|
||||
expect(tosLink).toBeInTheDocument();
|
||||
expect(tosLink).toHaveAttribute("href", "https://www.all-hands.dev/tos");
|
||||
expect(tosLink).toHaveAttribute("target", "_blank");
|
||||
expect(tosLink).toHaveAttribute("rel", "noopener noreferrer");
|
||||
|
||||
expect(privacyLink).toBeInTheDocument();
|
||||
expect(privacyLink).toHaveAttribute(
|
||||
"href",
|
||||
"https://www.all-hands.dev/privacy",
|
||||
);
|
||||
expect(privacyLink).toHaveAttribute("target", "_blank");
|
||||
expect(privacyLink).toHaveAttribute("rel", "noopener noreferrer");
|
||||
});
|
||||
|
||||
it("should render all required text content", () => {
|
||||
// Arrange & Act
|
||||
render(<TermsAndPrivacyNotice />);
|
||||
|
||||
// Assert
|
||||
const termsSection = screen.getByTestId("terms-and-privacy-notice");
|
||||
expect(termsSection).toHaveTextContent(
|
||||
"AUTH$BY_SIGNING_UP_YOU_AGREE_TO_OUR",
|
||||
);
|
||||
expect(termsSection).toHaveTextContent("COMMON$TERMS_OF_SERVICE");
|
||||
expect(termsSection).toHaveTextContent("COMMON$AND");
|
||||
expect(termsSection).toHaveTextContent("COMMON$PRIVACY_POLICY");
|
||||
});
|
||||
});
|
||||
242
frontend/__tests__/routes/root-layout.test.tsx
Normal file
242
frontend/__tests__/routes/root-layout.test.tsx
Normal file
@@ -0,0 +1,242 @@
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { it, describe, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { createRoutesStub } from "react-router";
|
||||
import MainApp from "#/routes/root-layout";
|
||||
import OptionService from "#/api/option-service/option-service.api";
|
||||
import AuthService from "#/api/auth-service/auth-service.api";
|
||||
import SettingsService from "#/api/settings-service/settings-service.api";
|
||||
|
||||
// Mock other hooks that are not the focus of these tests
|
||||
vi.mock("#/hooks/use-github-auth-url", () => ({
|
||||
useGitHubAuthUrl: () => "https://github.com/oauth/authorize",
|
||||
}));
|
||||
|
||||
vi.mock("#/hooks/use-is-on-tos-page", () => ({
|
||||
useIsOnTosPage: () => false,
|
||||
}));
|
||||
|
||||
vi.mock("#/hooks/use-auto-login", () => ({
|
||||
useAutoLogin: () => {},
|
||||
}));
|
||||
|
||||
vi.mock("#/hooks/use-auth-callback", () => ({
|
||||
useAuthCallback: () => {},
|
||||
}));
|
||||
|
||||
vi.mock("#/hooks/use-migrate-user-consent", () => ({
|
||||
useMigrateUserConsent: () => ({
|
||||
migrateUserConsent: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock("#/hooks/use-reo-tracking", () => ({
|
||||
useReoTracking: () => {},
|
||||
}));
|
||||
|
||||
vi.mock("#/hooks/use-sync-posthog-consent", () => ({
|
||||
useSyncPostHogConsent: () => {},
|
||||
}));
|
||||
|
||||
vi.mock("#/utils/custom-toast-handlers", () => ({
|
||||
displaySuccessToast: vi.fn(),
|
||||
}));
|
||||
|
||||
const RouterStub = createRoutesStub([
|
||||
{
|
||||
Component: MainApp,
|
||||
path: "/",
|
||||
children: [
|
||||
{
|
||||
Component: () => <div data-testid="outlet-content">Content</div>,
|
||||
path: "/",
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const createWrapper = () => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
||||
describe("MainApp - Email Verification Flow", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Default mocks for services
|
||||
vi.spyOn(OptionService, "getConfig").mockResolvedValue({
|
||||
APP_MODE: "saas",
|
||||
GITHUB_CLIENT_ID: "test-client-id",
|
||||
POSTHOG_CLIENT_KEY: "test-posthog-key",
|
||||
PROVIDERS_CONFIGURED: ["github"],
|
||||
AUTH_URL: "https://auth.example.com",
|
||||
FEATURE_FLAGS: {
|
||||
ENABLE_BILLING: false,
|
||||
HIDE_LLM_SETTINGS: false,
|
||||
ENABLE_JIRA: false,
|
||||
ENABLE_JIRA_DC: false,
|
||||
ENABLE_LINEAR: false,
|
||||
},
|
||||
});
|
||||
|
||||
vi.spyOn(AuthService, "authenticate").mockResolvedValue(true);
|
||||
|
||||
vi.spyOn(SettingsService, "getSettings").mockResolvedValue({
|
||||
language: "en",
|
||||
user_consents_to_analytics: true,
|
||||
llm_model: "",
|
||||
llm_base_url: "",
|
||||
agent: "",
|
||||
llm_api_key: null,
|
||||
llm_api_key_set: false,
|
||||
search_api_key_set: false,
|
||||
confirmation_mode: false,
|
||||
security_analyzer: null,
|
||||
remote_runtime_resource_factor: null,
|
||||
provider_tokens_set: {},
|
||||
enable_default_condenser: false,
|
||||
condenser_max_size: null,
|
||||
enable_sound_notifications: false,
|
||||
enable_proactive_conversation_starters: false,
|
||||
enable_solvability_analysis: false,
|
||||
max_budget_per_task: null,
|
||||
});
|
||||
|
||||
// Mock localStorage
|
||||
vi.stubGlobal("localStorage", {
|
||||
getItem: vi.fn(() => null),
|
||||
setItem: vi.fn(),
|
||||
removeItem: vi.fn(),
|
||||
clear: vi.fn(),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("should display EmailVerificationModal when email_verification_required=true is in query params", async () => {
|
||||
// Arrange & Act
|
||||
render(
|
||||
<RouterStub initialEntries={["/?email_verification_required=true"]} />,
|
||||
{ wrapper: createWrapper() },
|
||||
);
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText("AUTH$PLEASE_CHECK_EMAIL_TO_VERIFY"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should set emailVerified state and pass to AuthModal when email_verified=true is in query params", async () => {
|
||||
// Arrange
|
||||
// Mock a 401 error to simulate unauthenticated user
|
||||
const axiosError = {
|
||||
response: { status: 401 },
|
||||
isAxiosError: true,
|
||||
};
|
||||
vi.spyOn(AuthService, "authenticate").mockRejectedValue(axiosError);
|
||||
|
||||
// Act
|
||||
render(<RouterStub initialEntries={["/?email_verified=true"]} />, {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
|
||||
// Assert - Wait for AuthModal to render (since user is not authenticated)
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText("AUTH$EMAIL_VERIFIED_PLEASE_LOGIN"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle both email_verification_required and email_verified params together", async () => {
|
||||
// Arrange & Act
|
||||
render(
|
||||
<RouterStub
|
||||
initialEntries={[
|
||||
"/?email_verification_required=true&email_verified=true",
|
||||
]}
|
||||
/>,
|
||||
{ wrapper: createWrapper() },
|
||||
);
|
||||
|
||||
// Assert - EmailVerificationModal should take precedence
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText("AUTH$PLEASE_CHECK_EMAIL_TO_VERIFY"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should remove query parameters from URL after processing", async () => {
|
||||
// Arrange & Act
|
||||
const { container } = render(
|
||||
<RouterStub initialEntries={["/?email_verification_required=true"]} />,
|
||||
{ wrapper: createWrapper() },
|
||||
);
|
||||
|
||||
// Assert - Wait for the modal to appear (which indicates processing happened)
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText("AUTH$PLEASE_CHECK_EMAIL_TO_VERIFY"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Verify that the query parameter was processed by checking the modal appeared
|
||||
// The hook removes the parameter from the URL, so we verify the behavior indirectly
|
||||
expect(container).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not display EmailVerificationModal when email_verification_required is not in query params", async () => {
|
||||
// Arrange - No query params set
|
||||
|
||||
// Act
|
||||
render(<RouterStub />, { wrapper: createWrapper() });
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByText("AUTH$PLEASE_CHECK_EMAIL_TO_VERIFY"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not display email verified message when email_verified is not in query params", async () => {
|
||||
// Arrange
|
||||
// Mock a 401 error to simulate unauthenticated user
|
||||
const axiosError = {
|
||||
response: { status: 401 },
|
||||
isAxiosError: true,
|
||||
};
|
||||
vi.spyOn(AuthService, "authenticate").mockRejectedValue(axiosError);
|
||||
|
||||
// Act
|
||||
render(<RouterStub />, { wrapper: createWrapper() });
|
||||
|
||||
// Assert - AuthModal should render but without email verified message
|
||||
await waitFor(() => {
|
||||
const authModal = screen.queryByText(
|
||||
"AUTH$SIGN_IN_WITH_IDENTITY_PROVIDER",
|
||||
);
|
||||
if (authModal) {
|
||||
expect(
|
||||
screen.queryByText("AUTH$EMAIL_VERIFIED_PLEASE_LOGIN"),
|
||||
).not.toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSearchParams } from "react-router";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import OpenHandsLogo from "#/assets/branding/openhands-logo.svg?react";
|
||||
import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop";
|
||||
@@ -14,12 +13,15 @@ import { useAuthUrl } from "#/hooks/use-auth-url";
|
||||
import { GetConfigResponse } from "#/api/option-service/option.types";
|
||||
import { Provider } from "#/types/settings";
|
||||
import { useTracking } from "#/hooks/use-tracking";
|
||||
import { TermsAndPrivacyNotice } from "#/components/shared/terms-and-privacy-notice";
|
||||
|
||||
interface AuthModalProps {
|
||||
githubAuthUrl: string | null;
|
||||
appMode?: GetConfigResponse["APP_MODE"] | null;
|
||||
authUrl?: GetConfigResponse["AUTH_URL"];
|
||||
providersConfigured?: Provider[];
|
||||
emailVerified?: boolean;
|
||||
hasDuplicatedEmail?: boolean;
|
||||
}
|
||||
|
||||
export function AuthModal({
|
||||
@@ -27,11 +29,11 @@ export function AuthModal({
|
||||
appMode,
|
||||
authUrl,
|
||||
providersConfigured,
|
||||
emailVerified = false,
|
||||
hasDuplicatedEmail = false,
|
||||
}: AuthModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const { trackLoginButtonClick } = useTracking();
|
||||
const [searchParams] = useSearchParams();
|
||||
const hasDuplicatedEmail = searchParams.get("duplicated_email") === "true";
|
||||
|
||||
const gitlabAuthUrl = useAuthUrl({
|
||||
appMode: appMode || null,
|
||||
@@ -126,6 +128,13 @@ export function AuthModal({
|
||||
<ModalBackdrop>
|
||||
<ModalBody className="border border-tertiary">
|
||||
<OpenHandsLogo width={68} height={46} />
|
||||
{emailVerified && (
|
||||
<div className="flex flex-col gap-2 w-full items-center text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t(I18nKey.AUTH$EMAIL_VERIFIED_PLEASE_LOGIN)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{hasDuplicatedEmail && (
|
||||
<div className="text-center text-danger text-sm mt-2 mb-2">
|
||||
{t(I18nKey.AUTH$DUPLICATE_EMAIL_ERROR)}
|
||||
@@ -206,30 +215,7 @@ export function AuthModal({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p
|
||||
className="mt-4 text-xs text-center text-muted-foreground"
|
||||
data-testid="auth-modal-terms-of-service"
|
||||
>
|
||||
{t(I18nKey.AUTH$BY_SIGNING_UP_YOU_AGREE_TO_OUR)}{" "}
|
||||
<a
|
||||
href="https://www.all-hands.dev/tos"
|
||||
target="_blank"
|
||||
className="underline hover:text-primary"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t(I18nKey.COMMON$TERMS_OF_SERVICE)}
|
||||
</a>{" "}
|
||||
{t(I18nKey.COMMON$AND)}{" "}
|
||||
<a
|
||||
href="https://www.all-hands.dev/privacy"
|
||||
target="_blank"
|
||||
className="underline hover:text-primary"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t(I18nKey.COMMON$PRIVACY_POLICY)}
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<TermsAndPrivacyNotice />
|
||||
</ModalBody>
|
||||
</ModalBackdrop>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import OpenHandsLogo from "#/assets/branding/openhands-logo.svg?react";
|
||||
import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop";
|
||||
import { ModalBody } from "#/components/shared/modals/modal-body";
|
||||
import { TermsAndPrivacyNotice } from "#/components/shared/terms-and-privacy-notice";
|
||||
|
||||
interface EmailVerificationModalProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function EmailVerificationModal({
|
||||
onClose,
|
||||
}: EmailVerificationModalProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<ModalBackdrop onClose={onClose}>
|
||||
<ModalBody className="border border-tertiary">
|
||||
<OpenHandsLogo width={68} height={46} />
|
||||
<div className="flex flex-col gap-2 w-full items-center text-center">
|
||||
<h1 className="text-2xl font-bold">
|
||||
{t(I18nKey.AUTH$PLEASE_CHECK_EMAIL_TO_VERIFY)}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<TermsAndPrivacyNotice />
|
||||
</ModalBody>
|
||||
</ModalBackdrop>
|
||||
);
|
||||
}
|
||||
37
frontend/src/components/shared/terms-and-privacy-notice.tsx
Normal file
37
frontend/src/components/shared/terms-and-privacy-notice.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
interface TermsAndPrivacyNoticeProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function TermsAndPrivacyNotice({
|
||||
className = "mt-4 text-xs text-center text-muted-foreground",
|
||||
}: TermsAndPrivacyNoticeProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<p className={className} data-testid="terms-and-privacy-notice">
|
||||
{t(I18nKey.AUTH$BY_SIGNING_UP_YOU_AGREE_TO_OUR)}{" "}
|
||||
<a
|
||||
href="https://www.all-hands.dev/tos"
|
||||
target="_blank"
|
||||
className="underline hover:text-primary"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t(I18nKey.COMMON$TERMS_OF_SERVICE)}
|
||||
</a>{" "}
|
||||
{t(I18nKey.COMMON$AND)}{" "}
|
||||
<a
|
||||
href="https://www.all-hands.dev/privacy"
|
||||
target="_blank"
|
||||
className="underline hover:text-primary"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t(I18nKey.COMMON$PRIVACY_POLICY)}
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
63
frontend/src/hooks/use-email-verification.ts
Normal file
63
frontend/src/hooks/use-email-verification.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import React from "react";
|
||||
import { useSearchParams } from "react-router";
|
||||
|
||||
/**
|
||||
* Hook to handle email verification logic from URL query parameters.
|
||||
* Manages the email verification modal state and email verified state
|
||||
* based on query parameters in the URL.
|
||||
*
|
||||
* @returns An object containing:
|
||||
* - emailVerificationModalOpen: boolean state for modal visibility
|
||||
* - setEmailVerificationModalOpen: function to control modal visibility
|
||||
* - emailVerified: boolean state for email verification status
|
||||
* - setEmailVerified: function to control email verification status
|
||||
* - hasDuplicatedEmail: boolean state for duplicate email error status
|
||||
*/
|
||||
export function useEmailVerification() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [emailVerificationModalOpen, setEmailVerificationModalOpen] =
|
||||
React.useState(false);
|
||||
const [emailVerified, setEmailVerified] = React.useState(false);
|
||||
const [hasDuplicatedEmail, setHasDuplicatedEmail] = React.useState(false);
|
||||
|
||||
// Check for email verification query parameters
|
||||
React.useEffect(() => {
|
||||
const emailVerificationRequired = searchParams.get(
|
||||
"email_verification_required",
|
||||
);
|
||||
const emailVerifiedParam = searchParams.get("email_verified");
|
||||
const duplicatedEmailParam = searchParams.get("duplicated_email");
|
||||
let shouldUpdate = false;
|
||||
|
||||
if (emailVerificationRequired === "true") {
|
||||
setEmailVerificationModalOpen(true);
|
||||
searchParams.delete("email_verification_required");
|
||||
shouldUpdate = true;
|
||||
}
|
||||
|
||||
if (emailVerifiedParam === "true") {
|
||||
setEmailVerified(true);
|
||||
searchParams.delete("email_verified");
|
||||
shouldUpdate = true;
|
||||
}
|
||||
|
||||
if (duplicatedEmailParam === "true") {
|
||||
setHasDuplicatedEmail(true);
|
||||
searchParams.delete("duplicated_email");
|
||||
shouldUpdate = true;
|
||||
}
|
||||
|
||||
// Clean up the URL by removing parameters if any were found
|
||||
if (shouldUpdate) {
|
||||
setSearchParams(searchParams, { replace: true });
|
||||
}
|
||||
}, [searchParams, setSearchParams]);
|
||||
|
||||
return {
|
||||
emailVerificationModalOpen,
|
||||
setEmailVerificationModalOpen,
|
||||
emailVerified,
|
||||
setEmailVerified,
|
||||
hasDuplicatedEmail,
|
||||
};
|
||||
}
|
||||
@@ -730,6 +730,8 @@ export enum I18nKey {
|
||||
MICROAGENT_MANAGEMENT$USE_MICROAGENTS = "MICROAGENT_MANAGEMENT$USE_MICROAGENTS",
|
||||
AUTH$BY_SIGNING_UP_YOU_AGREE_TO_OUR = "AUTH$BY_SIGNING_UP_YOU_AGREE_TO_OUR",
|
||||
AUTH$NO_PROVIDERS_CONFIGURED = "AUTH$NO_PROVIDERS_CONFIGURED",
|
||||
AUTH$PLEASE_CHECK_EMAIL_TO_VERIFY = "AUTH$PLEASE_CHECK_EMAIL_TO_VERIFY",
|
||||
AUTH$EMAIL_VERIFIED_PLEASE_LOGIN = "AUTH$EMAIL_VERIFIED_PLEASE_LOGIN",
|
||||
AUTH$DUPLICATE_EMAIL_ERROR = "AUTH$DUPLICATE_EMAIL_ERROR",
|
||||
COMMON$TERMS_OF_SERVICE = "COMMON$TERMS_OF_SERVICE",
|
||||
COMMON$AND = "COMMON$AND",
|
||||
|
||||
@@ -11679,6 +11679,38 @@
|
||||
"de": "Mindestens ein Identitätsanbieter muss konfiguriert werden (z.B. GitHub)",
|
||||
"uk": "Принаймні один постачальник ідентифікації має бути налаштований (наприклад, GitHub)"
|
||||
},
|
||||
"AUTH$PLEASE_CHECK_EMAIL_TO_VERIFY": {
|
||||
"en": "Please check your email to verify your account.",
|
||||
"ja": "アカウントを確認するためにメールを確認してください。",
|
||||
"zh-CN": "请检查您的电子邮件以验证您的账户。",
|
||||
"zh-TW": "請檢查您的電子郵件以驗證您的帳戶。",
|
||||
"ko-KR": "계정을 확인하려면 이메일을 확인하세요.",
|
||||
"no": "Vennligst sjekk e-posten din for å bekrefte kontoen din.",
|
||||
"it": "Controlla la tua email per verificare il tuo account.",
|
||||
"pt": "Por favor, verifique seu e-mail para verificar sua conta.",
|
||||
"es": "Por favor, verifica tu correo electrónico para verificar tu cuenta.",
|
||||
"ar": "يرجى التحقق من بريدك الإلكتروني للتحقق من حسابك.",
|
||||
"fr": "Veuillez vérifier votre e-mail pour vérifier votre compte.",
|
||||
"tr": "Hesabınızı doğrulamak için lütfen e-postanızı kontrol edin.",
|
||||
"de": "Bitte überprüfen Sie Ihre E-Mail, um Ihr Konto zu verifizieren.",
|
||||
"uk": "Будь ласка, перевірте свою електронну пошту, щоб підтвердити свій обліковий запис."
|
||||
},
|
||||
"AUTH$EMAIL_VERIFIED_PLEASE_LOGIN": {
|
||||
"en": "Your email has been verified. Please login below.",
|
||||
"ja": "メールアドレスが確認されました。下記からログインしてください。",
|
||||
"zh-CN": "您的电子邮件已验证。请在下方登录。",
|
||||
"zh-TW": "您的電子郵件已驗證。請在下方登錄。",
|
||||
"ko-KR": "이메일이 확인되었습니다. 아래에서 로그인하세요.",
|
||||
"no": "E-posten din er bekreftet. Vennligst logg inn nedenfor.",
|
||||
"it": "La tua email è stata verificata. Effettua il login qui sotto.",
|
||||
"pt": "Seu e-mail foi verificado. Por favor, faça login abaixo.",
|
||||
"es": "Tu correo electrónico ha sido verificado. Por favor, inicia sesión a continuación.",
|
||||
"ar": "تم التحقق من بريدك الإلكتروني. يرجى تسجيل الدخول أدناه.",
|
||||
"fr": "Votre e-mail a été vérifié. Veuillez vous connecter ci-dessous.",
|
||||
"tr": "E-postanız doğrulandı. Lütfen aşağıdan giriş yapın.",
|
||||
"de": "Ihre E-Mail wurde verifiziert. Bitte melden Sie sich unten an.",
|
||||
"uk": "Вашу електронну пошту підтверджено. Будь ласка, увійдіть нижче."
|
||||
},
|
||||
"AUTH$DUPLICATE_EMAIL_ERROR": {
|
||||
"en": "Your account is unable to be created. Please use a different login or try again.",
|
||||
"ja": "アカウントを作成できません。別のログインを使用するか、もう一度お試しください。",
|
||||
|
||||
@@ -15,6 +15,7 @@ import { useConfig } from "#/hooks/query/use-config";
|
||||
import { Sidebar } from "#/components/features/sidebar/sidebar";
|
||||
import { AuthModal } from "#/components/features/waitlist/auth-modal";
|
||||
import { ReauthModal } from "#/components/features/waitlist/reauth-modal";
|
||||
import { EmailVerificationModal } from "#/components/features/waitlist/email-verification-modal";
|
||||
import { AnalyticsConsentFormModal } from "#/components/features/analytics/analytics-consent-form-modal";
|
||||
import { useSettings } from "#/hooks/query/use-settings";
|
||||
import { useMigrateUserConsent } from "#/hooks/use-migrate-user-consent";
|
||||
@@ -26,6 +27,7 @@ import { useAutoLogin } from "#/hooks/use-auto-login";
|
||||
import { useAuthCallback } from "#/hooks/use-auth-callback";
|
||||
import { useReoTracking } from "#/hooks/use-reo-tracking";
|
||||
import { useSyncPostHogConsent } from "#/hooks/use-sync-posthog-consent";
|
||||
import { useEmailVerification } from "#/hooks/use-email-verification";
|
||||
import { LOCAL_STORAGE_KEYS } from "#/utils/local-storage";
|
||||
import { EmailVerificationGuard } from "#/components/features/guards/email-verification-guard";
|
||||
import { MaintenanceBanner } from "#/components/features/maintenance/maintenance-banner";
|
||||
@@ -91,6 +93,12 @@ export default function MainApp() {
|
||||
const effectiveGitHubAuthUrl = isOnTosPage ? null : gitHubAuthUrl;
|
||||
|
||||
const [consentFormIsOpen, setConsentFormIsOpen] = React.useState(false);
|
||||
const {
|
||||
emailVerificationModalOpen,
|
||||
setEmailVerificationModalOpen,
|
||||
emailVerified,
|
||||
hasDuplicatedEmail,
|
||||
} = useEmailVerification();
|
||||
|
||||
// Auto-login if login method is stored in local storage
|
||||
useAutoLogin();
|
||||
@@ -236,9 +244,18 @@ export default function MainApp() {
|
||||
appMode={config.data?.APP_MODE}
|
||||
providersConfigured={config.data?.PROVIDERS_CONFIGURED}
|
||||
authUrl={config.data?.AUTH_URL}
|
||||
emailVerified={emailVerified}
|
||||
hasDuplicatedEmail={hasDuplicatedEmail}
|
||||
/>
|
||||
)}
|
||||
{renderReAuthModal && <ReauthModal />}
|
||||
{emailVerificationModalOpen && (
|
||||
<EmailVerificationModal
|
||||
onClose={() => {
|
||||
setEmailVerificationModalOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{config.data?.APP_MODE === "oss" && consentFormIsOpen && (
|
||||
<AnalyticsConsentFormModal
|
||||
onClose={() => {
|
||||
|
||||
Reference in New Issue
Block a user