Skip to content

Commit

Permalink
implement stage 1 design of otp form (#58) (#61) (#88)
Browse files Browse the repository at this point in the history
working on orders page

rebase from dev and fix eslint errors

implement nextSibling functiion

implement previousSibling while you delete

rebase from develop

resolve deployment errors

implement 2fa functionality

working on verify otp codes and redirect

rebasing from develop

resolve vendor token must be string

implement 2fa verfication

Co-authored-by: Rurangwa Leo <88591087+wayneleon1@users.noreply.github.com>
  • Loading branch information
niyobern and wayneleon1 authored Jul 19, 2024
1 parent eaec0e8 commit e8dad2c
Show file tree
Hide file tree
Showing 22 changed files with 1,279 additions and 2 deletions.
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@types/swiper": "^6.0.0",
"axios": "^1.7.2",
"axios-mock-adapter": "^1.22.0",
"date-fns": "^3.6.0",
"chart.js": "^4.4.3",
"dotenv": "^16.4.5",
"formik": "^2.4.6",
Expand Down
9 changes: 9 additions & 0 deletions public/iphone.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions public/product.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
173 changes: 173 additions & 0 deletions src/__test__/Orders/Orders.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import { Provider } from 'react-redux';
import { describe, expect, beforeEach } from 'vitest';
import configureStore from 'redux-mock-store';
import { Orders } from '@/components/Orders/Orders';
import { Order } from '@/interfaces/order';

const mockOrders: Order[] = [
{
id: 1,
trackingNumber: '123',
updatedAt: '2023-07-17T00:00:00Z',
status: 'Pending',
totalAmount: 100,
deliveryInfo:
'{"address": "123 Main St", "city": "Anytown", "country": "USA"}',
paymentInfo: null,
createdAt: '',
paid: false,
orderDetails: [],
},
{
id: 2,
trackingNumber: '456',
updatedAt: '2023-07-16T00:00:00Z',
status: 'Completed',
totalAmount: 200,
deliveryInfo:
'{"address": "456 Elm St", "city": "Othertown", "country": "USA"}',
paymentInfo: null,
createdAt: '',
paid: false,
orderDetails: [],
},
];

const mockStore = configureStore([]);

describe('Orders Component', () => {
let store: ReturnType<typeof mockStore>;

beforeEach(() => {
store = mockStore({
orders: {
orders: mockOrders,
},
});
});

test('renders without crashing', () => {
render(
<Provider store={store}>
<Orders />
</Provider>
);
expect(screen.getByTestId('trackingNumber')).toBeInTheDocument();
expect(screen.getByText(/ID/i)).toBeInTheDocument();
});

test('renders table headers correctly', () => {
render(
<Provider store={store}>
<Orders />
</Provider>
);
expect(screen.getByTestId('trackingNumber')).toBeInTheDocument();
expect(screen.getByTestId('updatedAt')).toBeInTheDocument();
expect(screen.getByTestId('status')).toBeInTheDocument();
expect(screen.getByTestId('totalAmount')).toBeInTheDocument();
expect(screen.getByTestId('action')).toBeInTheDocument();
});

test('renders pagination controls', () => {
render(
<Provider store={store}>
<Orders />
</Provider>
);
expect(screen.getByRole('navigation')).toBeInTheDocument();
});

test('sorts by column when header is clicked', () => {
render(
<Provider store={store}>
<Orders />
</Provider>
);
const orderColumn = screen.getByTestId('trackingNumber');
fireEvent.click(orderColumn);
const sortedOrders = [...mockOrders].sort((a, b) =>
a.trackingNumber.localeCompare(b.trackingNumber)
);
const rows = screen.getAllByRole('row');
rows.forEach((row, index) => {
if (index === 0) return; // Skip header row
expect(row).toHaveTextContent(sortedOrders[index - 1].trackingNumber);
});
});

test('filters orders by status when filter button is clicked', () => {
render(
<Provider store={store}>
<Orders />
</Provider>
);
const pendingFilter = screen.getByTestId('pending-filter');
fireEvent.click(pendingFilter);
const filteredOrders = mockOrders.filter(
(order) => order.status === 'Pending'
);
const rows = screen.getAllByRole('row');
expect(rows.length - 1).toBe(filteredOrders.length); // Subtract 1 for header row
rows.forEach((row, index) => {
if (index === 0) return; // Skip header row
expect(row).toHaveTextContent(filteredOrders[index - 1].trackingNumber);
});
});

test('searches orders when typing in search bar', () => {
render(
<Provider store={store}>
<Orders />
</Provider>
);
const searchInput = screen.getByPlaceholderText(/Search order/i);
fireEvent.change(searchInput, { target: { value: '123' } });
const filteredOrders = mockOrders.filter((order) =>
order.trackingNumber.includes('123')
);
const rows = screen.getAllByRole('row');
expect(rows.length - 1).toBe(filteredOrders.length); // Subtract 1 for header row
rows.forEach((row, index) => {
if (index === 0) return; // Skip header row
expect(row).toHaveTextContent(filteredOrders[index - 1].trackingNumber);
});
});

test('opens OrderDetailsModal when view button is clicked', () => {
render(
<Provider store={store}>
<Orders />
</Provider>
);
const viewButton = screen.getAllByRole('button', { name: /view/i })[0];
fireEvent.click(viewButton);
expect(screen.getByText(/Order #234/i)).toBeInTheDocument();
});

test('pagination controls work correctly', () => {
const manyOrders = Array.from({ length: 15 }, (_, index) => ({
id: index + 1,
trackingNumber: `TRACK${index + 1}`,
updatedAt: `2023-07-${index < 9 ? 0 : ''}${index + 1}T00:00:00Z`,
status: index % 2 === 0 ? 'Pending' : 'Completed',
totalAmount: (index + 1) * 100,
deliveryInfo:
'{"address": "Address", "city": "City", "country": "Country"}',
}));
const newStore = mockStore({
orders: {
orders: manyOrders,
},
});
render(
<Provider store={newStore}>
<Orders />
</Provider>
);
const rows = screen.getAllByRole('row');
expect(rows.length).toBe(11); //
});
});
3 changes: 3 additions & 0 deletions src/app/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
import buyerSlice from '@/app/Dashboard/buyerSlice';
import orderSlice from './Dashboard/orderSlice';

import ordersSliceReducer from '@/features/Orders/ordersSlice';

export const store = configureStore({
reducer: {
products: productsReducer,
Expand All @@ -28,6 +30,7 @@ export const store = configureStore({
passwordReset: passwordResetReducer,
buyer: buyerSlice,
order: orderSlice,
orders: ordersSliceReducer,
},
});

Expand Down
43 changes: 43 additions & 0 deletions src/components/Cart/Cart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import CartItem from './CartItem';
import HSButton from '../form/HSButton';

export default function Cart() {
return (
<div className="flex flex-col max-w-screen-lg mx-auto">
<div className="flex w-full justify-end items-center pr-32 md:pr-48 py-6">
<h1 className="text-2xl font-bold px-8">Products in cart</h1>
<div className="flex gap-48 text-sm font-light text-gray-500">
<span>3 products</span>
<button type="button" className="flex gap-2">
<span>View all</span>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.66699 12.9273L5.73972 14L11.7397 8L5.73972 2L4.66699 3.07273L9.59423 8L4.66699 12.9273Z"
fill="#9095A1"
/>
</svg>
<span className="hidden">Next</span>
</button>
</div>
</div>
<div className="w-fit">
<CartItem price={59.2} name="Canon Camera" />
<CartItem price={47} name="Galaxy Fold Z6" />
<CartItem price={98} name="Digital Television" />
<div className="flex justify-end gap-20 py-6 items-center">
<div className="flex gap-2 items-center">
<h2 className="text-2xl font-bold text-gray-900">Total:</h2>
<span className="text-xl font-medium text-primary">$20088</span>
</div>
<HSButton title="CHECKOUT" />
</div>
</div>
</div>
);
}
107 changes: 107 additions & 0 deletions src/components/Cart/CartItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { useState } from 'react';

interface CartProps {
price: number;
name: string;
}

function CartItem({ price, name }: CartProps) {
const [quantity, setQuantity] = useState(0);
const [size, setSize] = useState<'M' | 'S' | 'L'>('M');

const handleQuantityChange = (amount: number) => {
setQuantity((prevQuantity) => Math.max(0, prevQuantity + amount));
};

const handleSize = (newSize: 'M' | 'S' | 'L') => {
setSize(newSize);
};

return (
<div className="flex items-center border-b border-gray-400 pt-8 pb-4 h-max max-w-screen-md">
<img
src="/product.svg"
alt="Product"
className="w-48 h-32 object-cover rounded-md"
/>
<div className="flex flex-col flex-grow w-full justify-between px-8">
<span className="font-bold text-2xl">{name}</span>
<div className="flex justify-between">
<div className="flex items-center gap-4 py-2 flex-col">
<span className="text-gray-600 text-center w-full text-sm">
Color
</span>
<div className="flex gap-2">
<button
type="button"
className="w-6 h-6 rounded-full border-2 border-primary bg-white"
>
{' '}
</button>
<button
type="button"
className="w-6 h-6 rounded-full border-2 border-primary bg-gray-200"
>
{' '}
</button>
</div>
</div>
<div className="flex items-center gap-4 py-2 flex-col">
<span className="text-gray-600 text-center text-sm">Size</span>
<div className="flex">
<button
onClick={() => handleSize('L')}
type="button"
className={`px-3 text-sm py-1 border border-primary ${size === 'L' ? 'text-white bg-primary' : 'text-gray-600'}`}
>
L
</button>
<button
onClick={() => handleSize('M')}
type="button"
className={`px-3 text-sm py-1 border border-primary ${size === 'M' ? 'text-white bg-primary' : 'text-gray-600'}`}
>
M
</button>
<button
onClick={() => handleSize('S')}
type="button"
className={`px-3 text-sm py-1 border border-primary ${size === 'S' ? 'text-white bg-primary' : 'text-gray-600'}`}
>
S
</button>
</div>
</div>
<div className="flex items-center gap-4 py-2 flex-col text-gary-600 text-sm">
<span className="text-gray-600 text-center">Quantity</span>
<div className="flex items-center space-x-2">
<button
type="button"
onClick={() => handleQuantityChange(-1)}
className="px-3 py-1 bg-gray-100 rounded-md"
>
-
</button>
<span>{quantity}</span>
<button
type="button"
onClick={() => handleQuantityChange(1)}
className="px-3 py-1 bg-gray-100 rounded-md"
>
+
</button>
</div>
</div>
</div>
</div>
<div className="flex flex-col gap-6 py-4 items-end w-64 justify-between">
<button type="button" className="text-red-500 text-lg font-medium">
Remove
</button>
<span className="font-bold text-xl mt-4">${price * quantity}</span>
</div>
</div>
);
}

export default CartItem;
Loading

0 comments on commit e8dad2c

Please sign in to comment.