Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions apps/frontend/src/app/components/AccountsPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use client';

import React from 'react';
import StaffCard from './StaffCard';

interface User {
user_id: number;
name: string;
email: string;
is_admin: boolean;
created_at?: string;
}

const mockUsers: User[] = [
{ user_id: 1, name: 'Mehana Nagarur', email: 'nagarur.m@northeastern.edu', is_admin: true },
{ user_id: 2, name: 'Alex Rivera', email: 'rivera.a@northeastern.edu', is_admin: true },
{ user_id: 3, name: 'Jordan Lee', email: 'lee.j@northeastern.edu', is_admin: true },
{ user_id: 4, name: 'Priya Sharma', email: 'sharma.p@northeastern.edu', is_admin: true },
{ user_id: 5, name: 'Chris Nguyen', email: 'nguyen.c@northeastern.edu', is_admin: true },
{ user_id: 6, name: 'Taylor Brooks', email: 'brooks.t@northeastern.edu', is_admin: false },
{ user_id: 7, name: 'Sam Patel', email: 'patel.s@northeastern.edu', is_admin: false },
{ user_id: 8, name: 'Morgan Clarke', email: 'clarke.m@northeastern.edu', is_admin: false },
{ user_id: 9, name: 'Jamie Wu', email: 'wu.j@northeastern.edu', is_admin: false },
{ user_id: 10, name: 'Riley Thompson', email: 'thompson.r@northeastern.edu',is_admin: false },
{ user_id: 11, name: 'Avery Johnson', email: 'johnson.a@northeastern.edu', is_admin: false },
{ user_id: 12, name: 'Casey Martinez', email: 'martinez.c@northeastern.edu',is_admin: false },
{ user_id: 13, name: 'Drew Hassan', email: 'hassan.d@northeastern.edu', is_admin: false },
{ user_id: 14, name: 'Quinn Okafor', email: 'okafor.q@northeastern.edu', is_admin: false },
{ user_id: 15, name: 'Blake Fernandez', email: 'fernandez.b@northeastern.edu',is_admin: false },
];

export const facilitationTeam = mockUsers.filter(u => u.is_admin);
export const teamMembers = mockUsers.filter(u => !u.is_admin);



export default function AccountsPage() {
return (
<div>
<h1 className="![font-family:var(--font-heading)] !text-[length:var(--font-size-heading-1)] !font-semibold">Accounts</h1>
<h3 className="![font-family:var(--font-heading)] !text-[length:var(--font-size-heading-3)] !font-semibold">Core BRANCH Facilitation Team</h3>
<div className="grid grid-cols-3 md:grid-cols-7 gap-3 !pt-3 !pb-7">
{facilitationTeam.map(user => (
<StaffCard key={user.user_id} name={user.name} />
))}
</div>
<h3 className="![font-family:var(--font-heading)] !text-[length:var(--font-size-heading-3)] !font-semibold">BRANCH Team Members</h3>
<div className="grid grid-cols-3 md:grid-cols-7 gap-3 !pt-3 !pb-7">
{teamMembers.map(user => (
<StaffCard key={user.user_id} name={user.name} />
))}
</div>
</div>
);
}
24 changes: 12 additions & 12 deletions apps/frontend/src/app/components/StaffCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,33 @@
import React from 'react';
import Image from 'next/image';
import { useState } from 'react';
import { PiUserCircleThin } from "react-icons/pi";


interface StaffCardProps {
image?: string;
name: string;
title: string;
}


