-
-
Notifications
You must be signed in to change notification settings - Fork 440
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(console): add console landing page to accept user invitation
- Loading branch information
1 parent
4766d22
commit 2d7e81d
Showing
7 changed files
with
175 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
packages/console/src/pages/AcceptInvitation/SwitchAccount/index.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
@use '@/scss/underscore' as _; | ||
|
||
.container { | ||
display: flex; | ||
flex-direction: column; | ||
width: 100vw; | ||
height: 100vh; | ||
background: var(--color-surface-1); | ||
align-items: center; | ||
justify-content: center; | ||
overflow: hidden; | ||
|
||
.wrapper { | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
width: 540px; | ||
padding: _.unit(35) _.unit(17.5); | ||
gap: _.unit(6); | ||
background: var(--color-bg-float); | ||
border-radius: 16px; | ||
box-shadow: var(--shadow-1); | ||
text-align: center; | ||
white-space: pre-wrap; | ||
|
||
.logo { | ||
height: 36px; | ||
} | ||
|
||
.title { | ||
font: var(--font-title-2); | ||
} | ||
|
||
.description { | ||
font: var(--font-body-2); | ||
color: var(--color-text-secondary); | ||
} | ||
|
||
.button { | ||
width: 100%; | ||
} | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
packages/console/src/pages/AcceptInvitation/SwitchAccount/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { useTranslation } from 'react-i18next'; | ||
|
||
import Logo from '@/assets/images/logo.svg'; | ||
import Button from '@/ds-components/Button'; | ||
import useCurrentUser from '@/hooks/use-current-user'; | ||
|
||
import * as styles from './index.module.scss'; | ||
|
||
type Props = { | ||
onClickSwitch: () => void; | ||
}; | ||
|
||
function SwitchAccount({ onClickSwitch }: Props) { | ||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); | ||
const { user } = useCurrentUser(); | ||
const { id, primaryEmail, username } = user ?? {}; | ||
|
||
return ( | ||
<div className={styles.container}> | ||
<div className={styles.wrapper}> | ||
<Logo className={styles.logo} /> | ||
<div className={styles.title}> | ||
{/** Since this is a Logto Cloud feature, ideally the primary email should always be available. | ||
* However, in case it's not (e.g. in dev env), we fallback to username and then finally the ID. | ||
*/} | ||
{t('invitation.email_not_match_title', { email: primaryEmail ?? username ?? id })} | ||
</div> | ||
<div className={styles.description}>{t('invitation.email_not_match_description')}</div> | ||
<Button | ||
type="primary" | ||
size="large" | ||
className={styles.button} | ||
title="invitation.switch_account" | ||
onClick={onClickSwitch} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export default SwitchAccount; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { useLogto } from '@logto/react'; | ||
import { OrganizationInvitationStatus } from '@logto/schemas'; | ||
import { useEffect } from 'react'; | ||
import { useTranslation } from 'react-i18next'; | ||
import { useParams } from 'react-router-dom'; | ||
import useSWR from 'swr'; | ||
|
||
import { useCloudApi } from '@/cloud/hooks/use-cloud-api'; | ||
import { type InvitationResponse } from '@/cloud/types/router'; | ||
import AppError from '@/components/AppError'; | ||
import AppLoading from '@/components/AppLoading'; | ||
import { type RequestError } from '@/hooks/use-api'; | ||
import useRedirectUri from '@/hooks/use-redirect-uri'; | ||
|
||
import SwitchAccount from './SwitchAccount'; | ||
|
||
function AcceptInvitation() { | ||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); | ||
const { signIn } = useLogto(); | ||
const redirectUri = useRedirectUri(); | ||
const { invitationId = '' } = useParams(); | ||
const cloudApi = useCloudApi(); | ||
|
||
// The request is only made when the user has signed-in and the invitation ID is available. | ||
// The response data is returned only when the current user matches the invitee email. Otherwise, it returns 404. | ||
const { data: invitation, error } = useSWR<InvitationResponse, RequestError>( | ||
invitationId && `/api/invitations/${invitationId}`, | ||
async () => cloudApi.get('/api/invitations/:invitationId', { params: { invitationId } }) | ||
); | ||
|
||
useEffect(() => { | ||
if (!invitation) { | ||
return; | ||
} | ||
(async () => { | ||
const { id, tenantId } = invitation; | ||
|
||
// Accept the invitation and redirect to the tenant page. | ||
await cloudApi.patch(`/api/invitations/:invitationId/status`, { | ||
params: { invitationId: id }, | ||
body: { status: OrganizationInvitationStatus.Accepted }, | ||
}); | ||
|
||
// eslint-disable-next-line @silverhand/fp/no-mutation | ||
window.location.href = `/${tenantId}`; | ||
})(); | ||
}, [cloudApi, error, invitation, t]); | ||
|
||
// No invitation returned, indicating the current signed-in user is not the invitee. | ||
if (error?.status === 404) { | ||
return ( | ||
<SwitchAccount | ||
onClickSwitch={() => { | ||
void signIn({ | ||
redirectUri: redirectUri.href, | ||
loginHint: `urn:logto:invitation:${invitationId}`, | ||
}); | ||
}} | ||
/> | ||
); | ||
} | ||
|
||
if (invitation && invitation.status !== OrganizationInvitationStatus.Pending) { | ||
return <AppError errorMessage={t('invitation.invalid_invitation_status')} />; | ||
} | ||
|
||
return <AppLoading />; | ||
} | ||
|
||
export default AcceptInvitation; |