Skip to content

Commit

Permalink
clue/FEE: Implement User and Admin Menu Dropdowns #392 (#487)
Browse files Browse the repository at this point in the history
Feat:
- added a menu for the user settings in the /admin route that includes
edit profile and sign out buttons
- the sign out button logs a user out and redirects to /login page
- the edit profile button directs user to /admin/edit-profile page

---------

Co-authored-by: Ryan Furrer <[email protected]>
Co-authored-by: Chris Nowicki <[email protected]>
Co-authored-by: Mai Vang <[email protected]>
Co-authored-by: Alex Appleget <[email protected]>
Co-authored-by: Jennifer Tieu <[email protected]>
Co-authored-by: Shashi Lo <[email protected]>
Co-authored-by: Cody Epstein <[email protected]>
Co-authored-by: Danielle Lindblom <[email protected]>
  • Loading branch information
9 people authored Nov 19, 2024
1 parent 491d856 commit f92114f
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 18 deletions.
23 changes: 22 additions & 1 deletion components/AdminNav/AdminNav.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
import { AdminNav } from './AdminNav';
import { AuthContextProvider } from '@/context/AuthContextProvider';
import { render, screen } from '@testing-library/react';
import React from 'react';

const mockPush = jest.fn();
const mockUsePathname = jest.fn();

jest.mock('next/navigation', () => ({
useRouter() {
return {
push: mockPush,
};
},
usePathname() {
return mockUsePathname();
},
}));

describe('AdminNav Component', () => {
beforeEach(() => {
render(<AdminNav />);
jest.clearAllMocks();

render(
<AuthContextProvider>
<AdminNav />
</AuthContextProvider>,
);
});

it('should render the navigation links with correct href attributes', () => {
Expand Down
90 changes: 88 additions & 2 deletions components/AdminUserSettings/AdminUserSettings.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,98 @@
import { AdminUserSettings } from './AdminUserSettings';
import { render, screen } from '@testing-library/react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import React from 'react';

const mockPush = jest.fn();
const mockUsePathname = jest.fn();
const mockLogoutAccount = jest.fn();

const mockUseAuthContext = {
logoutAccount: mockLogoutAccount,
};

jest.mock('../../context/AuthContextProvider', () => ({
useAuthContext() {
return {
...mockUseAuthContext,
};
},
}));

jest.mock('next/navigation', () => ({
useRouter() {
return {
push: mockPush,
};
},
usePathname() {
return mockUsePathname();
},
}));

describe('AdminUserSettings Component', () => {
it('should render the component', () => {
beforeEach(() => {
jest.clearAllMocks();

render(<AdminUserSettings />);
});

it('should render the component', async () => {
const adminUserSettings = screen.getByTestId('admin-user-settings');

expect(adminUserSettings).toBeInTheDocument();
});

it('should show user options when clicked', () => {
const adminUserSettings = screen.getByTestId('admin-user-settings');

fireEvent.click(adminUserSettings);

waitFor(() => {
expect(screen.getByTestId('edit-profile-link')).toBeInTheDocument();
expect(screen.getByTestId('sign-out-button')).toBeInTheDocument();
});
});

it('should not show user options when closed', () => {
const adminUserSettings = screen.getByTestId('admin-user-settings');

fireEvent.click(adminUserSettings);

fireEvent.click(adminUserSettings);

waitFor(() => {
expect(screen.getByTestId('edit-profile-link')).not.toBeInTheDocument();
expect(screen.getByTestId('sign-out-button')).not.toBeInTheDocument();
});
});

it('should direct to /account/settings route when the edit profile button is clicked', () => {
const adminUserSettings = screen.getByTestId('admin-user-settings');

fireEvent.click(adminUserSettings);

waitFor(() => {
const editProfileButton = screen.getByTestId('edit-profile-link');
fireEvent.click(editProfileButton);
});

waitFor(() => {
expect(mockPush).toHaveBeenCalledWith('/account/settings');
});
});

it('should direct to /login route when the sign out button is clicked', () => {
const adminUserSettings = screen.getByTestId('admin-user-settings');

fireEvent.click(adminUserSettings);

waitFor(() => {
const signOutButton = screen.getByTestId('sign-out-button');
fireEvent.click(signOutButton);
});

waitFor(() => {
expect(mockPush).toHaveBeenCalledWith('/login');
});
});
});
80 changes: 65 additions & 15 deletions components/AdminUserSettings/AdminUserSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,75 @@
// Copyright (c) Gridiron Survivor.
// Licensed under the MIT License.

'use client';
import React, { JSX } from 'react';
import { Button } from '../Button/Button';
import { useDataStore } from '@/store/dataStore';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '../TableDropDownMenu/TableDropDownMenu';
import { LucideChevronsUpDown } from 'lucide-react';
import React from 'react';
import { useAuthContext } from '@/context/AuthContextProvider';
import { useRouter } from 'next/navigation';
import LinkCustom from '../LinkCustom/LinkCustom';

/**
* The admin user settings component.
* @returns The rendered admin user settings.
* Renders admin user settings.
* @returns {JSX.Element} The rendered admin user settings component.
*/
export const AdminUserSettings = (): React.JSX.Element => {
export const AdminUserSettings = (): JSX.Element => {
const router = useRouter();
const { logoutAccount } = useAuthContext();
const { user } = useDataStore((state) => state);

/**
* Handles the logout.
* @returns {Promise<void>} The logout promise.
*/
const handleLogout = async (): Promise<void> => {
try {
await logoutAccount();
router.push('/login');
} catch (error) {
throw error;
}
};

return (
<div
className="admin-user-settings flex gap-2 px-2 py-2 items-center outline outline-border rounded text-foreground"
data-testid="admin-user-settings"
>
<span className="bg-cyan-500 w-8 h-8 rounded-full" />
<p>Users Name</p>
<LucideChevronsUpDown
className="ml-auto text-muted-foreground"
size={16}
/>
</div>
<DropdownMenu>
<DropdownMenuTrigger
className="w-56 focus:outline-none mx-auto"
data-testid="admin-user-settings"
>
<div className="admin-user-settings w-full flex space-between gap-2 px-2 py-2 items-center border border-border rounded-lg text-foreground overflow-hidden">
<span className="bg-cyan-500 w-8 h-8 rounded-full" />
<p className="truncate ... w-36 ">{user.email}</p>
<LucideChevronsUpDown className="text-muted-foreground" size={16} />
</div>
</DropdownMenuTrigger>
<DropdownMenuContent className="border border-border rounded-lg p-0 w-56 ">
<DropdownMenuItem className="cursor-pointer rounded-b-none flex focus:bg-muted">
<LinkCustom
className="text-base no-underline hover:text-foreground w-full py-2 px-0 text-muted-foreground hover:underline"
href="/account/settings"
data-testid="edit-profile-link"
>
Edit Profile
</LinkCustom>
</DropdownMenuItem>
<DropdownMenuItem className="cursor-pointer rounded-none focus:bg-muted">
<Button
className="w-full text-base no-underline py-2 px-0 h-auto text-muted-foreground hover:text-foreground font-normal justify-normal hover:underline"
variant="link"
label="Sign Out"
onClick={handleLogout}
data-testid="sign-out-button"
/>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
};

0 comments on commit f92114f

Please sign in to comment.