Skip to content

Commit

Permalink
feat: read auth card props from context, simplify UI config (#789)
Browse files Browse the repository at this point in the history
  • Loading branch information
bswags authored Jul 10, 2024
1 parent 507d58f commit ac44833
Show file tree
Hide file tree
Showing 24 changed files with 240 additions and 192 deletions.
9 changes: 5 additions & 4 deletions account-kit/react/src/components/auth/card/add-passkey.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useAddPasskey } from "../../../hooks/useAddPasskey.js";
import { useUiConfig } from "../../../hooks/useUiConfig.js";
import { AddPasskeyIllustration } from "../../../icons/illustrations/add-passkey.js";
import {
PasskeyShieldIllustration,
Expand All @@ -8,7 +9,6 @@ import { ls } from "../../../strings.js";
import { Button } from "../../button.js";
import { PoweredBy } from "../../poweredby.js";
import { useAuthContext } from "../context.js";
import type { AuthCardProps } from "./index.js";

const BENEFITS = [
{
Expand All @@ -24,7 +24,8 @@ const BENEFITS = [
];

// eslint-disable-next-line jsdoc/require-jsdoc
export const AddPasskey = ({ config }: { config: AuthCardProps }) => {
export const AddPasskey = () => {
const { illustrationStyle } = useUiConfig();
const { setAuthStep } = useAuthContext();
const { addPasskey, isAddingPasskey } = useAddPasskey({
onSuccess: () => {
Expand All @@ -36,7 +37,7 @@ export const AddPasskey = ({ config }: { config: AuthCardProps }) => {
<div className="flex flex-col gap-5 items-center">
<div className="flex flex-col items-center justify-center h-12 w-12">
<AddPasskeyIllustration
illustrationStyle={config.illustrationStyle ?? "flat"}
illustrationStyle={illustrationStyle ?? "flat"}
height="48"
width="48"
/>
Expand All @@ -48,7 +49,7 @@ export const AddPasskey = ({ config }: { config: AuthCardProps }) => {
{BENEFITS.map(({ title, icon: Icon, description }) => (
<div key={title} className="flex gap-2">
<div className="h-5 w-5 flex items-center justify-center">
<Icon illustrationStyle={config.illustrationStyle ?? "flat"} />
<Icon illustrationStyle={illustrationStyle ?? "flat"} />
</div>
<div className="flex flex-col">
<p className="font-semibold text-sm">{title}</p>
Expand Down
63 changes: 36 additions & 27 deletions account-kit/react/src/components/auth/card/index.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,53 @@
import {
useCallback,
useLayoutEffect,
useMemo,
useRef,
type ReactNode,
} from "react";
import { useCallback, useLayoutEffect, useMemo, useRef } from "react";
import { useSignerStatus } from "../../../hooks/useSignerStatus.js";
import { IS_SIGNUP_QP } from "../../constants.js";
import { useAuthContext } from "../context.js";
import type { AuthIllustrationStyle, AuthType } from "../types.js";
import { Step } from "./steps.js";
import { Notification } from "../../notification.js";
import { useAuthError } from "../../../hooks/useAuthError.js";
import { Navigation } from "../../navigation.js";
import { useAuthModal } from "../../../hooks/useAuthModal.js";
import { useElementHeight } from "../../../hooks/useElementHeight.js";
import { useUiConfig } from "../../../hooks/useUiConfig.js";

export type AuthCardProps = {
hideError?: boolean;
header?: ReactNode;
showSignInText?: boolean;
illustrationStyle?: AuthIllustrationStyle;
// Each section can contain multiple auth types which will be grouped together
// and separated by an OR divider
sections?: AuthType[][];
className?: string;
onAuthSuccess?: () => void;
};

/**
* React component containing an Auth view with configured auth methods
* and options based on the config passed to the AlchemyAccountProvider
*
* @param props Card Props
* @param props.header optional header for the card (good place to put your app name or logo)
* @param props.showSignInText optional boolean to show the sign in text (defaults to true)
* @param props.sections array of sections, each containing an array of auth types
* @param {AuthCardProps} props Card Props
* @param {string} props.className optional class name to apply to the card
* @returns a react component containing the AuthCard
*/
export const AuthCard = (
props: AuthCardProps & { showNavigation?: boolean; showClose?: boolean }
) => {
const { showClose = false, onAuthSuccess, hideError } = props;
export const AuthCard = (props: AuthCardProps) => {
return <AuthCardContent {...props} />;
};

// this isn't used externally
// eslint-disable-next-line jsdoc/require-jsdoc
export const AuthCardContent = ({
className,
showClose = false,
}: {
className?: string;
showClose?: boolean;
}) => {
const { closeAuthModal } = useAuthModal();
const { status, isAuthenticating } = useSignerStatus();
const { authStep, setAuthStep } = useAuthContext();

const error = useAuthError();

const contentRef = useRef<HTMLDivElement>(null);
const { height } = useElementHeight(contentRef);

const { auth } = useUiConfig();
const hideError = auth?.hideError;
const onAuthSuccess = auth?.onAuthSuccess;

// TODO: Finalize the steps that allow going back
const canGoBack = useMemo(() => {
return ["email_verify"].includes(authStep.type);
Expand All @@ -66,6 +65,7 @@ export const AuthCard = (

useLayoutEffect(() => {
if (authStep.type === "complete") {
closeAuthModal();
onAuthSuccess?.();
} else if (isAuthenticating && authStep.type === "initial") {
const urlParams = new URLSearchParams(window.location.search);
Expand All @@ -75,7 +75,14 @@ export const AuthCard = (
createPasskeyAfter: urlParams.get(IS_SIGNUP_QP) === "true",
});
}
}, [authStep, status, isAuthenticating, setAuthStep, onAuthSuccess]);
}, [
authStep,
status,
isAuthenticating,
setAuthStep,
onAuthSuccess,
closeAuthModal,
]);

return (
<div className="relative">
Expand All @@ -95,7 +102,9 @@ export const AuthCard = (
>
<div
ref={contentRef}
className="modal-box relative flex flex-col items-center gap-5 text-fg-primary"
className={`modal-box relative flex flex-col items-center gap-5 text-fg-primary ${
className ?? ""
}`}
>
{(canGoBack || showClose) && (
<Navigation
Expand All @@ -105,7 +114,7 @@ export const AuthCard = (
onClose={closeAuthModal}
/>
)}
<Step {...props} />
<Step />
</div>
</div>
</div>
Expand Down
9 changes: 5 additions & 4 deletions account-kit/react/src/components/auth/card/loading/email.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@ import { useAuthContext, type AuthStep } from "../../context.js";
import { Spinner } from "../../../../icons/spinner.js";
import { ls } from "../../../../strings.js";
import { EmailIllustration } from "../../../../icons/illustrations/email.js";
import type { AuthCardProps } from "../index.js";
import { useUiConfig } from "../../../../hooks/useUiConfig.js";

interface LoadingEmailProps {
config: AuthCardProps;
context: Extract<AuthStep, { type: "email_verify" }>;
}

// eslint-disable-next-line jsdoc/require-jsdoc
export const LoadingEmail = ({ config, context }: LoadingEmailProps) => {
export const LoadingEmail = ({ context }: LoadingEmailProps) => {
// yup, re-sent and resent. I'm not fixing it
const [emailResent, setEmailResent] = useState(false);

const { illustrationStyle } = useUiConfig();
const { setAuthStep } = useAuthContext();
const { authenticate } = useAuthenticate({
onSuccess: () => {
Expand All @@ -38,7 +39,7 @@ export const LoadingEmail = ({ config, context }: LoadingEmailProps) => {
<div className="flex flex-col gap-5 items-center">
<div className="flex flex-col items-center justify-center h-12 w-12">
<EmailIllustration
illustrationStyle={config.illustrationStyle ?? "flat"}
illustrationStyle={illustrationStyle ?? "flat"}
height="48"
width="48"
className="animate-pulse"
Expand Down
10 changes: 4 additions & 6 deletions account-kit/react/src/components/auth/card/loading/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import type { AuthCardProps } from "..";
import type { AuthStep } from "../../context";
import type { AuthStep } from "../../context.js";
import { CompletingEmailAuth, LoadingEmail } from "./email.js";
import { LoadingPasskeyAuth } from "./passkey.js";

type LoadingAuthProps = {
config: AuthCardProps;
context?: AuthStep;
};

// eslint-disable-next-line jsdoc/require-jsdoc
export const LoadingAuth = ({ context, config }: LoadingAuthProps) => {
export const LoadingAuth = ({ context }: LoadingAuthProps) => {
switch (context?.type) {
case "email_verify":
return <LoadingEmail config={config} context={context} />;
return <LoadingEmail context={context} />;
case "passkey_verify":
return <LoadingPasskeyAuth config={config} />;
return <LoadingPasskeyAuth />;
case "email_completing":
return <CompletingEmailAuth context={context} />;
default: {
Expand Down
10 changes: 5 additions & 5 deletions account-kit/react/src/components/auth/card/loading/passkey.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ import { ls } from "../../../../strings.js";
import { LoadingPasskey } from "../../../../icons/passkey.js";
import { Button } from "../../../button.js";
import { PoweredBy } from "../../../poweredby.js";
import type { AuthCardProps } from "../index.js";
import { useUiConfig } from "../../../../hooks/useUiConfig.js";

// eslint-disable-next-line jsdoc/require-jsdoc
export const LoadingPasskeyAuth = ({ config }: { config: AuthCardProps }) => {
export const LoadingPasskeyAuth = () => {
const { illustrationStyle } = useUiConfig();

return (
<div className="flex flex-col gap-5 items-center">
<div className="flex flex-col items-center justify-center">
<LoadingPasskey
illustrationStyle={config.illustrationStyle ?? "flat"}
/>
<LoadingPasskey illustrationStyle={illustrationStyle ?? "flat"} />
</div>

<h3 className="font-semibold text-lg">{ls.loadingPasskey.title}</h3>
Expand Down
14 changes: 8 additions & 6 deletions account-kit/react/src/components/auth/card/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ import { Fragment } from "react";
import { Divider } from "../../divider.js";
import { PoweredBy } from "../../poweredby.js";
import { AuthSection } from "../sections/AuthSection.js";
import type { AuthCardProps } from "./index.js";
import { ls } from "../../../strings.js";
import { useUiConfig } from "../../../hooks/useUiConfig.js";

// eslint-disable-next-line jsdoc/require-jsdoc
export const MainAuthContent = ({
header = null,
showSignInText = true,
sections,
}: AuthCardProps) => {
export const MainAuthContent = () => {
const { auth } = useUiConfig();

const header = auth?.header ?? null;
const sections = auth?.sections;
const showSignInText = auth?.showSignInText ?? false;

return (
<>
{header}
Expand Down
8 changes: 5 additions & 3 deletions account-kit/react/src/components/auth/card/passkey-added.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { useUiConfig } from "../../../hooks/useUiConfig.js";
import { AddedPasskeyIllustration } from "../../../icons/illustrations/added-passkey.js";
import { PoweredBy } from "../../poweredby.js";
import type { AuthCardProps } from "./index.js";

// eslint-disable-next-line jsdoc/require-jsdoc
export function PasskeyAdded({ config }: { config: AuthCardProps }) {
export function PasskeyAdded() {
const { illustrationStyle } = useUiConfig();

return (
<div className="flex flex-col gap-5 items-center">
<div className="flex flex-col items-center justify-center h-12 w-12">
<AddedPasskeyIllustration
illustrationStyle={config.illustrationStyle ?? "flat"}
illustrationStyle={illustrationStyle ?? "flat"}
height="48"
width="48"
/>
Expand Down
11 changes: 5 additions & 6 deletions account-kit/react/src/components/auth/card/steps.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
import { useAuthContext } from "../context.js";
import { AddPasskey } from "./add-passkey.js";
import { EoaConnectCard } from "./eoa.js";
import type { AuthCardProps } from "./index.js";
import { LoadingAuth } from "./loading/index.js";
import { MainAuthContent } from "./main.js";
import { PasskeyAdded } from "./passkey-added.js";

// eslint-disable-next-line jsdoc/require-jsdoc
export const Step = (props: AuthCardProps) => {
export const Step = () => {
const { authStep } = useAuthContext();

switch (authStep.type) {
case "email_verify":
case "passkey_verify":
case "email_completing":
return <LoadingAuth config={props} context={authStep} />;
return <LoadingAuth context={authStep} />;
case "passkey_create":
return <AddPasskey config={props} />;
return <AddPasskey />;
case "passkey_create_success":
return <PasskeyAdded config={props} />;
return <PasskeyAdded />;
case "eoa_connect":
return <EoaConnectCard authStep={authStep} />;
case "complete":
case "initial":
default:
return <MainAuthContent {...props} />;
return <MainAuthContent />;
}
};
28 changes: 8 additions & 20 deletions account-kit/react/src/components/auth/modal.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,16 @@
import { AuthCard } from "./card/index.js";
import { type AlchemyAccountsUIConfig } from "../../context.js";
import { AuthCardContent } from "./card/index.js";
import { useAuthModal } from "../../hooks/useAuthModal.js";
import { Dialog } from "../dialog/dialog.js";
import { useUiConfig } from "../../hooks/useUiConfig.js";

type AuthModalProps = {
open: boolean;
hideError?: boolean;
auth: NonNullable<AlchemyAccountsUIConfig["auth"]>;
};

export const AuthModal = ({ open, hideError, auth }: AuthModalProps) => {
const { closeAuthModal } = useAuthModal();
export const AuthModal = () => {
const { modalBaseClassName } = useUiConfig();
const { isOpen, closeAuthModal } = useAuthModal();

return (
<Dialog isOpen={open} onClose={closeAuthModal}>
<div className="modal md:w-[368px]">
<AuthCard
hideError={hideError}
header={auth.header}
sections={auth.sections}
illustrationStyle={auth.illustrationStyle}
onAuthSuccess={() => closeAuthModal()}
showClose
/>
<Dialog isOpen={isOpen} onClose={closeAuthModal}>
<div className={`modal md:w-[368px] ${modalBaseClassName ?? ""}`}>
<AuthCardContent showClose />
</div>
</Dialog>
);
Expand Down
2 changes: 0 additions & 2 deletions account-kit/react/src/components/auth/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,3 @@ export type AuthType =
}
| { type: "passkey" }
| { type: "injected" };

export type AuthIllustrationStyle = "outline" | "linear" | "filled" | "flat";
4 changes: 2 additions & 2 deletions account-kit/react/src/components/navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { Button } from "./button.js";
interface NavigationProps {
onBack?: () => void;
onClose: () => void;
showBack: boolean;
showBack?: boolean;
showClose: boolean;
}

// eslint-disable-next-line jsdoc/require-jsdoc
export const Navigation = ({
showBack,
showBack = false,
showClose,
onBack,
onClose,
Expand Down
Loading

0 comments on commit ac44833

Please sign in to comment.