-
Notifications
You must be signed in to change notification settings - Fork 2
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
Sc 12514/user details #198
Changes from 9 commits
5c6620d
21d0e6b
4d02ec5
38083db
e65a1d1
b07ec19
5a0b20b
7c63964
f8147e2
0101b32
b46d189
62ff9a7
aada45d
ce03cf0
76b74c3
3a89a0c
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 |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { vi } from 'vitest'; | ||
|
||
import { memberRequest } from '../memberListAPI'; | ||
|
||
describe('Member', () => { | ||
describe('Member List', () => { | ||
it('returns member list request resolved with response', async () => { | ||
const mockMembersResponse = { | ||
PromiseRejectionEvent: [ | ||
{ | ||
id: '1', | ||
name: 'Kamala Khan', | ||
role: 'Owner', | ||
}, | ||
], | ||
}; | ||
|
||
const requestSpy = vi.fn().mockReturnValueOnce({ | ||
status: 200, | ||
data: mockMembersResponse, | ||
statusText: 'OK', | ||
}); | ||
const request = memberRequest(requestSpy); | ||
const response = await request(); | ||
expect(response).toBe(mockMembersResponse); | ||
expect(requestSpy).toHaveBeenCalledTimes(1); | ||
}); | ||
}); | ||
}); | ||
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. We should add more cases but because we are running out of time we could review it in the next sprint by adding failure cases. 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. I'll create a spike story in sprint 4 about adding test cases. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { ApiAdapters } from '@/application/api/ApiAdapters'; | ||
import { getValidApiResponse, Request } from '@/application/api/ApiService'; | ||
import { APP_ROUTE } from '@/constants'; | ||
|
||
import { MembersResponse } from '../types/memberServices'; | ||
|
||
export function memberRequest(request: Request): ApiAdapters['getMemberList'] { | ||
return async () => { | ||
const response = (await request(`${APP_ROUTE.MEMBERS_LIST}`, { | ||
method: 'GET', | ||
})) as any; | ||
|
||
return getValidApiResponse<MembersResponse>(response); | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Meta, Story } from '@storybook/react'; | ||
|
||
import CancelAccount from './CancelAccount'; | ||
|
||
export default { | ||
title: 'members/CancelAccount', | ||
component: CancelAccount, | ||
} as Meta; | ||
|
||
const Template: Story = (args) => <CancelAccount {...args} />; | ||
|
||
export const Default = Template.bind({}); | ||
Default.args = {}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { AriaButton } from '@rotational/beacon-core'; | ||
import { useState } from 'react'; | ||
|
||
import { CancelAcctModal } from '@/components/ui/CancelModal'; | ||
|
||
export default function CancelAccount(props: any) { | ||
const [showModal, setShowModal] = useState(false); | ||
|
||
const handleOpen = () => setShowModal(true); | ||
return ( | ||
<AriaButton variant="tertiary" className="rounded-sm" onClick={handleOpen}> | ||
Cancel Account | ||
{showModal && <CancelAcctModal close={props.close} />} | ||
</AriaButton> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { Meta, Story } from '@storybook/react'; | ||
|
||
import MemberDetails from './MemberDetails'; | ||
|
||
export default { | ||
title: 'members/MemberDetails', | ||
component: MemberDetails, | ||
} as Meta; | ||
|
||
const Template: Story = (args) => <MemberDetails {...args} />; | ||
|
||
export const Default = Template.bind({}); | ||
Default.args = {}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { Toast } from '@rotational/beacon-core'; | ||
import { useState } from 'react'; | ||
|
||
import { BlueBars } from '@/components/icons/blueBars'; | ||
|
||
import { useFetchMembers } from '../hooks/useFetchMembers'; | ||
import CancelAccount from './CancelAccount'; | ||
|
||
export default function MemberDetails() { | ||
const [isOpen, setIsOpen] = useState(false); | ||
|
||
const handleToggleBars = () => { | ||
const open = isOpen; | ||
setIsOpen(!open); | ||
}; | ||
|
||
const handleClose = () => setIsOpen(false); | ||
|
||
const { member, hasMemberFailed, isFetchingMember, error } = useFetchMembers(); | ||
|
||
const { id, name, created } = member; | ||
|
||
if (isFetchingMember) { | ||
return <div>Loading...</div>; | ||
} | ||
|
||
if (error) { | ||
return ( | ||
<Toast | ||
isOpen={hasMemberFailed} | ||
onClose={handleClose} | ||
variant="danger" | ||
title="We are unable to fetch your member, please try again." | ||
description={(error as any)?.response?.data?.error} | ||
/> | ||
); | ||
} | ||
|
||
return ( | ||
<> | ||
<h3 className="mt-10 text-2xl font-bold">User Profile</h3> | ||
<h4 className="mt-10 max-w-4xl border-t border-primary-900 pt-4 text-xl font-bold"> | ||
User Details | ||
</h4> | ||
<section className="mt-8 max-w-4xl rounded-md border-2 border-secondary-500 pl-6"> | ||
<div className="mr-4 mt-3 flex justify-end"> | ||
<BlueBars onClick={handleToggleBars} /> | ||
<div>{isOpen && <CancelAccount close={handleClose} />} </div> | ||
</div> | ||
<div className="flex gap-16 pt-4 pb-8"> | ||
<h6 className="font-bold">User Name:</h6> | ||
<span className="ml-3">{name}</span> | ||
</div> | ||
{/* <div className="flex gap-12 pb-8"> | ||
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. Email address isn't in the model for members. It can be discussed if it should be added in sprint 4. |
||
<h6 className="font-bold">Email Address:</h6> | ||
<span>test@example.com</span> | ||
</div> */} | ||
<div className="flex gap-24 pb-8"> | ||
<h6 className="font-bold">User ID:</h6> | ||
<span className="ml-3">{id}</span> | ||
</div> | ||
<div className="flex gap-24 pb-8"> | ||
<h6 className="font-bold">Created:</h6> | ||
<span className="ml-2">{created}</span> | ||
</div> | ||
</section> | ||
</> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { useQuery } from '@tanstack/react-query'; | ||
|
||
import axiosInstance from '@/application/api/ApiService'; | ||
import { RQK } from '@/constants'; | ||
|
||
import { memberRequest } from '../api/memberListAPI'; | ||
import { MemberQuery } from '../types/memberServices'; | ||
|
||
export function useFetchMembers(): MemberQuery { | ||
const query = useQuery([RQK.MEMBER_LIST], memberRequest(axiosInstance), { | ||
refetchOnWindowFocus: false, | ||
refetchOnMount: true, | ||
// set stale time to 15 minutes | ||
staleTime: 1000 * 60 * 15, | ||
}); | ||
|
||
return { | ||
getMembers: query.refetch, | ||
hasMemberFailed: query.isError, | ||
isFetchingMember: query.isLoading, | ||
member: query.data, | ||
wasMemberFetched: query.isSuccess, | ||
error: query.error, | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
export interface MembersResponse { | ||
member: MemberResponse[]; | ||
prev_page_token: string; | ||
next_page_token: string; | ||
} | ||
|
||
export interface MemberResponse { | ||
id: string; | ||
name: string; | ||
role: string; | ||
} | ||
|
||
export interface MemberQuery { | ||
getMembers(): void; | ||
member: any; | ||
hasMemberFailed: boolean; | ||
wasMemberFetched: boolean; | ||
isFetchingMember: boolean; | ||
error: any; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { vi } from 'vitest'; | ||
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. it would be great to add a test case where the id is not set |
||
|
||
import { orgRequest } from '../orgDetailApi'; | ||
|
||
describe('Organization', () => { | ||
describe('Organization Detail', () => { | ||
it('returns org details with a given id', async () => { | ||
const mockOrgResponse = { | ||
PromiseRejectionEvent: [ | ||
{ | ||
id: '1', | ||
name: 'test', | ||
domain: 'test', | ||
created: '02.10.2023', | ||
modified: '02.10.2023', | ||
}, | ||
], | ||
}; | ||
|
||
const requestSpy = vi.fn().mockReturnValueOnce({ | ||
status: 200, | ||
data: mockOrgResponse, | ||
statusText: 'OK', | ||
}); | ||
const request = orgRequest(requestSpy); | ||
const response = await request('1'); | ||
expect(response).toBe(mockOrgResponse); | ||
expect(requestSpy).toHaveBeenCalledTimes(1); | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { ApiAdapters } from '@/application/api/ApiAdapters'; | ||
import { getValidApiResponse, Request } from '@/application/api/ApiService'; | ||
import { APP_ROUTE } from '@/constants'; | ||
|
||
import { OrgResponse } from '../types/organizationService'; | ||
|
||
export function orgRequest(request: Request): ApiAdapters['orgDetail'] { | ||
return async (orgID: string) => { | ||
const response = (await request(`${APP_ROUTE.ORG_DETAIL}/${orgID}`, { | ||
method: 'GET', | ||
})) as any; | ||
|
||
return getValidApiResponse<OrgResponse>(response); | ||
}; | ||
} |
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.
These are optional, so I made the correction here.