-
-
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): implement interim landing page for new users to join i…
…nvited tenants (#5560)
- Loading branch information
1 parent
6990a3e
commit f83e85b
Showing
11 changed files
with
237 additions
and
16 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,6 +48,6 @@ | |
"access": "public" | ||
}, | ||
"devDependencies": { | ||
"@logto/cloud": "0.2.5-2a72cc4" | ||
"@logto/cloud": "0.2.5-81f06ea" | ||
} | ||
} |
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
77 changes: 77 additions & 0 deletions
77
packages/console/src/cloud/pages/Main/InvitationList/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,77 @@ | ||
@use '@/scss/underscore' as _; | ||
|
||
.container { | ||
display: flex; | ||
flex-direction: column; | ||
height: 100%; | ||
min-height: 600px; | ||
background: var(--color-surface-1); | ||
align-items: center; | ||
justify-content: center; | ||
overflow-y: auto; | ||
|
||
.wrapper { | ||
display: flex; | ||
flex-direction: column; | ||
width: 540px; | ||
padding: _.unit(20) _.unit(17.5); | ||
gap: _.unit(6); | ||
background: var(--color-bg-float); | ||
border-radius: 16px; | ||
box-shadow: var(--shadow-1); | ||
white-space: pre-wrap; | ||
|
||
.icon { | ||
width: 40px; | ||
height: 40px; | ||
flex-shrink: 0; | ||
} | ||
|
||
.title { | ||
font: var(--font-headline-2); | ||
} | ||
|
||
.description { | ||
font: var(--font-body-2); | ||
color: var(--color-text-secondary); | ||
} | ||
|
||
.tenant { | ||
display: flex; | ||
align-items: center; | ||
padding: _.unit(3) _.unit(4); | ||
gap: _.unit(3); | ||
border-radius: 12px; | ||
border: 1px solid var(--color-divider); | ||
|
||
.name { | ||
@include _.multi-line-ellipsis(2); | ||
} | ||
|
||
.tag { | ||
margin-left: _.unit(-2); | ||
} | ||
} | ||
|
||
.separator { | ||
display: flex; | ||
align-items: center; | ||
gap: _.unit(4); | ||
|
||
span { | ||
font: var(--font-body-2); | ||
color: var(--color-text-secondary); | ||
} | ||
|
||
hr { | ||
flex: 1; | ||
border: none; | ||
border-top: 1px solid var(--color-divider); | ||
} | ||
} | ||
|
||
.createTenantButton { | ||
width: 100%; | ||
} | ||
} | ||
} |
89 changes: 89 additions & 0 deletions
89
packages/console/src/cloud/pages/Main/InvitationList/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,89 @@ | ||
import { OrganizationInvitationStatus } from '@logto/schemas'; | ||
import { useContext, useState } from 'react'; | ||
import { useTranslation } from 'react-i18next'; | ||
|
||
import Icon from '@/assets/icons/organization-preview.svg'; | ||
import { useCloudApi } from '@/cloud/hooks/use-cloud-api'; | ||
import { type TenantResponse, type InvitationListResponse } from '@/cloud/types/router'; | ||
import CreateTenantModal from '@/components/CreateTenantModal'; | ||
import TenantEnvTag from '@/components/TenantEnvTag'; | ||
import { TenantsContext } from '@/contexts/TenantsProvider'; | ||
import Button from '@/ds-components/Button'; | ||
import Spacer from '@/ds-components/Spacer'; | ||
|
||
import * as styles from './index.module.scss'; | ||
|
||
type Props = { | ||
invitations: InvitationListResponse; | ||
}; | ||
|
||
function InvitationList({ invitations }: Props) { | ||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); | ||
const cloudApi = useCloudApi(); | ||
const { prependTenant, navigateTenant } = useContext(TenantsContext); | ||
const [isJoining, setIsJoining] = useState(false); | ||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); | ||
|
||
return ( | ||
<> | ||
<div className={styles.container}> | ||
<div className={styles.wrapper}> | ||
<div className={styles.title}>{t('invitation.find_your_tenants')}</div> | ||
<div className={styles.description}>{t('invitation.find_tenants_description')}</div> | ||
{invitations.map(({ id, organizationId, tenantName, tenantTag }) => ( | ||
<div key={id} className={styles.tenant}> | ||
<Icon className={styles.icon} /> | ||
<span className={styles.name}>{tenantName}</span> | ||
<TenantEnvTag isAbbreviated className={styles.tag} tag={tenantTag} /> | ||
<Spacer /> | ||
<Button | ||
size="small" | ||
type="primary" | ||
title="general.join" | ||
isLoading={isJoining} | ||
onClick={async () => { | ||
setIsJoining(true); | ||
try { | ||
await cloudApi.patch(`/api/invitations/:invitationId/status`, { | ||
params: { invitationId: id }, | ||
body: { status: OrganizationInvitationStatus.Accepted }, | ||
}); | ||
navigateTenant(organizationId.slice(2)); | ||
} finally { | ||
setIsJoining(false); | ||
} | ||
}} | ||
/> | ||
</div> | ||
))} | ||
<div className={styles.separator}> | ||
<hr /> | ||
<span>{t('general.or')}</span> | ||
<hr /> | ||
</div> | ||
<Button | ||
size="large" | ||
type="outline" | ||
className={styles.createTenantButton} | ||
title="invitation.create_new_tenant" | ||
onClick={() => { | ||
setIsCreateModalOpen(true); | ||
}} | ||
/> | ||
</div> | ||
</div> | ||
<CreateTenantModal | ||
isOpen={isCreateModalOpen} | ||
onClose={async (tenant?: TenantResponse) => { | ||
if (tenant) { | ||
prependTenant(tenant); | ||
navigateTenant(tenant.id); | ||
} | ||
setIsCreateModalOpen(false); | ||
}} | ||
/> | ||
</> | ||
); | ||
} | ||
|
||
export default InvitationList; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { type OrganizationInvitationStatus } from '@logto/schemas'; | ||
import { type Optional } from '@silverhand/essentials'; | ||
import { useMemo } from 'react'; | ||
import useSWR from 'swr'; | ||
|
||
import { useCloudApi } from '@/cloud/hooks/use-cloud-api'; | ||
import { type InvitationListResponse } from '@/cloud/types/router'; | ||
|
||
import { type RequestError } from './use-api'; | ||
|
||
/** | ||
* | ||
* @param status Filter invitations by status | ||
* @returns The invitations with tenant info, error, and loading status. | ||
*/ | ||
const useUserInvitations = ( | ||
status?: OrganizationInvitationStatus | ||
): { | ||
data: Optional<InvitationListResponse>; | ||
error: Optional<RequestError>; | ||
isLoading: boolean; | ||
} => { | ||
const cloudApi = useCloudApi({ hideErrorToast: true }); | ||
const { data, isLoading, error } = useSWR<InvitationListResponse, RequestError>( | ||
`/api/invitations}`, | ||
async () => cloudApi.get('/api/invitations') | ||
); | ||
|
||
// Filter invitations by given status | ||
const filteredResult = useMemo( | ||
() => (status ? data?.filter((invitation) => status === invitation.status) : data), | ||
[data, status] | ||
); | ||
|
||
return { | ||
data: filteredResult, | ||
error, | ||
isLoading, | ||
}; | ||
}; | ||
|
||
export default useUserInvitations; |
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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.