Skip to content

Commit

Permalink
Show user linked account in admin panel user detail page (#200)
Browse files Browse the repository at this point in the history
  • Loading branch information
byn9826 authored Dec 12, 2024
1 parent f338b77 commit 2345bea
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 1 deletion.
7 changes: 6 additions & 1 deletion admin-panel/app/[lang]/users/[authId]/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
usePostApiV1UsersByAuthIdSmsMfaMutation,
usePostApiV1UsersByAuthIdVerifyEmailMutation,
usePutApiV1UsersByAuthIdMutation,
useDeleteApiV1UsersByAuthIdAccountLinkingMutation,
} from 'services/auth/api'
import { users } from 'tests/userMock'
import { roles } from 'tests/roleMock'
Expand Down Expand Up @@ -57,6 +58,7 @@ vi.mock(
usePostApiV1UsersByAuthIdSmsMfaMutation: vi.fn(),
usePostApiV1UsersByAuthIdVerifyEmailMutation: vi.fn(),
usePutApiV1UsersByAuthIdMutation: vi.fn(),
useDeleteApiV1UsersByAuthIdAccountLinkingMutation: vi.fn(),
}),
)

Expand Down Expand Up @@ -93,6 +95,7 @@ const mockEnrollSmsMfa = vi.fn()
const mockUnenrollEmailMfa = vi.fn()
const mockUnenrollSmsMfa = vi.fn()
const mockUnenrollOtpMfa = vi.fn()
const mockUnlinkAccount = vi.fn()

describe(
'user',
Expand Down Expand Up @@ -121,7 +124,9 @@ describe(
(useDeleteApiV1UsersByAuthIdEmailMfaMutation as Mock)
.mockReturnValue([mockUnenrollEmailMfa, { isLoading: false }]);
(useDeleteApiV1UsersByAuthIdOtpMfaMutation as Mock).mockReturnValue([mockUnenrollOtpMfa, { isLoading: false }]);
(useDeleteApiV1UsersByAuthIdSmsMfaMutation as Mock).mockReturnValue([mockUnenrollSmsMfa, { isLoading: false }])
(useDeleteApiV1UsersByAuthIdSmsMfaMutation as Mock).mockReturnValue([mockUnenrollSmsMfa, { isLoading: false }]);
(useDeleteApiV1UsersByAuthIdAccountLinkingMutation as Mock)
.mockReturnValue([mockUnlinkAccount, { isLoading: false }])
})

