diff --git a/packages/web/app/src/lib/supertokens/last-auth-method.ts b/packages/web/app/src/lib/supertokens/last-auth-method.ts new file mode 100644 index 000000000..2fc6e5f3f --- /dev/null +++ b/packages/web/app/src/lib/supertokens/last-auth-method.ts @@ -0,0 +1,31 @@ +import { useCallback, useMemo, useState } from 'react'; +import Cookies from 'js-cookie'; + +const LAST_USED_AUTH_METHOD_KEY = 'hive:last-used-auth-method'; + +type AuthProvider = 'github' | 'google' | 'email' | 'okta' | 'oidc'; + +export function updateLastAuthMethod(provider: AuthProvider) { + Cookies.set(LAST_USED_AUTH_METHOD_KEY, provider); +} + +export function useLastAuthMethod() { + const [authProvider, setAuthProvider] = useState( + (Cookies.get(LAST_USED_AUTH_METHOD_KEY) as AuthProvider) ?? null, + ); + + const updateAuthProvider = useCallback( + (provider: AuthProvider) => { + setAuthProvider(provider); + Cookies.set(LAST_USED_AUTH_METHOD_KEY, provider); + }, + [setAuthProvider], + ); + + const api = useMemo( + () => [authProvider, updateAuthProvider] as const, + [authProvider, updateAuthProvider], + ); + + return api; +} diff --git a/packages/web/app/src/lib/supertokens/start-auth-flow-for-provider.ts b/packages/web/app/src/lib/supertokens/start-auth-flow-for-provider.ts index c3a759859..d1123ecb0 100644 --- a/packages/web/app/src/lib/supertokens/start-auth-flow-for-provider.ts +++ b/packages/web/app/src/lib/supertokens/start-auth-flow-for-provider.ts @@ -1,5 +1,6 @@ import { getAuthorisationURLWithQueryParamsAndSetState } from 'supertokens-auth-react/recipe/thirdpartyemailpassword'; import { env } from '@/env/frontend'; +import { updateLastAuthMethod } from './last-auth-method'; /** * utility for starting the login flow manually without clicking a button @@ -25,5 +26,7 @@ export const startAuthFlowForProvider = async ( frontendRedirectURI: `${env.appBaseUrl}/auth/callback/${thirdPartyId}${redirectPart}`, }); + updateLastAuthMethod(thirdPartyId); + window.location.assign(authUrl); }; diff --git a/packages/web/app/src/lib/supertokens/third-party-email-password-react-oidc-provider.ts b/packages/web/app/src/lib/supertokens/third-party-email-password-react-oidc-provider.ts index d4d3caa7e..f722a1185 100644 --- a/packages/web/app/src/lib/supertokens/third-party-email-password-react-oidc-provider.ts +++ b/packages/web/app/src/lib/supertokens/third-party-email-password-react-oidc-provider.ts @@ -1,6 +1,7 @@ import { UserInput } from 'supertokens-auth-react/lib/build/recipe/thirdpartyemailpassword/types'; import { getAuthorisationURLWithQueryParamsAndSetState } from 'supertokens-auth-react/recipe/thirdpartyemailpassword'; import { env } from '@/env/frontend'; +import { updateLastAuthMethod } from './last-auth-method'; export const createThirdPartyEmailPasswordReactOIDCProvider = () => ({ id: 'oidc', @@ -79,6 +80,8 @@ export const startAuthFlowForOIDCProvider = async (oidcId: string) => { }, }); + updateLastAuthMethod('oidc'); + // Redirects to the OIDC provider window.location.assign(authUrl); }; diff --git a/packages/web/app/src/pages/auth-sign-in.tsx b/packages/web/app/src/pages/auth-sign-in.tsx index e9f663e23..e313b028e 100644 --- a/packages/web/app/src/pages/auth-sign-in.tsx +++ b/packages/web/app/src/pages/auth-sign-in.tsx @@ -23,14 +23,48 @@ import { } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { Meta } from '@/components/ui/meta'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { useToast } from '@/components/ui/use-toast'; +import { useLastAuthMethod } from '@/lib/supertokens/last-auth-method'; import { startAuthFlowForProvider } from '@/lib/supertokens/start-auth-flow-for-provider'; import { enabledProviders, isProviderEnabled } from '@/lib/supertokens/thirdparty'; -import { exhaustiveGuard } from '@/lib/utils'; +import { cn, exhaustiveGuard } from '@/lib/utils'; import { zodResolver } from '@hookform/resolvers/zod'; +import { Slot } from '@radix-ui/react-slot'; import { useMutation } from '@tanstack/react-query'; import { Link, Navigate, useRouter } from '@tanstack/react-router'; +export function SignInButton(props: { + children: React.ReactNode; + previousSignIn: boolean; + variant?: 'outline' | 'default'; + tooltipClassName?: string; +}) { + if (props.previousSignIn) { + return ( + + + + {props.children} + + + + You signed in with it last time. + + + ); + } + + return <>{props.children}; +} + const SignInFormSchema = z.object({ email: z .string({ @@ -44,6 +78,7 @@ type SignInFormValues = z.infer; export function AuthSignInPage(props: { redirectToPath: string }) { const session = useSessionContext(); + const [lastAuthMethod, setLastAuthMethod] = useLastAuthMethod(); const router = useRouter(); const { toast } = useToast(); const emailPasswordSignIn = useMutation({ @@ -53,6 +88,7 @@ export function AuthSignInPage(props: { redirectToPath: string }) { switch (status) { case 'OK': { + setLastAuthMethod('email'); void router.navigate({ to: props.redirectToPath, }); @@ -157,99 +193,112 @@ export function AuthSignInPage(props: { redirectToPath: string }) { -
- - ( - - Email - - - - - - )} - /> - ( - -
- Password - - Forgot your password? - -
- - - - -
- )} - /> - - - - {enabledProviders.length ? : null} - {isProviderEnabled('google') ? ( - - ) : null} - {isProviderEnabled('github') ? ( - - ) : null} - {isProviderEnabled('okta') ? ( - - ) : null} - {isProviderEnabled('oidc') ? ( - - ) : null} + +
+ + ( + + Email + + + + + + )} + /> + ( + +
+ Password + + Forgot your password? + +
+ + + + +
+ )} + /> + + + + + + {enabledProviders.length ? : null} + {isProviderEnabled('google') ? ( + + + + ) : null} + {isProviderEnabled('github') ? ( + + + + ) : null} + + {isProviderEnabled('okta') ? ( + + + + ) : null} + {isProviderEnabled('oidc') ? ( + + + + ) : null} +
Don't have an account?{' '} diff --git a/packages/web/app/src/pages/auth-sign-up.tsx b/packages/web/app/src/pages/auth-sign-up.tsx index 65570ef41..fbff5cb7a 100644 --- a/packages/web/app/src/pages/auth-sign-up.tsx +++ b/packages/web/app/src/pages/auth-sign-up.tsx @@ -24,14 +24,17 @@ import { } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { Meta } from '@/components/ui/meta'; +import { TooltipProvider } from '@/components/ui/tooltip'; import { useToast } from '@/components/ui/use-toast'; import { env } from '@/env/frontend'; +import { useLastAuthMethod } from '@/lib/supertokens/last-auth-method'; import { startAuthFlowForProvider } from '@/lib/supertokens/start-auth-flow-for-provider'; import { enabledProviders, isProviderEnabled } from '@/lib/supertokens/thirdparty'; import { exhaustiveGuard } from '@/lib/utils'; import { zodResolver } from '@hookform/resolvers/zod'; import { useMutation } from '@tanstack/react-query'; import { Link, Navigate, useRouter } from '@tanstack/react-router'; +import { SignInButton } from './auth-sign-in'; const SignUpFormSchema = z.object({ firstName: z.string({ @@ -51,6 +54,7 @@ const SignUpFormSchema = z.object({ type SignUpFormValues = z.infer; export function AuthSignUpPage(props: { redirectToPath: string }) { + const [lastAuthMethod] = useLastAuthMethod(); const router = useRouter(); const session = useSessionContext(); @@ -199,17 +203,45 @@ export function AuthSignUpPage(props: { redirectToPath: string }) { /> -
- -
+ + + +
+ ( + + First name + + + + + + )} + /> + ( + + Last name + + + + + + )} + /> +
( - First name + Email - + @@ -217,96 +249,78 @@ export function AuthSignUpPage(props: { redirectToPath: string }) { /> ( - Last name + Password - + )} /> -
- ( - - Email - - - - - - )} - /> - ( - - Password - - - - - - )} - /> - - - - {enabledProviders.length ? : null} - {isProviderEnabled('google') ? ( - - ) : null} - {isProviderEnabled('github') ? ( - - ) : null} - {isProviderEnabled('okta') ? ( - - ) : null} - {isProviderEnabled('oidc') ? ( - - ) : null} + + + + {enabledProviders.length ? : null} + {isProviderEnabled('google') ? ( + + + + ) : null} + {isProviderEnabled('github') ? ( + + + + ) : null} + {isProviderEnabled('okta') ? ( + + + + ) : null} + {isProviderEnabled('oidc') ? ( + + + + ) : null} +
Already have an account?{' '} diff --git a/packages/web/app/tailwind.config.cjs b/packages/web/app/tailwind.config.cjs index 593daf2c9..a021856f0 100644 --- a/packages/web/app/tailwind.config.cjs +++ b/packages/web/app/tailwind.config.cjs @@ -227,6 +227,14 @@ module.exports = { from: { height: 'var(--radix-accordion-content-height)' }, to: { height: 0 }, }, + shimmer: { + from: { + backgroundPosition: '0 0', + }, + to: { + backgroundPosition: '-200% 0', + }, + }, }, animation: { // Dropdown menu @@ -255,6 +263,8 @@ module.exports = { 'toast-swipe-out-y': 'toast-swipe-out-y 100ms ease-out forwards', 'accordion-down': 'accordion-down 0.2s ease-out', 'accordion-up': 'accordion-up 0.2s ease-out', + // + shimmer: 'shimmer 1.5s linear infinite', }, minHeight: { content: 'var(--content-height)',