[web] auth: use toast component for one click sign up popup (#1393)
This commit is contained in:
parent
b597fff269
commit
47f4c6edb8
|
|
@ -1,10 +1,10 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useSessionStorage } from 'usehooks-ts';
|
||||
|
||||
import { useToast } from '~/components/global/toasts/useToast';
|
||||
|
||||
import { useI18nPathname } from '~/next-i18nostic/src';
|
||||
|
||||
import AuthOneClickSignupCard from './AuthOneClickSignupCard';
|
||||
|
|
@ -12,55 +12,61 @@ import AuthOneClickSignupCard from './AuthOneClickSignupCard';
|
|||
import { useSessionContext } from '@supabase/auth-helpers-react';
|
||||
|
||||
const POPUP_DURATION = 15_000;
|
||||
const TOAST_DURATION = 60 * 60 * 1000; // 1 hour
|
||||
|
||||
export default function AuthOneClickSignup() {
|
||||
const { pathname } = useI18nPathname();
|
||||
const lastToastId = useRef<string | null>(null);
|
||||
const { showToast, dismissToast } = useToast();
|
||||
|
||||
const { isLoading: isUserLoading, session } = useSessionContext();
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [dismissedSignUpPrompt, setDismissedSignUpPrompt] =
|
||||
useSessionStorage<boolean>('gfe:auth:sign-up-prompt', false);
|
||||
|
||||
// Don't show it on homepage
|
||||
const isHomepage = pathname === '/' || pathname === '/projects';
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setDismissedSignUpPrompt(true);
|
||||
}, [setDismissedSignUpPrompt]);
|
||||
|
||||
useEffect(() => {
|
||||
if (session || isUserLoading || dismissedSignUpPrompt || isHomepage) {
|
||||
if (lastToastId.current) {
|
||||
dismissToast(lastToastId.current);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Show popup after 15 seconds
|
||||
const timer = setTimeout(() => {
|
||||
setIsVisible(true);
|
||||
const { id } = showToast({
|
||||
animateFrom: 'bottom',
|
||||
customComponent: () => <AuthOneClickSignupCard onClose={handleClose} />,
|
||||
duration: TOAST_DURATION,
|
||||
side: 'end',
|
||||
variant: 'custom',
|
||||
});
|
||||
|
||||
lastToastId.current = id;
|
||||
}, POPUP_DURATION);
|
||||
|
||||
return () => {
|
||||
setIsVisible(false);
|
||||
if (lastToastId.current) {
|
||||
dismissToast(lastToastId.current);
|
||||
}
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [session, isUserLoading, dismissedSignUpPrompt, isHomepage]);
|
||||
}, [
|
||||
session,
|
||||
isUserLoading,
|
||||
dismissedSignUpPrompt,
|
||||
isHomepage,
|
||||
showToast,
|
||||
handleClose,
|
||||
dismissToast,
|
||||
]);
|
||||
|
||||
if (!isVisible || session || isUserLoading || isHomepage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
setIsVisible(false);
|
||||
setDismissedSignUpPrompt(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className={clsx(
|
||||
'fixed bottom-6 right-6',
|
||||
'z-popover shadow-lg',
|
||||
'hidden sm:block',
|
||||
)}
|
||||
exit={{ opacity: 0, y: 100 }}
|
||||
initial={{ opacity: 0, y: 100 }}
|
||||
transition={{ duration: 0.3, ease: 'easeOut' }}>
|
||||
<AuthOneClickSignupCard onClose={handleClose} />
|
||||
</motion.div>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,11 +65,11 @@ export default function AuthOneClickSignupCard({ onClose }: Props) {
|
|||
})}
|
||||
</Text>
|
||||
<Button
|
||||
className="group transition-all"
|
||||
className="group/button transition-all"
|
||||
icon={RiCloseLine}
|
||||
iconClassName={clsx(
|
||||
themeTextInvertColor,
|
||||
'group-hover:text-neutral-900 dark:group-hover:text-white',
|
||||
'group-hover/button:text-neutral-900 dark:group-hover/button:text-white',
|
||||
)}
|
||||
isLabelHidden={true}
|
||||
label="Close"
|
||||
|
|
@ -105,11 +105,11 @@ export default function AuthOneClickSignupCard({ onClose }: Props) {
|
|||
onClick={() => signInWithProvider('google')}
|
||||
/>
|
||||
<Button
|
||||
className="group transition-all"
|
||||
className="group/button transition-all"
|
||||
icon={RiGithubFill}
|
||||
iconClassName={clsx(
|
||||
themeTextInvertColor,
|
||||
'group-hover:text-neutral-900 dark:group-hover:text-white',
|
||||
'group-hover/button:text-neutral-900 dark:group-hover/button:text-white',
|
||||
)}
|
||||
isLabelHidden={true}
|
||||
isLoading={loading}
|
||||
|
|
@ -129,7 +129,7 @@ export default function AuthOneClickSignupCard({ onClose }: Props) {
|
|||
})}
|
||||
</Text>
|
||||
<Button
|
||||
className="group transition-all"
|
||||
className="group/button transition-all"
|
||||
href={signInUpHref({
|
||||
query: {
|
||||
view: 'email',
|
||||
|
|
@ -138,7 +138,7 @@ export default function AuthOneClickSignupCard({ onClose }: Props) {
|
|||
icon={RiMailLine}
|
||||
iconClassName={clsx(
|
||||
themeTextInvertColor,
|
||||
'group-hover:text-neutral-900 dark:group-hover:text-white',
|
||||
'group-hover/button:text-neutral-900 dark:group-hover/button:text-white',
|
||||
)}
|
||||
isDisabled={loading}
|
||||
isLabelHidden={true}
|
||||
|
|
|
|||
|
|
@ -194,10 +194,14 @@ export const ToastImpl = React.forwardRef<
|
|||
'data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)]',
|
||||
'data-[swipe=move]:transition-none',
|
||||
'data-[state=open]:animate-in',
|
||||
'data-[state=open]:sm:slide-in-from-left-full',
|
||||
props.animateFrom === 'left'
|
||||
? 'data-[state=open]:sm:slide-in-from-left-full'
|
||||
: 'data-[state=open]:sm:slide-in-from-bottom-full',
|
||||
'data-[state=closed]:animate-out',
|
||||
'data-[state=closed]:fade-out-80',
|
||||
'data-[state=closed]:slide-out-to-left-full',
|
||||
props.animateFrom === 'left'
|
||||
? 'data-[state=closed]:slide-out-to-left-full'
|
||||
: 'data-[state=closed]:slide-out-to-bottom-full',
|
||||
'flex',
|
||||
props.side === 'start' && 'self-start',
|
||||
props.side === 'end' && 'self-end justify-end',
|
||||
|
|
@ -207,7 +211,7 @@ export const ToastImpl = React.forwardRef<
|
|||
const { customComponent: Component, ...remainingProps } = props;
|
||||
|
||||
return (
|
||||
<li ref={ref} className={commonClass} {...remainingProps}>
|
||||
<li ref={ref} {...remainingProps} className={commonClass}>
|
||||
<Component />
|
||||
</li>
|
||||
);
|
||||
|
|
@ -312,7 +316,7 @@ type BaseToastProps = Omit<
|
|||
React.ComponentPropsWithoutRef<typeof ToastPrimitive.Root>,
|
||||
'children' | 'title'
|
||||
> &
|
||||
Readonly<{ side?: 'end' | 'start' }>;
|
||||
Readonly<{ animateFrom?: 'bottom' | 'left'; side?: 'end' | 'start' }>;
|
||||
|
||||
export type DefaultToastProps = BaseToastProps & DefaultProps;
|
||||
|
||||
|
|
@ -325,11 +329,24 @@ const Toast = React.forwardRef<
|
|||
ToastProps
|
||||
>((props, ref) => {
|
||||
if (props.variant === 'custom') {
|
||||
const { customComponent, variant, ...remainingProps } = props;
|
||||
const {
|
||||
customComponent,
|
||||
variant,
|
||||
className,
|
||||
side,
|
||||
animateFrom = 'left',
|
||||
...remainingProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<ToastPrimitive.Root ref={ref} asChild={true} {...remainingProps}>
|
||||
<ToastImpl customComponent={customComponent} variant={variant} />
|
||||
<ToastImpl
|
||||
animateFrom={animateFrom}
|
||||
className={className}
|
||||
customComponent={customComponent}
|
||||
side={side}
|
||||
variant={variant}
|
||||
/>
|
||||
</ToastPrimitive.Root>
|
||||
);
|
||||
}
|
||||
|
|
@ -345,6 +362,7 @@ const Toast = React.forwardRef<
|
|||
variant,
|
||||
onClose,
|
||||
side = 'start',
|
||||
animateFrom = 'left',
|
||||
...remainingProps
|
||||
} = props;
|
||||
|
||||
|
|
@ -353,6 +371,7 @@ const Toast = React.forwardRef<
|
|||
<ToastImpl
|
||||
addOnIcon={addOnIcon}
|
||||
addOnLabel={addOnLabel}
|
||||
animateFrom={animateFrom}
|
||||
className={className}
|
||||
description={description}
|
||||
icon={icon}
|
||||
|
|
|
|||
Loading…
Reference in New Issue