From 55d88143db5d37f75e2a2e6a09f36229523b415f Mon Sep 17 00:00:00 2001 From: Kate Date: Tue, 25 Nov 2025 17:36:22 -0800 Subject: [PATCH 1/6] Added minor UI\UX updates for Mentorship portal --- .../api/internal/queries/mentorship.py | 14 ++- .../unit/components/ProgramActions.test.tsx | 102 ------------------ .../unit/components/ProgramCard.test.tsx | 60 ++++------- .../unit/pages/CreateModule.test.tsx | 2 +- .../unit/pages/CreateProgram.test.tsx | 8 +- .../__tests__/unit/pages/EditModule.test.tsx | 4 +- .../__tests__/unit/pages/EditProgram.test.tsx | 2 +- .../__tests__/unit/pages/Program.test.tsx | 10 +- .../[programKey]/modules/[moduleKey]/page.tsx | 5 +- .../mentorship/programs/[programKey]/page.tsx | 2 +- frontend/src/app/mentorship/programs/page.tsx | 4 +- .../programs/[programKey]/edit/page.tsx | 2 +- .../modules/[moduleKey]/edit/page.tsx | 2 +- .../[moduleKey]/issues/[issueId]/page.tsx | 4 +- .../modules/[moduleKey]/issues/page.tsx | 2 +- .../{[menteeHandle] => [menteeKey]}/page.tsx | 10 +- .../[programKey]/modules/[moduleKey]/page.tsx | 2 +- .../[programKey]/modules/create/page.tsx | 2 +- .../mentorship/programs/[programKey]/page.tsx | 2 +- frontend/src/components/CardDetailsPage.tsx | 88 +++++++-------- .../{ProgramActions.tsx => EntityActions.tsx} | 77 ++++++++----- frontend/src/components/ModuleCard.tsx | 19 ++-- frontend/src/components/ModuleForm.tsx | 11 +- frontend/src/components/ProgramCard.tsx | 34 +++--- frontend/src/components/ProgramForm.tsx | 11 +- frontend/src/components/SingleModuleCard.tsx | 82 ++------------ frontend/src/server/queries/menteeQueries.ts | 6 +- frontend/src/types/__generated__/graphql.ts | 4 +- .../__generated__/menteeQueries.generated.ts | 4 +- 29 files changed, 188 insertions(+), 387 deletions(-) delete mode 100644 frontend/__tests__/unit/components/ProgramActions.test.tsx rename frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/mentees/{[menteeHandle] => [menteeKey]}/page.tsx (97%) rename frontend/src/components/{ProgramActions.tsx => EntityActions.tsx} (52%) diff --git a/backend/apps/mentorship/api/internal/queries/mentorship.py b/backend/apps/mentorship/api/internal/queries/mentorship.py index 5f96abb035..cab13ed8d5 100644 --- a/backend/apps/mentorship/api/internal/queries/mentorship.py +++ b/backend/apps/mentorship/api/internal/queries/mentorship.py @@ -48,15 +48,13 @@ def is_mentor(self, login: str) -> bool: return Mentor.objects.filter(github_user=github_user).exists() @strawberry.field - def get_mentee_details( - self, program_key: str, module_key: str, mentee_handle: str - ) -> MenteeNode: + def get_mentee_details(self, program_key: str, module_key: str, mentee_key: str) -> MenteeNode: """Get detailed information about a mentee in a specific module.""" try: module = Module.objects.only("id").get(key=module_key, program__key=program_key) github_user = GithubUser.objects.only("login", "name", "avatar_url", "bio").get( - login=mentee_handle + login=mentee_key ) mentee = Mentee.objects.only("id", "experience_level", "domains", "tags").get( @@ -65,7 +63,7 @@ def get_mentee_details( is_enrolled = MenteeModule.objects.filter(mentee=mentee, module=module).exists() if not is_enrolled: - message = f"Mentee {mentee_handle} is not enrolled in module {module_key}" + message = f"Mentee {mentee_key} is not enrolled in module {module_key}" raise ObjectDoesNotExist(message) return MenteeNode( @@ -88,7 +86,7 @@ def get_mentee_module_issues( self, program_key: str, module_key: str, - mentee_handle: str, + mentee_key: str, limit: int = 20, offset: int = 0, ) -> list[IssueNode]: @@ -96,13 +94,13 @@ def get_mentee_module_issues( try: module = Module.objects.only("id").get(key=module_key, program__key=program_key) - github_user = GithubUser.objects.only("id").get(login=mentee_handle) + github_user = GithubUser.objects.only("id").get(login=mentee_key) mentee = Mentee.objects.only("id").get(github_user=github_user) is_enrolled = MenteeModule.objects.filter(mentee=mentee, module=module).exists() if not is_enrolled: - message = f"Mentee {mentee_handle} is not enrolled in module {module_key}" + message = f"Mentee {mentee_key} is not enrolled in module {module_key}" raise ObjectDoesNotExist(message) issues_qs = ( diff --git a/frontend/__tests__/unit/components/ProgramActions.test.tsx b/frontend/__tests__/unit/components/ProgramActions.test.tsx deleted file mode 100644 index 043b9d401d..0000000000 --- a/frontend/__tests__/unit/components/ProgramActions.test.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { fireEvent, screen } from '@testing-library/react' -import '@testing-library/jest-dom' -import { useSession as mockUseSession } from 'next-auth/react' -import { render } from 'wrappers/testUtil' -import { ProgramStatusEnum } from 'types/__generated__/graphql' -import ProgramActions from 'components/ProgramActions' - -const mockPush = jest.fn() -jest.mock('next/navigation', () => ({ - ...jest.requireActual('next/navigation'), - useRouter: () => ({ push: mockPush }), -})) - -jest.mock('next-auth/react', () => { - const actual = jest.requireActual('next-auth/react') - return { - ...actual, - useSession: jest.fn(), - } -}) - -describe('ProgramActions', () => { - let setStatus: jest.Mock - beforeEach(() => { - setStatus = jest.fn() - mockPush.mockClear() - }) - - beforeAll(async () => { - ;(mockUseSession as jest.Mock).mockReturnValue({ - data: { - user: { - name: 'Test User', - email: 'test@example.com', - login: 'testuser', - isLeader: true, - }, - expires: '2099-01-01T00:00:00.000Z', - }, - status: 'authenticated', - loading: false, - }) - }) - - test('renders and toggles dropdown', () => { - render() - const button = screen.getByTestId('program-actions-button') - fireEvent.click(button) - expect(screen.getByText('Add Module')).toBeInTheDocument() - expect(screen.getByText('Publish Program')).toBeInTheDocument() - fireEvent.click(button) - expect(screen.queryByText('Add Module')).not.toBeInTheDocument() - }) - - test('handles Add Module action', () => { - render() - const button = screen.getByTestId('program-actions-button') - fireEvent.click(button) - fireEvent.click(screen.getByRole('menuitem', { name: /add module/i })) - expect(mockPush).toHaveBeenCalled() - expect(setStatus).not.toHaveBeenCalled() - }) - - test('handles Publish action', () => { - render() - const button = screen.getByTestId('program-actions-button') - fireEvent.click(button) - fireEvent.click(screen.getByRole('menuitem', { name: /publish program/i })) - expect(setStatus).toHaveBeenCalledWith(ProgramStatusEnum.Published) - expect(mockPush).not.toHaveBeenCalled() - }) - - test('handles Move to Draft action', () => { - render() - const button = screen.getByTestId('program-actions-button') - fireEvent.click(button) - fireEvent.click(screen.getByRole('menuitem', { name: /move to draft/i })) - expect(setStatus).toHaveBeenCalledWith(ProgramStatusEnum.Draft) - }) - - test('handles Mark as Completed action', () => { - render() - const button = screen.getByTestId('program-actions-button') - fireEvent.click(button) - fireEvent.click(screen.getByRole('menuitem', { name: /mark as completed/i })) - expect(setStatus).toHaveBeenCalledWith(ProgramStatusEnum.Completed) - }) - - test('dropdown closes on outside click', () => { - render( -
- - -
- ) - const button = screen.getByTestId('program-actions-button') - fireEvent.click(button) - expect(screen.getByText('Add Module')).toBeInTheDocument() - fireEvent.mouseDown(screen.getByTestId('outside')) - expect(screen.queryByText('Add Module')).not.toBeInTheDocument() - }) -}) diff --git a/frontend/__tests__/unit/components/ProgramCard.test.tsx b/frontend/__tests__/unit/components/ProgramCard.test.tsx index 288f0074ef..0381faf3d8 100644 --- a/frontend/__tests__/unit/components/ProgramCard.test.tsx +++ b/frontend/__tests__/unit/components/ProgramCard.test.tsx @@ -106,8 +106,8 @@ describe('ProgramCard', () => { expect(screen.getByText('admin')).toBeInTheDocument() }) - it('calls onView when Preview button is clicked', () => { - render( + it('calls onView when card is clicked', () => { + const { container } = render( { /> ) - const previewButton = screen.getByText('Preview').closest('button') - fireEvent.click(previewButton!) + const card = container.querySelector('[role="button"][tabindex="0"]') as HTMLElement + fireEvent.click(card) expect(mockOnView).toHaveBeenCalledWith('test-program') }) - it('navigates to edit page when Edit Program is clicked', () => { + it('navigates to edit page when Edit is clicked', () => { const router = useRouter() render( @@ -135,7 +135,7 @@ describe('ProgramCard', () => { ) fireEvent.click(screen.getByTestId('program-actions-button')) - fireEvent.click(screen.getByText('Edit Program')) + fireEvent.click(screen.getByText('Edit')) expect(router.push).toHaveBeenCalledWith('/my/mentorship/programs/test-program/edit') }) @@ -165,13 +165,15 @@ describe('ProgramCard', () => { /> ) - expect(screen.getByText('View Details')).toBeInTheDocument() + const card = document.querySelector('[role="button"][tabindex="0"]') + expect(card).toBeInTheDocument() expect(screen.queryByText('Preview')).not.toBeInTheDocument() expect(screen.queryByText('Edit')).not.toBeInTheDocument() + expect(screen.queryByText('View Details')).not.toBeInTheDocument() }) - it('calls onView when View Details button is clicked', () => { - render( + it('calls onView when card is clicked', () => { + const { container } = render( { /> ) - const viewButton = screen.getByText('View Details').closest('button') - fireEvent.click(viewButton!) + const card = container.querySelector('[role="button"][tabindex="0"]') as HTMLElement + fireEvent.click(card) expect(mockOnView).toHaveBeenCalledWith('test-program') }) @@ -250,7 +252,7 @@ describe('ProgramCard', () => { }) describe('Description Handling', () => { - it('renders long descriptions with line-clamp-6 CSS class', () => { + it('renders long descriptions with line-clamp-8 CSS class', () => { const longDescription = 'A'.repeat(300) // Long enough to trigger line clamping const longDescProgram = { ...baseMockProgram, description: longDescription } @@ -266,7 +268,7 @@ describe('ProgramCard', () => { expect(screen.getByText(longDescription)).toBeInTheDocument() expect(screen.getByText(longDescription)).toBeInTheDocument() const descriptionElement = screen.getByText(longDescription) - expect(descriptionElement).toHaveClass('line-clamp-6') + expect(descriptionElement).toHaveClass('line-clamp-8') }) it('shows full description when short', () => { @@ -285,7 +287,7 @@ describe('ProgramCard', () => { expect(screen.getByText('Short description')).toBeInTheDocument() const descriptionElement = screen.getByText('Short description') - expect(descriptionElement).toHaveClass('line-clamp-6') + expect(descriptionElement).toHaveClass('line-clamp-8') }) it('shows fallback text when description is empty', () => { @@ -382,19 +384,6 @@ describe('ProgramCard', () => { }) describe('Icons', () => { - it('renders eye icon for Preview button', () => { - render( - - ) - - expect(screen.getByTestId('icon-eye')).toBeInTheDocument() - }) - it('renders actions button for admin menu', () => { render( { expect(screen.getByTestId('program-actions-button')).toBeInTheDocument() }) - - it('renders eye icon for View Details button', () => { - render( - - ) - - expect(screen.getByTestId('icon-eye')).toBeInTheDocument() - }) }) describe('Edge Cases', () => { - it('shows Edit Program in actions menu for admin access', () => { + it('shows Edit in actions menu for admin access', () => { render( { ) fireEvent.click(screen.getByTestId('program-actions-button')) - expect(screen.getByText('Edit Program')).toBeInTheDocument() + expect(screen.getByText('Edit')).toBeInTheDocument() }) it('handles program with minimal data', () => { diff --git a/frontend/__tests__/unit/pages/CreateModule.test.tsx b/frontend/__tests__/unit/pages/CreateModule.test.tsx index 6368b8645e..a334de0790 100644 --- a/frontend/__tests__/unit/pages/CreateModule.test.tsx +++ b/frontend/__tests__/unit/pages/CreateModule.test.tsx @@ -66,7 +66,7 @@ describe('CreateModulePage', () => { render() // Fill all inputs - fireEvent.change(screen.getByLabelText(/Module Name/i), { + fireEvent.change(screen.getByLabelText('Name *'), { target: { value: 'My Test Module' }, }) fireEvent.change(screen.getByLabelText(/Description/i), { diff --git a/frontend/__tests__/unit/pages/CreateProgram.test.tsx b/frontend/__tests__/unit/pages/CreateProgram.test.tsx index b32394cec8..80ae5db284 100644 --- a/frontend/__tests__/unit/pages/CreateProgram.test.tsx +++ b/frontend/__tests__/unit/pages/CreateProgram.test.tsx @@ -60,7 +60,7 @@ describe('CreateProgramPage (comprehensive tests)', () => { render() - expect(screen.queryByLabelText('Program Name *')).not.toBeInTheDocument() + expect(screen.queryByLabelText('Name *')).not.toBeInTheDocument() }) test('redirects with toast if not a project leader', async () => { @@ -103,7 +103,7 @@ describe('CreateProgramPage (comprehensive tests)', () => { render() - expect(await screen.findByLabelText('Program Name *')).toBeInTheDocument() + expect(await screen.findByLabelText('Name *')).toBeInTheDocument() }) test('submits form and redirects on success', async () => { @@ -127,7 +127,7 @@ describe('CreateProgramPage (comprehensive tests)', () => { render() - fireEvent.change(screen.getByLabelText('Program Name *'), { + fireEvent.change(screen.getByLabelText('Name *'), { target: { value: 'Test Program' }, }) fireEvent.change(screen.getByLabelText('Description *'), { @@ -186,7 +186,7 @@ describe('CreateProgramPage (comprehensive tests)', () => { render() - fireEvent.change(screen.getByLabelText('Program Name *'), { + fireEvent.change(screen.getByLabelText('Name *'), { target: { value: 'Test Program' }, }) fireEvent.change(screen.getByLabelText('Description *'), { diff --git a/frontend/__tests__/unit/pages/EditModule.test.tsx b/frontend/__tests__/unit/pages/EditModule.test.tsx index b535b00259..e93e30bdd7 100644 --- a/frontend/__tests__/unit/pages/EditModule.test.tsx +++ b/frontend/__tests__/unit/pages/EditModule.test.tsx @@ -86,8 +86,8 @@ describe('EditModulePage', () => { expect(await screen.findByDisplayValue('Existing Module')).toBeInTheDocument() // Modify values - fireEvent.change(screen.getByLabelText(/Module Name/i), { - target: { value: 'Updated Module Name' }, + fireEvent.change(screen.getByLabelText('Name *'), { + target: { value: 'Updated Name' }, }) fireEvent.change(screen.getByLabelText(/Description/i), { target: { value: 'Updated description' }, diff --git a/frontend/__tests__/unit/pages/EditProgram.test.tsx b/frontend/__tests__/unit/pages/EditProgram.test.tsx index a82e7a7bf6..ff4cc020ab 100644 --- a/frontend/__tests__/unit/pages/EditProgram.test.tsx +++ b/frontend/__tests__/unit/pages/EditProgram.test.tsx @@ -88,7 +88,7 @@ describe('EditProgramPage', () => { render() - expect(await screen.findByLabelText('Program Name *')).toBeInTheDocument() + expect(await screen.findByLabelText('Name *')).toBeInTheDocument() expect(screen.getByDisplayValue('Test')).toBeInTheDocument() }) }) diff --git a/frontend/__tests__/unit/pages/Program.test.tsx b/frontend/__tests__/unit/pages/Program.test.tsx index 68aef54469..dff2dcbd27 100644 --- a/frontend/__tests__/unit/pages/Program.test.tsx +++ b/frontend/__tests__/unit/pages/Program.test.tsx @@ -61,7 +61,7 @@ describe('ProgramsPage Component', () => { }) expect(screen.getByText('This is a summary of Program 1.')).toBeInTheDocument() - expect(screen.getByText('View Details')).toBeInTheDocument() + // Card is now clickable, no separate "View Details" button }) test('shows empty message when no programs found', async () => { @@ -91,14 +91,16 @@ describe('ProgramsPage Component', () => { }) }) - test('navigates to program detail page on View Details click', async () => { + test('navigates to program detail page on card click', async () => { render() await waitFor(() => { - const viewButton = screen.getByText('View Details') - fireEvent.click(viewButton) + expect(screen.getByText('Program 1')).toBeInTheDocument() }) + const card = screen.getAllByRole('button')[0] // First program card + fireEvent.click(card) + expect(mockRouter.push).toHaveBeenCalledWith('/mentorship/programs/program_1') }) }) diff --git a/frontend/src/app/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx b/frontend/src/app/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx index c986ddf5ce..c6a7ad7b00 100644 --- a/frontend/src/app/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx +++ b/frontend/src/app/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx @@ -1,7 +1,7 @@ 'use client' import { useQuery } from '@apollo/client/react' -import upperFirst from 'lodash/upperFirst' +import capitalize from 'lodash/capitalize' import { useParams } from 'next/navigation' import { useEffect, useState } from 'react' import { ErrorDisplay, handleAppError } from 'app/global-error' @@ -49,7 +49,7 @@ const ModuleDetailsPage = () => { } const moduleDetails = [ - { label: 'Experience Level', value: upperFirst(module.experienceLevel) }, + { label: 'Experience Level', value: capitalize(module.experienceLevel) }, { label: 'Start Date', value: formatDate(module.startedAt) }, { label: 'End Date', value: formatDate(module.endedAt) }, { @@ -63,7 +63,6 @@ const ModuleDetailsPage = () => { admins={admins} details={moduleDetails} domains={module.domains} - labels={module.labels} mentors={module.mentors} summary={module.description} tags={module.tags} diff --git a/frontend/src/app/mentorship/programs/[programKey]/page.tsx b/frontend/src/app/mentorship/programs/[programKey]/page.tsx index a80f54e179..23f25d0451 100644 --- a/frontend/src/app/mentorship/programs/[programKey]/page.tsx +++ b/frontend/src/app/mentorship/programs/[programKey]/page.tsx @@ -13,7 +13,7 @@ import DetailsCard from 'components/CardDetailsPage' import LoadingSpinner from 'components/LoadingSpinner' const ProgramDetailsPage = () => { - const { programKey } = useParams() as { programKey: string } + const { programKey } = useParams<{ programKey: string }>() const searchParams = useSearchParams() const router = useRouter() const shouldRefresh = searchParams.get('refresh') === 'true' diff --git a/frontend/src/app/mentorship/programs/page.tsx b/frontend/src/app/mentorship/programs/page.tsx index d02205b9b3..90524eac4f 100644 --- a/frontend/src/app/mentorship/programs/page.tsx +++ b/frontend/src/app/mentorship/programs/page.tsx @@ -54,7 +54,9 @@ const ProgramsPage = () => { >
{programs && - programs.filter((p) => p.status === ProgramStatusEnum.Published).map(renderProgramCard)} + programs + .filter((p) => p.status?.toUpperCase() === ProgramStatusEnum.Published) + .map(renderProgramCard)}
) diff --git a/frontend/src/app/my/mentorship/programs/[programKey]/edit/page.tsx b/frontend/src/app/my/mentorship/programs/[programKey]/edit/page.tsx index d4d0b8bc0b..d323bb943b 100644 --- a/frontend/src/app/my/mentorship/programs/[programKey]/edit/page.tsx +++ b/frontend/src/app/my/mentorship/programs/[programKey]/edit/page.tsx @@ -17,7 +17,7 @@ import LoadingSpinner from 'components/LoadingSpinner' import ProgramForm from 'components/ProgramForm' const EditProgramPage = () => { const router = useRouter() - const { programKey } = useParams() as { programKey: string } + const { programKey } = useParams<{ programKey: string }>() const { data: session, status: sessionStatus } = useSession() const [updateProgram, { loading: mutationLoading }] = useMutation(UpdateProgramDocument) const { diff --git a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/edit/page.tsx b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/edit/page.tsx index 18d30c86c3..d1b294d107 100644 --- a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/edit/page.tsx +++ b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/edit/page.tsx @@ -16,7 +16,7 @@ import LoadingSpinner from 'components/LoadingSpinner' import ModuleForm from 'components/ModuleForm' const EditModulePage = () => { - const { programKey, moduleKey } = useParams() as { programKey: string; moduleKey: string } + const { programKey, moduleKey } = useParams<{ programKey: string; moduleKey: string }>() const router = useRouter() const { data: sessionData, status: sessionStatus } = useSession() diff --git a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx index cb406b4704..0af0bafed2 100644 --- a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx +++ b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/[issueId]/page.tsx @@ -24,7 +24,7 @@ import SecondaryCard from 'components/SecondaryCard' import { TruncatedText } from 'components/TruncatedText' const ModuleIssueDetailsPage = () => { - const params = useParams() as { programKey: string; moduleKey: string; issueId: string } + const params = useParams<{ programKey: string; moduleKey: string; issueId: string }>() const { programKey, moduleKey, issueId } = params const formatDeadline = (deadline: string | null) => { @@ -266,7 +266,7 @@ const ModuleIssueDetailsPage = () => { className="flex items-center justify-between gap-2 rounded-lg bg-gray-200 p-3 dark:bg-gray-700" > {a.avatarUrl ? ( diff --git a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx index d24ea54c66..d5583561f3 100644 --- a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx +++ b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/issues/page.tsx @@ -16,7 +16,7 @@ const LABEL_ALL = 'all' const MAX_VISIBLE_LABELS = 5 const IssuesPage = () => { - const { programKey, moduleKey } = useParams() as { programKey: string; moduleKey: string } + const { programKey, moduleKey } = useParams<{ programKey: string; moduleKey: string }>() const router = useRouter() const searchParams = useSearchParams() const [selectedLabel, setSelectedLabel] = useState(searchParams.get('label') || LABEL_ALL) diff --git a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/mentees/[menteeHandle]/page.tsx b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/mentees/[menteeKey]/page.tsx similarity index 97% rename from frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/mentees/[menteeHandle]/page.tsx rename to frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/mentees/[menteeKey]/page.tsx index 50eb297b0d..9c5e23d670 100644 --- a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/mentees/[menteeHandle]/page.tsx +++ b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/mentees/[menteeKey]/page.tsx @@ -13,11 +13,11 @@ import LoadingSpinner from 'components/LoadingSpinner' import SecondaryCard from 'components/SecondaryCard' const MenteeProfilePage = () => { - const { programKey, moduleKey, menteeHandle } = useParams() as { + const { programKey, moduleKey, menteeKey } = useParams<{ programKey: string moduleKey: string - menteeHandle: string - } + menteeKey: string + }>() const [menteeDetails, setMenteeDetails] = useState(null) const [menteeIssues, setMenteeIssues] = useState([]) @@ -29,9 +29,9 @@ const MenteeProfilePage = () => { variables: { programKey, moduleKey, - menteeHandle, + menteeKey, }, - skip: !programKey || !moduleKey || !menteeHandle, + skip: !programKey || !moduleKey || !menteeKey, fetchPolicy: 'cache-and-network', }) diff --git a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx index 957aba1023..989c30deae 100644 --- a/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx +++ b/frontend/src/app/my/mentorship/programs/[programKey]/modules/[moduleKey]/page.tsx @@ -12,7 +12,7 @@ import LoadingSpinner from 'components/LoadingSpinner' import { getSimpleDuration } from 'components/ModuleCard' const ModuleDetailsPage = () => { - const { programKey, moduleKey } = useParams() as { programKey: string; moduleKey: string } + const { programKey, moduleKey } = useParams<{ programKey: string; moduleKey: string }>() const [module, setModule] = useState(null) const [admins, setAdmins] = useState(null) const [isLoading, setIsLoading] = useState(true) diff --git a/frontend/src/app/my/mentorship/programs/[programKey]/modules/create/page.tsx b/frontend/src/app/my/mentorship/programs/[programKey]/modules/create/page.tsx index 57b385b09e..a0666f8603 100644 --- a/frontend/src/app/my/mentorship/programs/[programKey]/modules/create/page.tsx +++ b/frontend/src/app/my/mentorship/programs/[programKey]/modules/create/page.tsx @@ -18,7 +18,7 @@ import ModuleForm from 'components/ModuleForm' const CreateModulePage = () => { const router = useRouter() - const { programKey } = useParams() as { programKey: string } + const { programKey } = useParams<{ programKey: string }>() const { data: sessionData, status: sessionStatus } = useSession() const [createModule, { loading: mutationLoading }] = useMutation(CreateModuleDocument) diff --git a/frontend/src/app/my/mentorship/programs/[programKey]/page.tsx b/frontend/src/app/my/mentorship/programs/[programKey]/page.tsx index a605c27597..e25859a092 100644 --- a/frontend/src/app/my/mentorship/programs/[programKey]/page.tsx +++ b/frontend/src/app/my/mentorship/programs/[programKey]/page.tsx @@ -17,7 +17,7 @@ import DetailsCard from 'components/CardDetailsPage' import LoadingSpinner from 'components/LoadingSpinner' const ProgramDetailsPage = () => { - const { programKey } = useParams() as { programKey: string } + const { programKey } = useParams<{ programKey: string }>() const { data: session } = useSession() const username = (session as ExtendedSession)?.user?.login diff --git a/frontend/src/components/CardDetailsPage.tsx b/frontend/src/components/CardDetailsPage.tsx index 811c6b0f16..ab5d4a2314 100644 --- a/frontend/src/components/CardDetailsPage.tsx +++ b/frontend/src/components/CardDetailsPage.tsx @@ -9,7 +9,6 @@ import { } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import upperFirst from 'lodash/upperFirst' -import { useRouter } from 'next/navigation' import { useSession } from 'next-auth/react' import type { ExtendedSession } from 'types/auth' import type { DetailsCardProps } from 'types/card' @@ -18,6 +17,7 @@ import { scrollToAnchor } from 'utils/scrollToAnchor' import { getSocialIcon } from 'utils/urlIconMappings' import AnchorTitle from 'components/AnchorTitle' import ChapterMapWrapper from 'components/ChapterMapWrapper' +import EntityActions from 'components/EntityActions' import HealthMetrics from 'components/HealthMetrics' import InfoBlock from 'components/InfoBlock' import Leaders from 'components/Leaders' @@ -26,7 +26,6 @@ import MenteeContributorsList from 'components/MenteeContributorsList' import MetricsScoreCircle from 'components/MetricsScoreCircle' import Milestones from 'components/Milestones' import ModuleCard from 'components/ModuleCard' -import ProgramActions from 'components/ProgramActions' import RecentIssues from 'components/RecentIssues' import RecentPullRequests from 'components/RecentPullRequests' import RecentReleases from 'components/RecentReleases' @@ -76,7 +75,6 @@ const DetailsCard = ({ userSummary, }: DetailsCardProps) => { const { data } = useSession() - const router = useRouter() // compute styles based on type prop const secondaryCardStyles = (() => { @@ -94,36 +92,20 @@ const DetailsCard = ({

{title}

- {type === 'program' && accessLevel === 'admin' && canUpdateStatus && ( - - )} - {type === 'module' && - accessLevel === 'admin' && - admins?.some( - (admin) => admin.login === ((data as ExtendedSession)?.user?.login as string) - ) && ( -
- - -
- )}
+ {type === 'program' && accessLevel === 'admin' && canUpdateStatus && ( + + )} + {type === 'module' && + accessLevel === 'admin' && + admins?.some( + (admin) => admin.login === ((data as ExtendedSession)?.user?.login as string) + ) && } {!isActive && } {isArchived && type === 'repository' && } {IS_PROJECT_HEALTH_ENABLED && type === 'project' && healthMetricsData.length > 0 && ( @@ -223,26 +205,28 @@ const DetailsCard = ({ )} {(type === 'program' || type === 'module') && ( <> -
- {tags?.length > 0 && ( - } - isDisabled={true} - /> - )} - {domains?.length > 0 && ( - } - isDisabled={true} - /> - )} -
+ {((tags?.length || 0) > 0 || (domains?.length || 0) > 0) && ( +
+ {tags?.length > 0 && ( + } + isDisabled={true} + /> + )} + {domains?.length > 0 && ( + } + isDisabled={true} + /> + )} +
+ )} {labels?.length > 0 && (
void + moduleKey?: string + status?: string + setStatus?: (newStatus: string) => void } -const ProgramActions: React.FC = ({ programKey, status, setStatus }) => { +const EntityActions: React.FC = ({ + type, + programKey, + moduleKey, + status, + setStatus, +}) => { const router = useRouter() const [dropdownOpen, setDropdownOpen] = useState(false) const dropdownRef = useRef(null) const handleAction = (actionKey: string) => { switch (actionKey) { - case 'edit Program': + case 'edit_program': router.push(`/my/mentorship/programs/${programKey}/edit`) break case 'create_module': router.push(`/my/mentorship/programs/${programKey}/modules/create`) break + case 'edit_module': + if (moduleKey) { + router.push(`/my/mentorship/programs/${programKey}/modules/${moduleKey}/edit`) + } + break + case 'view_issues': + if (moduleKey) { + router.push(`/my/mentorship/programs/${programKey}/modules/${moduleKey}/issues`) + } + break case 'publish': - setStatus(ProgramStatusEnum.Published) + setStatus?.(ProgramStatusEnum.Published) break case 'draft': - setStatus(ProgramStatusEnum.Draft) + setStatus?.(ProgramStatusEnum.Draft) break case 'completed': - setStatus(ProgramStatusEnum.Completed) + setStatus?.(ProgramStatusEnum.Completed) break } setDropdownOpen(false) } - const options = [ - { key: 'edit Program', label: 'Edit Program' }, - { key: 'create_module', label: 'Add Module' }, - ...(status === ProgramStatusEnum.Draft ? [{ key: 'publish', label: 'Publish Program' }] : []), - ...(status === ProgramStatusEnum.Published || status === ProgramStatusEnum.Completed - ? [{ key: 'draft', label: 'Move to Draft' }] - : []), - ...(status === ProgramStatusEnum.Published - ? [{ key: 'completed', label: 'Mark as Completed' }] - : []), - ] + const options = + type === 'program' + ? [ + { key: 'edit_program', label: 'Edit' }, + { key: 'create_module', label: 'Add Module' }, + ...(status === ProgramStatusEnum.Draft ? [{ key: 'publish', label: 'Publish' }] : []), + ...(status === ProgramStatusEnum.Published || status === ProgramStatusEnum.Completed + ? [{ key: 'draft', label: 'Unpublish' }] + : []), + ...(status === ProgramStatusEnum.Published + ? [{ key: 'completed', label: 'Mark as Completed' }] + : []), + ] + : [ + { key: 'edit_module', label: 'Edit' }, + { key: 'view_issues', label: 'View Issues' }, + ] useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -67,11 +91,14 @@ const ProgramActions: React.FC = ({ programKey, status, set return (
@@ -99,4 +126,4 @@ const ProgramActions: React.FC = ({ programKey, status, set ) } -export default ProgramActions +export default EntityActions diff --git a/frontend/src/components/ModuleCard.tsx b/frontend/src/components/ModuleCard.tsx index 66c71f8d54..58cf05bc74 100644 --- a/frontend/src/components/ModuleCard.tsx +++ b/frontend/src/components/ModuleCard.tsx @@ -38,12 +38,13 @@ const ModuleCard = ({ modules, accessLevel, admins }: ModuleCardProps) => { } const displayedModule = showAllModule ? modules : modules.slice(0, 4) + const isAdmin = accessLevel === 'admin' return (
{displayedModule.map((module) => { - return + return })}
{modules.length > 4 && ( @@ -69,26 +70,26 @@ const ModuleCard = ({ modules, accessLevel, admins }: ModuleCardProps) => { ) } -const ModuleItem = ({ details }: { details: Module }) => { +const ModuleItem = ({ module, isAdmin }: { module: Module; isAdmin: boolean }) => { const pathname = usePathname() return (
- + - - + + - {details.labels && details.labels.length > 0 && ( + {isAdmin && module.labels && module.labels.length > 0 && (
- +
)}
diff --git a/frontend/src/components/ModuleForm.tsx b/frontend/src/components/ModuleForm.tsx index ace8d99723..0768291a4e 100644 --- a/frontend/src/components/ModuleForm.tsx +++ b/frontend/src/components/ModuleForm.tsx @@ -63,13 +63,10 @@ const ModuleForm = ({
-

- Module Information -

-

- Module Configuration -

-

- Additional Details -