diff --git a/web/sdk/client/components/auth-oidc-button/auth-oidc-button.tsx b/web/sdk/client/components/auth-oidc-button/auth-oidc-button.tsx index 94ef6463b..b84534f41 100644 --- a/web/sdk/client/components/auth-oidc-button/auth-oidc-button.tsx +++ b/web/sdk/client/components/auth-oidc-button/auth-oidc-button.tsx @@ -2,6 +2,7 @@ import { Button } from '@raystack/apsara'; import { ComponentProps } from 'react'; import GoogleLogo from '~/client/assets/logos/google-logo.svg'; import { capitalize } from '~/utils'; +import { DisabledTooltip } from '../disabled-tooltip'; import styles from './auth-oidc-button.module.css'; const oidcLogoMap = new Map([['google', GoogleLogo]]); @@ -11,32 +12,36 @@ export type AuthOIDCButtonProps = Omit< 'variant' | 'color' | 'className' | 'leadingIcon' > & { provider: string; + disabledMessage?: string; }; export const AuthOIDCButton = ({ onClick, provider, disabled, + disabledMessage, children = `Continue with ${capitalize(provider)}`, ...props }: AuthOIDCButtonProps) => ( - + + + ); diff --git a/web/sdk/client/components/disabled-tooltip/disabled-tooltip.module.css b/web/sdk/client/components/disabled-tooltip/disabled-tooltip.module.css new file mode 100644 index 000000000..8a53d8bd6 --- /dev/null +++ b/web/sdk/client/components/disabled-tooltip/disabled-tooltip.module.css @@ -0,0 +1,4 @@ +.trigger { + display: flex; + width: 100%; +} diff --git a/web/sdk/client/components/disabled-tooltip/disabled-tooltip.tsx b/web/sdk/client/components/disabled-tooltip/disabled-tooltip.tsx new file mode 100644 index 000000000..6124ecab6 --- /dev/null +++ b/web/sdk/client/components/disabled-tooltip/disabled-tooltip.tsx @@ -0,0 +1,35 @@ +import { Tooltip } from '@raystack/apsara'; +import { ReactNode } from 'react'; +import styles from './disabled-tooltip.module.css'; + +export type DisabledTooltipProps = { + disabled?: boolean; + message?: string; + children: ReactNode; +}; + +export const DisabledTooltip = ({ + disabled = false, + message, + children +}: DisabledTooltipProps) => { + const active = disabled && !!message; + + if (!active) return children; + + return ( + + }> + {children} + + + {message} + + + ); +}; diff --git a/web/sdk/client/components/disabled-tooltip/index.ts b/web/sdk/client/components/disabled-tooltip/index.ts new file mode 100644 index 000000000..a87737b81 --- /dev/null +++ b/web/sdk/client/components/disabled-tooltip/index.ts @@ -0,0 +1,2 @@ +export { DisabledTooltip } from './disabled-tooltip'; +export type { DisabledTooltipProps } from './disabled-tooltip'; diff --git a/web/sdk/client/views/auth/magic-link/magic-link-view.tsx b/web/sdk/client/views/auth/magic-link/magic-link-view.tsx index 0eba5cfaf..e2cf8ec7f 100644 --- a/web/sdk/client/views/auth/magic-link/magic-link-view.tsx +++ b/web/sdk/client/views/auth/magic-link/magic-link-view.tsx @@ -12,6 +12,7 @@ import { type AuthContainerProps } from '~/client/components/auth-container'; import { AuthHeader } from '~/client/components/auth-header'; +import { DisabledTooltip } from '~/client/components/disabled-tooltip'; import { describeAuthError } from '~/client/utils/auth-error'; import { MAIL_OTP_STRATEGY } from '~/client/utils/constants'; import styles from './magic-link-view.module.css'; @@ -25,6 +26,7 @@ export type MagicLinkViewProps = ComponentPropsWithRef<'div'> & intent?: FlowIntent; acceptedDocumentIds?: string[]; disabled?: boolean; + disabledMessage?: string; onActivate?: () => void; }; @@ -51,6 +53,7 @@ export const MagicLinkView = ({ intent = FlowIntent.UNSPECIFIED, acceptedDocumentIds, disabled = false, + disabledMessage, onActivate, ...props }: MagicLinkViewProps) => { @@ -102,19 +105,21 @@ export const MagicLinkView = ({ const email = watch('email', ''); const formContent = !visible ? ( - + + + ) : (
- + + +
); diff --git a/web/sdk/client/views/auth/sign-in/sign-in-view.tsx b/web/sdk/client/views/auth/sign-in/sign-in-view.tsx index 50ab49015..935452782 100644 --- a/web/sdk/client/views/auth/sign-in/sign-in-view.tsx +++ b/web/sdk/client/views/auth/sign-in/sign-in-view.tsx @@ -24,6 +24,8 @@ export type SignInViewProps = ComponentPropsWithRef<'div'> & title?: string; excludes?: string[]; footer?: boolean; + /** Send no flow intent, so the server falls back to create-or-get. */ + disableIntent?: boolean; error?: AuthRejection; }; @@ -32,6 +34,7 @@ export const SignInView = ({ title = 'Login to Raystack', excludes = [], footer = true, + disableIntent = false, error, ...props }: SignInViewProps) => { @@ -52,6 +55,8 @@ export const SignInView = ({ FrontierServiceQueries.authenticate ); + const intent = disableIntent ? FlowIntent.UNSPECIFIED : FlowIntent.LOGIN; + const clearError = useCallback(() => setAuthError(null), []); const clickHandler = useCallback( @@ -61,7 +66,7 @@ export const SignInView = ({ const response = await authenticate({ strategyName: name, callbackUrl: config.callbackUrl, - flowIntent: FlowIntent.LOGIN + flowIntent: intent }); if (response.endpoint) { window.location.href = response.endpoint; @@ -73,7 +78,7 @@ export const SignInView = ({ }); } }, - [authenticate, config, clearError] + [authenticate, config, intent, clearError] ); const isUnknownError = @@ -91,7 +96,7 @@ export const SignInView = ({ ) : ( diff --git a/web/sdk/client/views/auth/sign-up/sign-up-view.tsx b/web/sdk/client/views/auth/sign-up/sign-up-view.tsx index cbef43369..11e410620 100644 --- a/web/sdk/client/views/auth/sign-up/sign-up-view.tsx +++ b/web/sdk/client/views/auth/sign-up/sign-up-view.tsx @@ -27,6 +27,7 @@ import styles from './sign-up-view.module.css'; const CONSENT_UNAVAILABLE_MESSAGE = 'The documents required to sign up could not be loaded. Please refresh the page.'; +const CONSENT_REQUIRED_TOOLTIP = 'You must agree to continue'; export type SignUpViewProps = ComponentPropsWithRef<'div'> & AuthContainerProps & { @@ -34,6 +35,10 @@ export type SignUpViewProps = ComponentPropsWithRef<'div'> & title?: string; excludes?: string[]; consentLabel?: ConsentLabel; + /** Send no flow intent, so the server falls back to create-or-get. */ + disableIntent?: boolean; + /** Tooltip shown on the disabled sign-up buttons until consent is given. */ + disabledContentMessage?: string; error?: AuthRejection; }; @@ -42,6 +47,8 @@ export const SignUpView = ({ title = 'Create your account', excludes = [], consentLabel, + disableIntent = false, + disabledContentMessage = CONSENT_REQUIRED_TOOLTIP, error, ...props }: SignUpViewProps) => { @@ -84,6 +91,8 @@ export const SignUpView = ({ const blocked = consentPending || consentFailed || (documents.length > 0 && !consented); + const intent = disableIntent ? FlowIntent.UNSPECIFIED : FlowIntent.SIGNUP; + const clearError = useCallback(() => setAuthError(null), []); const clickHandler = useCallback( @@ -93,7 +102,7 @@ export const SignUpView = ({ const response = await authenticate({ strategyName: name, callbackUrl: config.callbackUrl, - flowIntent: FlowIntent.SIGNUP, + flowIntent: intent, acceptedDocumentIds }); if (response.endpoint) { @@ -106,7 +115,7 @@ export const SignUpView = ({ }); } }, - [authenticate, config, acceptedDocumentIds, clearError] + [authenticate, config, intent, acceptedDocumentIds, clearError] ); const isUnknownError = @@ -128,9 +137,10 @@ export const SignUpView = ({ ) : ( @@ -144,6 +154,7 @@ export const SignUpView = ({ onClick={() => clickHandler(s.name)} provider={s.name} disabled={blocked} + disabledMessage={disabledContentMessage} data-test-id="frontier-sdk-signup-page-oidc-btn" />