it(
Expand Down
44 changes: 44 additions & 0 deletions admin-panel/app/[lang]/users/[authId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useParams } from 'next/navigation'
import {
useEffect, useMemo, useState,
} from 'react'
import { ArrowTopRightOnSquareIcon } from '@heroicons/react/16/solid'
import UserEmailVerified from 'components/UserEmailVerified'
import { routeTool } from 'tools'
import EntityStatusLabel from 'components/EntityStatusLabel'
Expand All @@ -26,6 +27,7 @@ import DeleteButton from 'components/DeleteButton'
import useLocaleRouter from 'hooks/useLocaleRoute'
import {
PutUserReq,
useDeleteApiV1UsersByAuthIdAccountLinkingMutation,
useDeleteApiV1UsersByAuthIdConsentedAppsAndAppIdMutation,
useDeleteApiV1UsersByAuthIdEmailMfaMutation,
useDeleteApiV1UsersByAuthIdLockedIpsMutation,
Expand Down Expand Up @@ -95,6 +97,7 @@ const Page = () => {
const [unenrollEmailMfa] = useDeleteApiV1UsersByAuthIdEmailMfaMutation()
const [unenrollSmsMfa] = useDeleteApiV1UsersByAuthIdSmsMfaMutation()
const [unenrollOtpMfa] = useDeleteApiV1UsersByAuthIdOtpMfaMutation()
const [unlinkAccount] = useDeleteApiV1UsersByAuthIdAccountLinkingMutation()

const updateObj = useMemo(
() => {
Expand Down Expand Up @@ -180,13 +183,21 @@ const Page = () => {
await enrollEmailMfa({ authId: String(authId) })
}

const handleUnlink = async () => {
await unlinkAccount({ authId: String(authId) })
}

const handleToggleUserRole = (role: string) => {
const newRoles = userRoles?.includes(role)
? userRoles.filter((userRole) => role !== userRole)
: [...(userRoles ?? []), role]
setUserRoles(newRoles)
}

const handleClickLinkedAccount = () => {
router.push(`${routeTool.Internal.Users}/${user?.linkedAuthId}`)
}

const renderEmailButtons = (user: UserDetail) => {
if (user.socialAccountId) return null
return (
Expand Down Expand Up @@ -277,6 +288,17 @@ const Page = () => {
)
}

const renderUnlinkAccountButtons = () => {
return (
<Button
size='xs'
onClick={handleUnlink}
>
{t('users.unlink')}
</Button>
)
}

if (!user) return null

return (
Expand Down Expand Up @@ -384,6 +406,28 @@ const Page = () => {
</TableCell>
</Table.Row>
)}
{user.linkedAuthId && (
<Table.Row>
<Table.Cell>{t('users.linkedWith')}</Table.Cell>
<Table.Cell>
<div className='flex max-md:flex-col gap-2'>
<a
className='text-cyan-600 cursor-pointer flex items-center gap-1'
onClick={handleClickLinkedAccount}
>
{user.linkedAuthId}
<ArrowTopRightOnSquareIcon className='w-4 h-4' />
</a>
<div className='md:hidden'>
{renderUnlinkAccountButtons()}
</div>
</div>
</Table.Cell>
<TableCell className='max-md:hidden'>
{renderUnlinkAccountButtons()}
</TableCell>
</Table.Row>
)}
<Table.Row>
<Table.Cell>{t('users.locale')}</Table.Cell>
<Table.Cell>
Expand Down
16 changes: 16 additions & 0 deletions admin-panel/app/api/v1/users/[authId]/account-linking/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { sendS2SRequest } from 'app/api/request'

type Params = {
authId: string;
}

export async function DELETE (
request: Request, context: { params: Params },
) {
const authId = context.params.authId

return sendS2SRequest({
method: 'DELETE',
uri: `/api/v1/users/${authId}/account-linking`,
})
}
41 changes: 41 additions & 0 deletions admin-panel/services/auth/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,26 @@ const injectedRtkApi = api
}),
invalidatesTags: ['Users'],
}),
postApiV1UsersByAuthIdAccountLinkingAndLinkingAuthId: build.mutation<
PostApiV1UsersByAuthIdAccountLinkingAndLinkingAuthIdApiResponse,
PostApiV1UsersByAuthIdAccountLinkingAndLinkingAuthIdApiArg
>({
query: (queryArg) => ({
url: `/api/v1/users/${queryArg.authId}/account-linking/${queryArg.linkingAuthId}`,
method: 'POST',
}),
invalidatesTags: ['Users'],
}),
deleteApiV1UsersByAuthIdAccountLinking: build.mutation<
DeleteApiV1UsersByAuthIdAccountLinkingApiResponse,
DeleteApiV1UsersByAuthIdAccountLinkingApiArg
>({
query: (queryArg) => ({
url: `/api/v1/users/${queryArg.authId}/account-linking`,
method: 'DELETE',
}),
invalidatesTags: ['Users'],
}),
getApiV1LogsEmail: build.query<
GetApiV1LogsEmailApiResponse,
GetApiV1LogsEmailApiArg
Expand Down Expand Up @@ -543,6 +563,24 @@ export type DeleteApiV1UsersByAuthIdSmsMfaApiArg = {
/** The authId of the user */
authId: string;
};
export type PostApiV1UsersByAuthIdAccountLinkingAndLinkingAuthIdApiResponse =
/** status 200 undefined */ {
success?: boolean;
};
export type PostApiV1UsersByAuthIdAccountLinkingAndLinkingAuthIdApiArg = {
/** The authId of the user */
authId: string;
/** The authId of the account to link with */
linkingAuthId: string;
};
export type DeleteApiV1UsersByAuthIdAccountLinkingApiResponse =
/** status 200 undefined */ {
success?: boolean;
};
export type DeleteApiV1UsersByAuthIdAccountLinkingApiArg = {
/** The authId of the user */
authId: string;
};
export type GetApiV1LogsEmailApiResponse =
/** status 200 A list of email logs */ {
logs?: EmailLog[];
Expand Down Expand Up @@ -686,6 +724,7 @@ export type User = {
id: number;
authId: string;
email: string | null;
linkedAuthId?: string | null;
socialAccountId: string | null;
socialAccountType: string | null;
firstName: string | null;
Expand Down Expand Up @@ -785,6 +824,8 @@ export const {
useDeleteApiV1UsersByAuthIdOtpMfaMutation,
usePostApiV1UsersByAuthIdSmsMfaMutation,
useDeleteApiV1UsersByAuthIdSmsMfaMutation,
usePostApiV1UsersByAuthIdAccountLinkingAndLinkingAuthIdMutation,
useDeleteApiV1UsersByAuthIdAccountLinkingMutation,
useGetApiV1LogsEmailQuery,
useLazyGetApiV1LogsEmailQuery,
useGetApiV1LogsEmailByIdQuery,
Expand Down
2 changes: 2 additions & 0 deletions admin-panel/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@
"name": "Name",
"locale": "Locale",
"status": "Status",
"linkedWith": "Linked Account",
"unlink": "Unlink",
"loginCount": "Login Count",
"emailVerified": "Email Verified",
"emailNotVerified": "Email not Verified",
Expand Down
2 changes: 2 additions & 0 deletions admin-panel/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@
"name": "Nom",
"locale": "Langue",
"status": "Statut",
"linkedWith": "Compte associé",
"unlink": "Dissocier",
"loginCount": "Nombre de connexions",
"emailVerified": "E-mail vérifié",
"emailNotVerified": "E-mail non vérifié",
Expand Down

0 comments on commit 2345bea

Please sign in to comment.