Skip to content
Merged
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
30 changes: 13 additions & 17 deletions src/__tests__/contexts/AuthContext.test.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,29 @@
/**
* @fileoverview AuthContext tests
* Tests that the useAuth hook throws when used outside provider
* and that the context provides the expected interface
* Tests that the useAuth hook throws when used outside provider.
*/
import React from 'react';
import { render, screen } from '@testing-library/react';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import { useAuth } from '@/contexts/AuthContext';

// We'll create a simple test component
function TestAuthHookUsage() {
try {
const auth = useAuth();
return <div data-testid="auth-result">Has auth context: {auth.isAuthenticated ? 'yes' : 'no'}</div>;
} catch (error: unknown) {
const message = error instanceof Error ? error.message : 'Unknown error';
return <div data-testid="auth-error">{message}</div>;
}
// Component that calls useAuth without a provider — it will throw on render.
// Rendering this with React Testing Library propagates the render error, which
// we assert via `expect(() => render(...)).toThrow(...)`. JSX is kept outside
// of any try/catch so the test plays nicely with React's error handling model
// (per the react-hooks/error-boundaries lint rule).
function HookUserOutsideProvider() {
useAuth();
return null;
}

describe('AuthContext', () => {
describe('useAuth Hook', () => {
it('should throw error when used outside AuthProvider', () => {
// Suppress console.error for this test
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

render(<TestAuthHookUsage />);

expect(screen.getByTestId('auth-error')).toHaveTextContent('useAuth must be used within an AuthProvider');
expect(() => render(<HookUserOutsideProvider />)).toThrow(
'useAuth must be used within an AuthProvider'
);

consoleSpy.mockRestore();
});
Expand Down
25 changes: 10 additions & 15 deletions src/__tests__/contexts/CartContext.test.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
/**
* @fileoverview CartContext tests
* Tests that the useCart hook throws when used outside provider
* Tests that the useCart hook throws when used outside provider.
*/
import React from 'react';
import { render, screen } from '@testing-library/react';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import { useCart } from '@/contexts/CartContext';

// Test component that uses the cart context
function TestCartHookUsage() {
try {
const cart = useCart();
return <div data-testid="cart-result">Has cart context: {cart.cartEntries.length} items</div>;
} catch (error: unknown) {
const message = error instanceof Error ? error.message : 'Unknown error';
return <div data-testid="cart-error">{message}</div>;
}
// See AuthContext.test.tsx for why this pattern (component that throws,
// asserted via expect(...).toThrow) replaces the JSX-in-try/catch approach.
function HookUserOutsideProvider() {
useCart();
return null;
}

describe('CartContext', () => {
describe('useCart Hook', () => {
it('should throw error when used outside CartProvider', () => {
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

render(<TestCartHookUsage />);

expect(screen.getByTestId('cart-error')).toHaveTextContent('useCart must be used within a CartProvider');
expect(() => render(<HookUserOutsideProvider />)).toThrow(
'useCart must be used within a CartProvider'
);

consoleSpy.mockRestore();
});
Expand Down
167 changes: 101 additions & 66 deletions src/app/about/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,88 +3,123 @@
import Layout from '@/components/Layout';

interface TeamMember {
login: string;
name: string | null;
role: 'Mentor' | 'Team Member';
login: string;
name: string | null;
role: 'Mentor' | 'Team Member';
}

// Team members sorted alphabetically by login, with roles assigned
const teamMembers: TeamMember[] = [
{ login: 'Avnermond12344', name: 'Avner Mondshine', role: 'Team Member' },
{ login: 'danielyehoshua123', name: null, role: 'Team Member' },
{ login: 'HarelZeevi', name: 'Harel Zeevi', role: 'Team Member' },
{ login: 'idanC1111', name: null, role: 'Team Member' },
{ login: 'NoamBenShimon', name: 'Noam Ben Shimon', role: 'Team Member' },
{ login: 'roishm', name: 'Roi Shmerling', role: 'Team Member' },
{ login: 'Tomer-David', name: null, role: 'Team Member' },
{ login: 'vMaroon', name: 'Maroon Ayoub', role: 'Mentor' },
{ login: 'Avnermond12344', name: 'Avner Mondshine', role: 'Team Member' },
{ login: 'danielyehoshua123', name: null, role: 'Team Member' },
{ login: 'HarelZeevi', name: 'Harel Zeevi', role: 'Team Member' },
{ login: 'idanC1111', name: null, role: 'Team Member' },
{ login: 'NoamBenShimon', name: 'Noam Ben Shimon', role: 'Team Member' },
{ login: 'roishm', name: 'Roi Shmerling', role: 'Team Member' },
{ login: 'Tomer-David', name: null, role: 'Team Member' },
{ login: 'vMaroon', name: 'Maroon Ayoub', role: 'Mentor' },
];

export default function AboutPage() {
const mentors = teamMembers.filter(m => m.role === 'Mentor');
const members = teamMembers.filter(m => m.role === 'Team Member');

return (
<Layout>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
{/* Hero Section */}
<div className="text-center mb-12">
<h1 className="text-4xl font-bold text-zinc-900 dark:text-white mb-4">
About Us
<div className="max-w-5xl mx-auto px-5 sm:px-8 py-12">
{/* Hero */}
<header className="mb-14 max-w-2xl animate-rise-in">
<p className="eyebrow mb-3">About</p>
<h1 className="font-display text-[2.6rem] sm:text-[3rem] leading-[1.05] tracking-tight text-(--ink-1) mb-5">
About Motzkin Store
</h1>
<p className="text-lg text-zinc-600 dark:text-zinc-400 max-w-2xl mx-auto">
Motzkin Store is a school equipment management system built by a dedicated team
of developers. Our goal is to simplify the process of managing and distributing
educational equipment to schools.
<p className="text-[1.05rem] leading-relaxed text-ink-2">
Motzkin Store is a school-supply ordering site for families in
Kiryat Motzkin. Pick the school, pick the grade, and order the
equipment list in one go.
</p>
</div>

{/* Team Section */}
<div className="mb-8">
<h2 className="text-2xl font-semibold text-zinc-900 dark:text-white text-center mb-8">
Meet Our Team
</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{teamMembers.map((member) => (
<a
key={member.login}
href={`https://github.com/${member.login}`}
target="_blank"
rel="noopener noreferrer"
className="group bg-white dark:bg-zinc-800 rounded-xl p-6 shadow-sm border border-zinc-200 dark:border-zinc-700 hover:shadow-lg hover:border-zinc-300 dark:hover:border-zinc-600 transition-all duration-200 hover:-translate-y-1"
>
<div className="flex flex-col items-center text-center">
{/* Avatar */}
<img
src={`https://github.com/${member.login}.png`}
alt={`${member.name || member.login}'s avatar`}
className="w-24 h-24 rounded-full mb-4 ring-2 ring-zinc-200 dark:ring-zinc-700 group-hover:ring-zinc-400 dark:group-hover:ring-zinc-500 transition-all"
/>
</header>

{/* Name */}
<h3 className="text-lg font-semibold text-zinc-900 dark:text-white mb-1">
{member.name || member.login}
</h3>
{/* Team */}
<section>
<div className="flex items-end justify-between mb-7 flex-wrap gap-3">
<div>
<p className="eyebrow mb-2">The team</p>
<h2 className="font-display text-[2rem] tracking-tight text-(--ink-1) leading-tight">
Built by students of the Technion
</h2>
</div>
<p className="text-[13px] text-ink-2 max-w-xs">
Developed as part of the Project in Software Engineering course,
in cooperation with the Kiryat Motzkin Municipality.
</p>
</div>

{/* GitHub Username */}
<p className="text-sm text-zinc-500 dark:text-zinc-400 mb-3">
@{member.login}
</p>
{mentors.length > 0 && (
<div className="mb-10">
<h3 className="text-[11px] uppercase tracking-[0.18em] font-semibold text-(--ink-3) mb-4">
Mentor
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-5">
{mentors.map((m, i) => (
<MemberCard key={m.login} member={m} delay={i} mentor />
))}
</div>
</div>
)}

{/* Role Badge */}
<span
className={`inline-flex items-center px-3 py-1 rounded-full text-xs font-medium ${
member.role === 'Mentor'
? 'bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400'
: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400'
}`}
>
{member.role}
</span>
</div>
</a>
))}
<div>
<h3 className="text-[11px] uppercase tracking-[0.18em] font-semibold text-(--ink-3) mb-4">
Team members
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-5">
{members.map((m, i) => (
<MemberCard key={m.login} member={m} delay={i + mentors.length} />
))}
</div>
</div>
</div>
</section>
</div>
</Layout>
);
}

function MemberCard({
member,
delay,
mentor = false,
}: {
member: TeamMember;
delay: number;
mentor?: boolean;
}) {
return (
<a
href={`https://github.com/${member.login}`}
target="_blank"
rel="noopener noreferrer"
className="group surface-card p-5 hover:border-(--ink-3) hover:-translate-y-0.5 transition-all duration-200 animate-rise-in"
style={{ animationDelay: `${120 + delay * 50}ms` }}
>
<div className="flex items-start gap-4">
<img

Check warning on line 104 in src/app/about/page.tsx

View workflow job for this annotation

GitHub Actions / Lint, Build & Test

Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element
src={`https://github.com/${member.login}.png`}
alt=""
className={`w-14 h-14 rounded-full border ${
mentor ? 'border-(--clay-500)' : 'border-(--line-strong)'
} group-hover:border-(--brand-700) transition-colors`}
/>
<div className="min-w-0 flex-1">
<h4 className="font-display text-[1.05rem] text-(--ink-1) tracking-tight leading-tight truncate">
{member.name || member.login}
</h4>
<p className="text-[12px] text-(--ink-3) truncate mt-0.5">@{member.login}</p>
{mentor && (
<p className="mt-2 inline-block text-[10px] uppercase tracking-[0.16em] font-semibold text-(--clay-900) bg-(--clay-50) px-1.5 py-0.5 rounded-sm">
Mentor
</p>
)}
</div>
</div>
</a>
);
}
Loading
Loading