export default function StaffCard({
image,
name,
title
name
}: StaffCardProps) {
const [imgError, setImgError] = useState(false);

return (
<div className="!w-[90px] h-100 flex flex-col items-center !pt-3 !gap-1 overflow-hidden">
{(image && !imgError) ? (
<Image src={image} alt="Staff" width={90} height={105} className="object-cover ![aspect-ratio:44/51] w-full" onError={() => setImgError(true)}/>
) : (
<div className="!w-[90px] ![aspect-ratio:44/51] bg-accent-dark-green"></div>
)}
<div className="flex flex-col items-center gap-1 !w-[90px]">
<p className="![font-family:var(--font-body)] !text-[length:var(--font-size-body)] !font-normal break-words w-full text-center">{name}</p>
<p className="![font-family:var(--font-family-body)] !text-[length:var(--font-size-callout)] !font-bold break-words w-full text-center">{title}</p>
<div data-testid="staff-card" className="relative !w-full !flex flex-col items-center !p-3 !gap-1 overflow-hidden !border-1 !border-[var(--color-black-300)] !border-solid">
<div className="w-full aspect-square">
{(image && !imgError) ? (
<Image src={image} alt="Staff" width={120} height={120} className="object-cover w-full h-full" onError={() => setImgError(true)}/>
) : (
<div data-testid="staff-placeholder" className="w-full h-full flex items-center justify-center bg-[var(--color-primary-300)]">
<PiUserCircleThin size="100%" className="text-[var(--color-accent-dark-green)]" />
</div>
)}
</div>
<p className="![font-family:var(--font-body)] !text-[length:var(--font-size-callout)] !font-bold break-words w-full text-center !pt-1 !pb-2">{name}</p>
</div>
)
}
35 changes: 35 additions & 0 deletions apps/frontend/test/components/AccountsPage.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { render, screen } from '../utils';
import AccountsPage, { facilitationTeam, teamMembers } from '@/app/components/AccountsPage';

describe('AccountsPage', () => {
it('renders the headings', () => {
render(<AccountsPage />);
expect(screen.getByText('Accounts')).toBeInTheDocument();
expect(screen.getByText('Core BRANCH Facilitation Team')).toBeInTheDocument();
expect(screen.getByText('BRANCH Team Members')).toBeInTheDocument();
});

it('renders the correct staff cards in the facilitation section', () => {
render(<AccountsPage />);
const section = screen.getByText('Core BRANCH Facilitation Team').closest('div');
const cards = section?.querySelectorAll('[data-testid="staff-card"]');

if (facilitationTeam.length === 0) {
expect(cards?.length).toBe(0);
} else {
expect(cards?.length).toBeGreaterThan(0);
}
});

it('renders the correct staff cards in the team members section', () => {
render(<AccountsPage />);
const section = screen.getByText('BRANCH Team Members').closest('div');
const cards = section?.querySelectorAll('[data-testid="staff-card"]');

if (teamMembers.length === 0) {
expect(cards?.length).toBe(0);
} else {
expect(cards?.length).toBeGreaterThan(0);
}
});
});
26 changes: 10 additions & 16 deletions apps/frontend/test/components/StaffCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,31 @@ import StaffCard from '@/app/components/StaffCard';

describe ('StaffCard', () => {
it('renders the placeholder image when no image given', () => {
render(<StaffCard name="name" title="title" />);
expect(document.querySelector('.bg-accent-dark-green')).toBeInTheDocument();
render(<StaffCard name="name" />);
expect(document.querySelector('[data-testid="staff-placeholder"]')).toBeInTheDocument();
});

it('renders the placeholder image when image given image has error', () => {
render(<StaffCard image="/" name="name" title="title" />);
render(<StaffCard image="/" name="name" />);
const img = document.querySelector('img');
fireEvent.error(img!);
expect(document.querySelector('.bg-accent-dark-green')).toBeInTheDocument();
expect(document.querySelector('[data-testid="staff-placeholder"]')).toBeInTheDocument();
});

it('renders the given image', () => {
render(<StaffCard image="/test.jpg" name="name" title="title" />);
render(<StaffCard image="/test.jpg" name="name" />);
const img = document.querySelector('img');
expect(img).toHaveAttribute('src', expect.stringContaining('test.jpg'));
});

it('renders the name', () => {
render(<StaffCard name="name" title="title" />);
render(<StaffCard name="name" />);
expect(screen.getByText('name')).toBeInTheDocument();
});

it('renders the title', () => {
render(<StaffCard name="name" title="title" />);
expect(screen.getByText('title')).toBeInTheDocument();
});

it('long name and title are wrapped', () => {
render(<StaffCard name="superduper longname" title="superduper longtitle" />);
it('long name is wrapped', () => {
render(<StaffCard name="superduper longname" />);
const name = screen.getByText('superduper longname');
const title = screen.getByText('superduper longtitle');
expect(name).toHaveClass('break-words');
expect(title).toHaveClass('break-words'); });
expect(name).toHaveClass('break-words');
});
})
Loading