-
Notifications
You must be signed in to change notification settings - Fork 5.7k
feat(auth): Show Last used label on SSO sign-in method #17093
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
3c164f7
dd1facb
520d068
cdc8b5d
6b9e078
a30a8d3
039485a
f547294
2efd5a9
7d62fbd
e6dd2f4
aae2da5
97809de
70d5527
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,12 +26,14 @@ import { | |
| type AuthTokenPair, | ||
| } from '~/generated-metadata/graphql'; | ||
|
|
||
| import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState'; | ||
| import { tokenPairState } from '@/auth/states/tokenPairState'; | ||
| import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState'; | ||
|
|
||
| import { isAppEffectRedirectEnabledState } from '@/app/states/isAppEffectRedirectEnabledState'; | ||
| import { useSignUpInNewWorkspace } from '@/auth/sign-in-up/hooks/useSignUpInNewWorkspace'; | ||
| import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState'; | ||
| import { LAST_AUTHENTICATED_METHOD_STORAGE_KEY } from '@/auth/states/lastAuthenticatedMethodState'; | ||
| import { loginTokenState } from '@/auth/states/loginTokenState'; | ||
| import { | ||
| SignInUpStep, | ||
| signInUpStepState, | ||
|
|
@@ -66,7 +68,6 @@ import { iconsState } from 'twenty-ui/display'; | |
| import { type AuthToken } from '~/generated/graphql'; | ||
| import { cookieStorage } from '~/utils/cookie-storage'; | ||
| import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl'; | ||
| import { loginTokenState } from '@/auth/states/loginTokenState'; | ||
|
|
||
| export const useAuth = () => { | ||
| const setTokenPair = useSetRecoilState(tokenPairState); | ||
|
|
@@ -176,10 +177,34 @@ export const useAuth = () => { | |
|
|
||
| goToRecoilSnapshot(initialSnapshot); | ||
|
|
||
| sessionStorage.clear(); | ||
| localStorage.clear(); | ||
| let lastAuthenticatedMethod: string | null = null; | ||
| try { | ||
| lastAuthenticatedMethod = localStorage.getItem( | ||
| LAST_AUTHENTICATED_METHOD_STORAGE_KEY, | ||
| ); | ||
| } catch { | ||
|
||
| // Ignore storage errors - last auth method is non-critical | ||
| } | ||
|
|
||
| try { | ||
| sessionStorage.clear(); | ||
| localStorage.clear(); | ||
| } catch { | ||
| // Ignore storage errors during sign-out | ||
| } | ||
|
|
||
| if (lastAuthenticatedMethod !== null) { | ||
| try { | ||
| localStorage.setItem( | ||
| LAST_AUTHENTICATED_METHOD_STORAGE_KEY, | ||
| lastAuthenticatedMethod, | ||
| ); | ||
| } catch { | ||
| // Ignore failures preserving last auth method - it's non-critical | ||
| } | ||
| } | ||
|
|
||
| await client.clearStore(); | ||
| // We need to explicitly clear the state to trigger the cookie deletion which include the parent domain | ||
| setLastAuthenticateWorkspaceDomain(null); | ||
| await loadMockedObjectMetadataItems(); | ||
| navigate(AppPath.SignInUp); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import styled from '@emotion/styled'; | ||
| import { Pill } from 'twenty-ui/components'; | ||
|
|
||
| export const StyledSSOButtonContainer = styled.div` | ||
| position: relative; | ||
| width: 100%; | ||
| `; | ||
|
|
||
| export const StyledLastUsedPill = styled(Pill)` | ||
| background: ${({ theme }) => theme.color.blue3}; | ||
| border: 1px solid ${({ theme }) => theme.color.blue5}; | ||
| border-radius: ${({ theme }) => theme.border.radius.pill}; | ||
| color: ${({ theme }) => theme.color.blue9}; | ||
| font-weight: ${({ theme }) => theme.font.weight.semiBold}; | ||
| position: absolute; | ||
| right: -${({ theme }) => theme.spacing(5)}; | ||
| top: -${({ theme }) => theme.spacing(2)}; | ||
| `; |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,15 +1,20 @@ | ||||||
| import { useLastAuthenticatedMethod } from '@/auth/sign-in-up/hooks/useLastAuthenticatedMethod'; | ||||||
| import { useSignInWithGoogle } from '@/auth/sign-in-up/hooks/useSignInWithGoogle'; | ||||||
| import { | ||||||
| SignInUpStep, | ||||||
| signInUpStepState, | ||||||
| } from '@/auth/states/signInUpStepState'; | ||||||
| import { type SocialSSOSignInUpActionType } from '@/auth/types/socialSSOSignInUp.type'; | ||||||
| import { useTheme } from '@emotion/react'; | ||||||
| import { useLingui } from '@lingui/react/macro'; | ||||||
| import { memo } from 'react'; | ||||||
| import { useRecoilValue } from 'recoil'; | ||||||
| import { HorizontalSeparator, IconGoogle } from 'twenty-ui/display'; | ||||||
| import { MainButton } from 'twenty-ui/input'; | ||||||
| import { type SocialSSOSignInUpActionType } from '@/auth/types/socialSSOSignInUp.type'; | ||||||
| import { | ||||||
| StyledLastUsedPill, | ||||||
| StyledSSOButtonContainer, | ||||||
| } from './SignInUpSSOButtonStyles'; | ||||||
|
|
||||||
| const GoogleIcon = memo(() => { | ||||||
| const theme = useTheme(); | ||||||
|
|
@@ -23,16 +28,29 @@ export const SignInUpWithGoogle = ({ | |||||
| }) => { | ||||||
| const { t } = useLingui(); | ||||||
| const signInUpStep = useRecoilValue(signInUpStepState); | ||||||
| const { lastAuthenticatedMethod, setLastAuthenticatedMethod } = | ||||||
| useLastAuthenticatedMethod(); | ||||||
| const { signInWithGoogle } = useSignInWithGoogle(); | ||||||
|
|
||||||
| const handleClick = () => { | ||||||
| setLastAuthenticatedMethod('google'); | ||||||
| signInWithGoogle({ action }); | ||||||
|
Comment on lines
+39
to
+41
This comment was marked as outdated.
Sorry, something went wrong. |
||||||
| }; | ||||||
|
Comment on lines
+39
to
+42
|
||||||
|
|
||||||
| const isLastUsed = lastAuthenticatedMethod === 'google'; | ||||||
|
|
||||||
| return ( | ||||||
| <> | ||||||
| <MainButton | ||||||
| Icon={GoogleIcon} | ||||||
| title={t`Continue with Google`} | ||||||
| onClick={() => signInWithGoogle({ action })} | ||||||
| variant={signInUpStep === SignInUpStep.Init ? undefined : 'secondary'} | ||||||
| fullWidth | ||||||
| /> | ||||||
| <StyledSSOButtonContainer> | ||||||
| <MainButton | ||||||
| Icon={GoogleIcon} | ||||||
| title={t`Continue with Google`} | ||||||
| onClick={handleClick} | ||||||
| variant={signInUpStep === SignInUpStep.Init ? undefined : 'secondary'} | ||||||
| fullWidth | ||||||
| /> | ||||||
| {isLastUsed && <StyledLastUsedPill label={t`Last`} />} | ||||||
|
||||||
| {isLastUsed && <StyledLastUsedPill label={t`Last`} />} | |
| {isLastUsed && <StyledLastUsedPill label={t`Last used`} />} |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,14 +1,19 @@ | ||||||
| import { useLastAuthenticatedMethod } from '@/auth/sign-in-up/hooks/useLastAuthenticatedMethod'; | ||||||
| import { useSignInWithMicrosoft } from '@/auth/sign-in-up/hooks/useSignInWithMicrosoft'; | ||||||
| import { | ||||||
| SignInUpStep, | ||||||
| signInUpStepState, | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fix lint |
||||||
| } from '@/auth/states/signInUpStepState'; | ||||||
| import { type SocialSSOSignInUpActionType } from '@/auth/types/socialSSOSignInUp.type'; | ||||||
| import { useTheme } from '@emotion/react'; | ||||||
| import { useLingui } from '@lingui/react/macro'; | ||||||
| import { useRecoilValue } from 'recoil'; | ||||||
| import { HorizontalSeparator, IconMicrosoft } from 'twenty-ui/display'; | ||||||
| import { MainButton } from 'twenty-ui/input'; | ||||||
| import { type SocialSSOSignInUpActionType } from '@/auth/types/socialSSOSignInUp.type'; | ||||||
| import { | ||||||
| StyledLastUsedPill, | ||||||
| StyledSSOButtonContainer, | ||||||
| } from './SignInUpSSOButtonStyles'; | ||||||
|
|
||||||
| export const SignInUpWithMicrosoft = ({ | ||||||
| action, | ||||||
|
|
@@ -19,17 +24,29 @@ export const SignInUpWithMicrosoft = ({ | |||||
| const { t } = useLingui(); | ||||||
|
|
||||||
| const signInUpStep = useRecoilValue(signInUpStepState); | ||||||
| const { lastAuthenticatedMethod, setLastAuthenticatedMethod } = | ||||||
| useLastAuthenticatedMethod(); | ||||||
| const { signInWithMicrosoft } = useSignInWithMicrosoft(); | ||||||
|
|
||||||
| const handleClick = () => { | ||||||
| setLastAuthenticatedMethod('microsoft'); | ||||||
|
||||||
| signInWithMicrosoft({ action }); | ||||||
| }; | ||||||
|
Comment on lines
+35
to
+38
|
||||||
|
|
||||||
| const isLastUsed = lastAuthenticatedMethod === 'microsoft'; | ||||||
|
|
||||||
| return ( | ||||||
| <> | ||||||
| <MainButton | ||||||
| Icon={() => <IconMicrosoft size={theme.icon.size.md} />} | ||||||
| title={t`Continue with Microsoft`} | ||||||
| onClick={() => signInWithMicrosoft({ action })} | ||||||
| variant={signInUpStep === SignInUpStep.Init ? undefined : 'secondary'} | ||||||
| fullWidth | ||||||
| /> | ||||||
| <StyledSSOButtonContainer> | ||||||
| <MainButton | ||||||
| Icon={() => <IconMicrosoft size={theme.icon.size.md} />} | ||||||
| title={t`Continue with Microsoft`} | ||||||
| onClick={handleClick} | ||||||
| variant={signInUpStep === SignInUpStep.Init ? undefined : 'secondary'} | ||||||
| fullWidth | ||||||
| /> | ||||||
| {isLastUsed && <StyledLastUsedPill label={t`Last`} />} | ||||||
|
||||||
| {isLastUsed && <StyledLastUsedPill label={t`Last`} />} | |
| {isLastUsed && <StyledLastUsedPill label={t`Last used`} />} |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,3 +1,4 @@ | ||||||
| import { useLastAuthenticatedMethod } from '@/auth/sign-in-up/hooks/useLastAuthenticatedMethod'; | ||||||
| import { useSSO } from '@/auth/sign-in-up/hooks/useSSO'; | ||||||
| import { | ||||||
| SignInUpStep, | ||||||
|
|
@@ -10,18 +11,24 @@ import { useRecoilValue, useSetRecoilState } from 'recoil'; | |||||
| import { isDefined } from 'twenty-shared/utils'; | ||||||
| import { HorizontalSeparator, IconLock } from 'twenty-ui/display'; | ||||||
| import { MainButton } from 'twenty-ui/input'; | ||||||
| import { | ||||||
| StyledLastUsedPill, | ||||||
| StyledSSOButtonContainer, | ||||||
| } from './SignInUpSSOButtonStyles'; | ||||||
|
|
||||||
| export const SignInUpWithSSO = () => { | ||||||
| const theme = useTheme(); | ||||||
| const { t } = useLingui(); | ||||||
| const setSignInUpStep = useSetRecoilState(signInUpStepState); | ||||||
| const workspaceAuthProviders = useRecoilValue(workspaceAuthProvidersState); | ||||||
|
|
||||||
| const signInUpStep = useRecoilValue(signInUpStepState); | ||||||
| const { lastAuthenticatedMethod, setLastAuthenticatedMethod } = | ||||||
| useLastAuthenticatedMethod(); | ||||||
|
|
||||||
| const { redirectToSSOLoginPage } = useSSO(); | ||||||
|
|
||||||
| const signInWithSSO = () => { | ||||||
| setLastAuthenticatedMethod('sso'); | ||||||
| if ( | ||||||
| isDefined(workspaceAuthProviders) && | ||||||
| workspaceAuthProviders.sso.length === 1 | ||||||
|
|
@@ -32,15 +39,20 @@ export const SignInUpWithSSO = () => { | |||||
| setSignInUpStep(SignInUpStep.SSOIdentityProviderSelection); | ||||||
| }; | ||||||
|
|
||||||
| const isLastUsed = lastAuthenticatedMethod === 'sso'; | ||||||
|
||||||
|
|
||||||
| return ( | ||||||
| <> | ||||||
| <MainButton | ||||||
| Icon={() => <IconLock size={theme.icon.size.md} />} | ||||||
| title={t`Single sign-on (SSO)`} | ||||||
| onClick={signInWithSSO} | ||||||
| variant={signInUpStep === SignInUpStep.Init ? undefined : 'secondary'} | ||||||
| fullWidth | ||||||
| /> | ||||||
| <StyledSSOButtonContainer> | ||||||
| <MainButton | ||||||
| Icon={() => <IconLock size={theme.icon.size.md} />} | ||||||
| title={t`Single sign-on (SSO)`} | ||||||
| onClick={signInWithSSO} | ||||||
| variant={signInUpStep === SignInUpStep.Init ? undefined : 'secondary'} | ||||||
| fullWidth | ||||||
| /> | ||||||
| {isLastUsed && <StyledLastUsedPill label={t`Last`} />} | ||||||
|
||||||
| {isLastUsed && <StyledLastUsedPill label={t`Last`} />} | |
| {isLastUsed && <StyledLastUsedPill label={t`Last used`} />} |
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If only a single method is enabled on that workspace then we probably shouldn't show last used
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { useRecoilValue } from 'recoil'; | ||
|
|
||
| import { | ||
| LAST_AUTHENTICATED_METHOD_STORAGE_KEY, | ||
| lastAuthenticatedMethodState, | ||
| type LastAuthenticatedMethod, | ||
| } from '@/auth/states/lastAuthenticatedMethodState'; | ||
|
|
||
| export const useLastAuthenticatedMethod = () => { | ||
| const lastAuthenticatedMethod = useRecoilValue(lastAuthenticatedMethodState); | ||
| const setLastAuthenticatedMethod = (method: LastAuthenticatedMethod) => { | ||
|
||
| localStorage.setItem( | ||
|
||
| LAST_AUTHENTICATED_METHOD_STORAGE_KEY, | ||
| JSON.stringify(method), | ||
| ); | ||
| }; | ||
|
||
|
|
||
| return { | ||
| lastAuthenticatedMethod, | ||
| setLastAuthenticatedMethod, | ||
| }; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import { atom } from 'recoil'; | ||
| import { localStorageEffect } from '~/utils/recoil/localStorageEffect'; | ||
|
|
||
| export type LastAuthenticatedMethod = 'google' | 'microsoft' | 'sso' | null; | ||
|
||
|
|
||
| export const LAST_AUTHENTICATED_METHOD_STORAGE_KEY = | ||
| 'lastAuthenticatedMethodState'; | ||
|
|
||
| export const lastAuthenticatedMethodState = atom<LastAuthenticatedMethod>({ | ||
| key: LAST_AUTHENTICATED_METHOD_STORAGE_KEY, | ||
| default: null, | ||
| effects: [localStorageEffect()], | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ export const FONT_DARK = { | |
| extraLight: GRAY_SCALE_DARK.gray7, | ||
| inverted: GRAY_SCALE_DARK.gray1, | ||
| danger: COLOR_DARK.red, | ||
| indigo: COLOR_DARK.blue9, | ||
|
||
| }, | ||
| ...FONT_COMMON, | ||
| }; | ||
This comment was marked as outdated.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.