Use compliance page logos for auth layout
Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -1,17 +1,48 @@
|
|||||||
|
import { useSystemTheme } from "@probo/hooks";
|
||||||
import { Logo } from "@probo/ui";
|
import { Logo } from "@probo/ui";
|
||||||
import type { PropsWithChildren } from "react";
|
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
|
||||||
|
import { Outlet } from "react-router";
|
||||||
|
import { graphql } from "relay-runtime";
|
||||||
|
|
||||||
export function AuthLayout(props: PropsWithChildren) {
|
import type { AuthLayoutQuery } from "./__generated__/AuthLayoutQuery.graphql";
|
||||||
const { children } = props;
|
|
||||||
|
export const authLayoutQuery = graphql`
|
||||||
|
query AuthLayoutQuery {
|
||||||
|
currentTrustCenter @required(action: THROW) {
|
||||||
|
logoFileUrl
|
||||||
|
darkLogoFileUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function AuthLayout(props: { queryRef: PreloadedQuery<AuthLayoutQuery> }) {
|
||||||
|
const { queryRef } = props;
|
||||||
|
|
||||||
|
const { currentTrustCenter: compliancePage } = usePreloadedQuery<AuthLayoutQuery>(authLayoutQuery, queryRef);
|
||||||
|
const theme = useSystemTheme();
|
||||||
|
|
||||||
|
const logoFileUrl = theme === "dark"
|
||||||
|
? compliancePage.darkLogoFileUrl ?? compliancePage.logoFileUrl
|
||||||
|
: compliancePage.logoFileUrl;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 min-h-screen text-txt-primary">
|
<div className="grid grid-cols-1 lg:grid-cols-2 min-h-screen text-txt-primary">
|
||||||
<div className="bg-level-0 flex flex-col items-center justify-center">
|
<div className="bg-level-0 flex flex-col items-center justify-center">
|
||||||
<div className="w-full max-w-md px-6">{children}</div>
|
<div className="w-full max-w-md px-6">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden lg:flex bg-dialog font-bold flex-col items-center justify-center p-8 text-txt-primary lg:p-10">
|
<div className="hidden lg:flex bg-dialog font-bold flex-col items-center justify-center p-8 text-txt-primary lg:p-10">
|
||||||
<div className="flex flex-col items-center justify-center gap-4">
|
<div className="flex flex-col items-center justify-center gap-4">
|
||||||
<Logo withPicto className="w-[440px]" />
|
{logoFileUrl
|
||||||
|
? (
|
||||||
|
<img
|
||||||
|
alt=""
|
||||||
|
src={logoFileUrl}
|
||||||
|
className="size-110 rounded-2xl"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: <Logo withPicto className="w-110" />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
29
apps/trust/src/pages/auth/AuthLayoutLoader.tsx
Normal file
29
apps/trust/src/pages/auth/AuthLayoutLoader.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useQueryLoader } from "react-relay";
|
||||||
|
|
||||||
|
import { RelayProvider } from "#/providers/RelayProviders";
|
||||||
|
|
||||||
|
import type { AuthLayoutQuery } from "./__generated__/AuthLayoutQuery.graphql";
|
||||||
|
import { AuthLayout, authLayoutQuery } from "./AuthLayout";
|
||||||
|
|
||||||
|
function AuthLayoutQueryLoader() {
|
||||||
|
const [queryRef, loadQuery] = useQueryLoader<AuthLayoutQuery>(authLayoutQuery);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!queryRef) {
|
||||||
|
return loadQuery({});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!queryRef) return null;
|
||||||
|
|
||||||
|
return <AuthLayout queryRef={queryRef} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AuthLayoutLoader() {
|
||||||
|
return (
|
||||||
|
<RelayProvider>
|
||||||
|
<AuthLayoutQueryLoader />
|
||||||
|
</RelayProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,7 +14,6 @@ import { useFormWithSchema } from "#/hooks/useFormWithSchema";
|
|||||||
|
|
||||||
import type { ConnectPageMutation } from "./__generated__/ConnectPageMutation.graphql";
|
import type { ConnectPageMutation } from "./__generated__/ConnectPageMutation.graphql";
|
||||||
import type { ConnectPageQuery } from "./__generated__/ConnectPageQuery.graphql";
|
import type { ConnectPageQuery } from "./__generated__/ConnectPageQuery.graphql";
|
||||||
import { AuthLayout } from "./AuthLayout";
|
|
||||||
|
|
||||||
export const connectPageQuery = graphql`
|
export const connectPageQuery = graphql`
|
||||||
query ConnectPageQuery {
|
query ConnectPageQuery {
|
||||||
@@ -126,49 +125,47 @@ export function ConnectPage(props: {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthLayout>
|
<div className="space-y-6 w-full max-w-md mx-auto">
|
||||||
<div className="space-y-6 w-full max-w-md mx-auto">
|
<div className="space-y-2 text-center">
|
||||||
<div className="space-y-2 text-center">
|
<h1 className="text-3xl font-bold">
|
||||||
<h1 className="text-3xl font-bold">
|
{__(`Connect to ${organization.name}'s compliance page`)}
|
||||||
{__(`Connect to ${organization.name}'s compliance page`)}
|
</h1>
|
||||||
</h1>
|
<p className="text-txt-tertiary">
|
||||||
<p className="text-txt-tertiary">
|
{__(
|
||||||
|
"Enter your email address to connect with a magic link and start requesting access to documents",
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={e => void handleSubmit(e)} className="space-y-4">
|
||||||
|
<Field
|
||||||
|
label={__("Email")}
|
||||||
|
placeholder="john.doe@acme.com"
|
||||||
|
{...register("email")}
|
||||||
|
type="email"
|
||||||
|
error={formState.errors.email?.message}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{magicLinkSent && (
|
||||||
|
<p className="text-txt-primary text-sm">
|
||||||
{__(
|
{__(
|
||||||
"Enter your email address to connect with a magic link and start requesting access to documents",
|
"Magic Link Sent! Check your emails and use the link to connect.",
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
<form onSubmit={e => void handleSubmit(e)} className="space-y-4">
|
<Button
|
||||||
<Field
|
type="submit"
|
||||||
label={__("Email")}
|
className="w-full"
|
||||||
placeholder="john.doe@acme.com"
|
disabled={formState.isSubmitting || (magicLinkSent && timer !== 0)}
|
||||||
{...register("email")}
|
>
|
||||||
type="email"
|
{magicLinkSent
|
||||||
error={formState.errors.email?.message}
|
? timer === 0
|
||||||
/>
|
? __("Resend Link")
|
||||||
|
: `${__("Resend Link in")} ${timer}s`
|
||||||
{magicLinkSent && (
|
: __("Send Magic Link")}
|
||||||
<p className="text-txt-primary text-sm">
|
</Button>
|
||||||
{__(
|
</form>
|
||||||
"Magic Link Sent! Check your emails and use the link to connect.",
|
</div>
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
className="w-full"
|
|
||||||
disabled={formState.isSubmitting || (magicLinkSent && timer !== 0)}
|
|
||||||
>
|
|
||||||
{magicLinkSent
|
|
||||||
? timer === 0
|
|
||||||
? __("Resend Link")
|
|
||||||
: `${__("Resend Link in")} ${timer}s`
|
|
||||||
: __("Send Magic Link")}
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</AuthLayout>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import { useFormWithSchema } from "#/hooks/useFormWithSchema";
|
|||||||
import { getPathPrefix } from "#/utils/pathPrefix";
|
import { getPathPrefix } from "#/utils/pathPrefix";
|
||||||
|
|
||||||
import type { VerifyMagicLinkPageMutation } from "./__generated__/VerifyMagicLinkPageMutation.graphql";
|
import type { VerifyMagicLinkPageMutation } from "./__generated__/VerifyMagicLinkPageMutation.graphql";
|
||||||
import { AuthLayout } from "./AuthLayout";
|
|
||||||
|
|
||||||
const verifyMagicLinkMutation = graphql`
|
const verifyMagicLinkMutation = graphql`
|
||||||
mutation VerifyMagicLinkPageMutation($input: VerifyMagicLinkInput!) {
|
mutation VerifyMagicLinkPageMutation($input: VerifyMagicLinkInput!) {
|
||||||
@@ -87,39 +86,37 @@ export default function VerifyMagicLinkPagePageMutation() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthLayout>
|
<div className="space-y-6 w-full max-w-md mx-auto">
|
||||||
<div className="space-y-6 w-full max-w-md mx-auto">
|
<div className="space-y-2 text-center">
|
||||||
<div className="space-y-2 text-center">
|
<h1 className="text-3xl font-bold">{__("Email Confirmation")}</h1>
|
||||||
<h1 className="text-3xl font-bold">{__("Email Confirmation")}</h1>
|
<p className="text-txt-tertiary">
|
||||||
<p className="text-txt-tertiary">
|
{__("Confirm your email address to complete registration")}
|
||||||
{__("Confirm your email address to complete registration")}
|
</p>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form onSubmit={e => void handleSubmit(e)} className="space-y-4">
|
|
||||||
<Field
|
|
||||||
label={__("Confirmation Token")}
|
|
||||||
type="text"
|
|
||||||
placeholder={__("Enter your confirmation token")}
|
|
||||||
{...form.register("token")}
|
|
||||||
error={form.formState.errors.token?.message}
|
|
||||||
disabled={form.formState.isSubmitting}
|
|
||||||
help={__(
|
|
||||||
"The token has been automatically filled from the URL if available",
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
className="w-full"
|
|
||||||
disabled={form.formState.isSubmitting}
|
|
||||||
>
|
|
||||||
{form.formState.isSubmitting
|
|
||||||
? __("Confirming...")
|
|
||||||
: __("Confirm Email")}
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</AuthLayout>
|
|
||||||
|
<form onSubmit={e => void handleSubmit(e)} className="space-y-4">
|
||||||
|
<Field
|
||||||
|
label={__("Confirmation Token")}
|
||||||
|
type="text"
|
||||||
|
placeholder={__("Enter your confirmation token")}
|
||||||
|
{...form.register("token")}
|
||||||
|
error={form.formState.errors.token?.message}
|
||||||
|
disabled={form.formState.isSubmitting}
|
||||||
|
help={__(
|
||||||
|
"The token has been automatically filled from the URL if available",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="w-full"
|
||||||
|
disabled={form.formState.isSubmitting}
|
||||||
|
>
|
||||||
|
{form.formState.isSubmitting
|
||||||
|
? __("Confirming...")
|
||||||
|
: __("Confirm Email")}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
108
apps/trust/src/pages/auth/__generated__/AuthLayoutQuery.graphql.ts
generated
Normal file
108
apps/trust/src/pages/auth/__generated__/AuthLayoutQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<c3e41c1d38cd6923e718ea03bba5bb44>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type AuthLayoutQuery$variables = Record<PropertyKey, never>;
|
||||||
|
export type AuthLayoutQuery$data = {
|
||||||
|
readonly currentTrustCenter: {
|
||||||
|
readonly darkLogoFileUrl: string | null | undefined;
|
||||||
|
readonly logoFileUrl: string | null | undefined;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type AuthLayoutQuery = {
|
||||||
|
response: AuthLayoutQuery$data;
|
||||||
|
variables: AuthLayoutQuery$variables;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ConcreteRequest = (function(){
|
||||||
|
var v0 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "logoFileUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
v1 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "darkLogoFileUrl",
|
||||||
|
"storageKey": null
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": [],
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "AuthLayoutQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"kind": "RequiredField",
|
||||||
|
"field": {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "TrustCenter",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "currentTrustCenter",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v0/*: any*/),
|
||||||
|
(v1/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
"action": "THROW"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Query",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": [],
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "AuthLayoutQuery",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "TrustCenter",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "currentTrustCenter",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v0/*: any*/),
|
||||||
|
(v1/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "92bbf193e99aa98673d1926bd1fdc890",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "AuthLayoutQuery",
|
||||||
|
"operationKind": "query",
|
||||||
|
"text": "query AuthLayoutQuery {\n currentTrustCenter {\n logoFileUrl\n darkLogoFileUrl\n id\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "d76b10f9b919f4224c4c8b478e66b4f1";
|
||||||
|
|
||||||
|
export default node;
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
|
import { lazy } from "@probo/react-lazy";
|
||||||
import {
|
import {
|
||||||
type AppRoute,
|
type AppRoute,
|
||||||
loaderFromQueryLoader,
|
loaderFromQueryLoader,
|
||||||
routeFromAppRoute,
|
routeFromAppRoute,
|
||||||
withQueryRef,
|
withQueryRef,
|
||||||
} from "@probo/routes";
|
} from "@probo/routes";
|
||||||
import { Fragment, lazy } from "react";
|
import { Fragment } from "react";
|
||||||
import { loadQuery } from "react-relay";
|
import { loadQuery } from "react-relay";
|
||||||
import { createBrowserRouter, redirect, useRouteError } from "react-router";
|
import { createBrowserRouter, redirect, useRouteError } from "react-router";
|
||||||
|
|
||||||
@@ -34,12 +35,17 @@ function ErrorBoundary() {
|
|||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: "/connect",
|
Component: lazy(() => import("#/pages/auth/AuthLayoutLoader")),
|
||||||
Component: lazy(() => import("./pages/auth/ConnectPageLoader")),
|
children: [
|
||||||
},
|
{
|
||||||
{
|
path: "/connect",
|
||||||
path: "/verify-magic-link",
|
Component: lazy(() => import("#/pages/auth/ConnectPageLoader")),
|
||||||
Component: lazy(() => import("./pages/auth/VerifyMagicLinkPage")),
|
},
|
||||||
|
{
|
||||||
|
path: "/verify-magic-link",
|
||||||
|
Component: lazy(() => import("#/pages/auth/VerifyMagicLinkPage")),
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
|
|||||||
Reference in New Issue
Block a user