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
417 changes: 412 additions & 5 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"preview": "vite preview",
"test": "vitest",
"test:ci": "vitest run --coverage --silent",
"test:coverage": "vitest run --coverage",
"test:coverage": "vitest --coverage",
"test:e2e": "cypress run",
"lint": "eslint"
},
Expand All @@ -33,6 +33,7 @@
"axios": "1.7.2",
"classnames": "2.5.1",
"ionicons": "7.4.0",
"lodash": "4.17.21",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-router": "5.3.4",
Expand All @@ -45,6 +46,7 @@
"@testing-library/jest-dom": "6.4.6",
"@testing-library/react": "16.0.0",
"@testing-library/user-event": "14.5.2",
"@types/lodash": "4.17.6",
"@types/react": "18.3.3",
"@types/react-dom": "18.3.0",
"@vitejs/plugin-legacy": "5.4.1",
Expand All @@ -54,6 +56,7 @@
"eslint": "8.57.0",
"eslint-plugin-react": "7.34.3",
"jsdom": "24.1.0",
"msw": "2.3.1",
"sass": "1.77.6",
"terser": "5.31.1",
"typescript": "5.5.2",
Expand Down
51 changes: 51 additions & 0 deletions src/__fixtures__/users.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { User } from 'common/models/user';

export const userFixture1: User = {
id: 1,
name: 'Leanne Graham',
username: 'Bret',
email: 'Sincere@april.biz',
address: {
street: 'Kulas Light',
suite: 'Apt. 556',
city: 'Gwenborough',
zipcode: '92998-3874',
geo: {
lat: '-37.3159',
lng: '81.1496',
},
},
phone: '1-770-736-8031 x56442',
website: 'hildegard.org',
company: {
name: 'Romaguera-Crona',
catchPhrase: 'Multi-layered client-server neural-net',
bs: 'harness real-time e-markets',
},
};

export const userFixture2: User = {
id: 2,
name: 'Ervin Howell',
username: 'Antonette',
email: 'Shanna@melissa.tv',
address: {
street: 'Victor Plains',
suite: 'Suite 879',
city: 'Wisokyburgh',
zipcode: '90566-7771',
geo: {
lat: '-43.9509',
lng: '-34.4618',
},
},
phone: '010-692-6593 x09125',
website: 'anastasia.net',
company: {
name: 'Deckow-Crist',
catchPhrase: 'Proactive didactic contingency',
bs: 'synergize scalable supply-chains',
},
};

export const usersFixture: User[] = [userFixture1, userFixture2];
14 changes: 14 additions & 0 deletions src/common/components/Block/__tests__/Block.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { render, screen } from 'test/test-utils';
import { describe, expect, it } from 'vitest';
import Block from '../Block';

describe('Block', () => {
it('should render successfully', async () => {
// ARRANGE
render(<Block />);
await screen.findByTestId('block');

// ASSERT
expect(screen.getByTestId('block')).toBeDefined();
});
});
36 changes: 36 additions & 0 deletions src/common/components/Card/CardRow.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
.row-card {
justify-content: center;
align-items: center;

.wrapper {
width: 100%;
}

// xs
@media (max-width: 575px) {
}
// sm
@media (min-width: 576px) {
.wrapper {
width: 80%;
}
}
// md
@media (min-width: 768px) {
.wrapper {
width: 60%;
}
}
// lg
@media (min-width: 992px) {
.wrapper {
width: 50%;
}
}
// xl
@media (min-width: 1200px) {
.wrapper {
width: 40%;
}
}
}
32 changes: 32 additions & 0 deletions src/common/components/Card/CardRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { IonRow } from '@ionic/react';
import { PropsWithChildren } from 'react';
import classNames from 'classnames';

import './CardRow.scss';
import { BaseComponentProps } from '../types';

/**
* Properties for the `CardRow` component.
* @see {@link BaseComponentProps}
* @see {@link PropsWithChildren}
*/
interface CardRowProps extends BaseComponentProps, PropsWithChildren {}

/**
* The `CardRow` component displays an `IonCard` (or other Card component)
* in a horizontal row, an `IonRow`.
*
* The content is horizontally and vertically centered within the row. The
* card width is responsive, adjusting based upon the viewport size.
* @param {CardRowProps} props - Component properties.
* @returns JSX
*/
const CardRow = ({ children, className, testid = 'row-card' }: CardRowProps): JSX.Element => {
return (
<IonRow className={classNames('row-card', className)} data-testid={testid}>
<div className="wrapper">{children}</div>
</IonRow>
);
};

export default CardRow;
27 changes: 27 additions & 0 deletions src/common/components/Card/EmptyCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { informationCircle } from 'ionicons/icons';

import MessageCard, { MessageCardProps } from './MessageCard';

/**
* Properties for the `EmptyCard` component.
* @see {@link MessageCard}
*/
interface EmptyCardProps extends MessageCardProps {}

/**
* The `EmptyCard` component renders a `MessageCard` displaying information
* when there is no data to display. For example, when a list of items has
* no items to display.
* @param {EmptyCardProps} props - Component properties.
* @returns JSX
*/
const EmptyCard = ({
icon = informationCircle,
testid = 'card-empty',
title = 'No data',
...cardProps
}: EmptyCardProps): JSX.Element => {
return <MessageCard icon={icon} testid={testid} title={title} {...cardProps} />;
};

export default EmptyCard;
27 changes: 27 additions & 0 deletions src/common/components/Card/ErrorCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { warning } from 'ionicons/icons';

import MessageCard, { MessageCardProps } from './MessageCard';

/**
* Properties for the `ErrorCard` component.
* @see {@link MessageCardProps}
*/
interface ErrorCardProps extends MessageCardProps {}

/**
* The `ErrorCard` component renders a `MessageCard` displaying information
* describing an exceptional event which has occurred.
* @param {ErrorCardProps} props - Component properties.
* @returns JSX
*/
const ErrorCard = ({
color = 'danger',
icon = warning,
testid = 'card-error',
title = 'Uh oh',
...cardProps
}: ErrorCardProps): JSX.Element => {
return <MessageCard color={color} icon={icon} testid={testid} title={title} {...cardProps} />;
};

export default ErrorCard;
12 changes: 12 additions & 0 deletions src/common/components/Card/MessageCard.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.card-message {
.title-block {
display: flex;
flex-direction: row;
align-items: center;

.icon {
font-size: 1.5rem;
margin-right: 0.5rem;
}
}
}
81 changes: 81 additions & 0 deletions src/common/components/Card/MessageCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {
IonCard,
IonCardContent,
IonCardHeader,
IonCardSubtitle,
IonCardTitle,
IonIcon,
} from '@ionic/react';
import { ReactNode } from 'react';
import classNames from 'classnames';

import './MessageCard.scss';
import { BaseComponentProps } from '../types';

/**
* Properties for the `MessageCard` component.
* @param {ReactNode} [content] - Optional. Card content.
* @param {ReactNode} [subtitle] - Optional. Card subtitle.
* @param {ReactNode} [title] - Optional. Card title.
* @see {@link BaseComponentProps}
* @see {@link HTMLIonCardElement}
* @see {@link HTMLIonIconElement}
*/
export interface MessageCardProps
extends BaseComponentProps,
Pick<HTMLIonCardElement, 'color'>,
Pick<HTMLIonIconElement, 'icon'> {
content?: ReactNode;
subtitle?: ReactNode;
title?: ReactNode;
}

/**
* The `MessageCard` component displays an `IonCard` which displays an
* informational message. The card consists of several optional elements
* to make it flexible. A title line with optional icon. A subtitle.
* And the card content.
* @param {MessageCardProps} props - Component properties.
* @returns JSX
*/
const MessageCard = ({
className,
color,
content,
icon,
subtitle,
testid = 'card-message',
title,
}: MessageCardProps): JSX.Element => {
const hasHeader = title || subtitle;

return (
<IonCard className={classNames('card-message', className)} data-testid={testid} color={color}>
{title && (
<IonCardHeader className="header">
<IonCardTitle className="title-block">
{icon && (
<IonIcon icon={icon} className="icon" data-testid={`${testid}-icon`}></IonIcon>
)}
<div className="title" data-testid={`${testid}-title`}>
{title}
</div>
</IonCardTitle>
{subtitle && (
<IonCardSubtitle className="subtitle" data-testid={`${testid}-subtitle`}>
{subtitle}
</IonCardSubtitle>
)}
</IonCardHeader>
)}

{content && (
<IonCardContent className="content" data-testid={`${testid}-content`}>
{content}
</IonCardContent>
)}
</IonCard>
);
};

export default MessageCard;
15 changes: 15 additions & 0 deletions src/common/components/Card/__tests__/CardRow.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { render, screen } from 'test/test-utils';
import { describe, expect, it } from 'vitest';

import CardRow from '../CardRow';

describe('CardRow', () => {
it('should render successfully', async () => {
// ASSERT
render(<CardRow />);
await screen.findByTestId('row-card');

// ASSERT
expect(screen.getByTestId('row-card')).toBeDefined();
});
});
16 changes: 16 additions & 0 deletions src/common/components/Card/__tests__/EmptyCard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { describe, expect, it } from 'vitest';

import { render, screen } from 'test/test-utils';

import EmptyCard from '../EmptyCard';

describe('EmptyCard', () => {
it('should render successfully', async () => {
// ARRANGE
render(<EmptyCard />);
await screen.findByTestId('card-empty');

// ASSERT
expect(screen.getByTestId('card-empty')).toBeDefined();
});
});
16 changes: 16 additions & 0 deletions src/common/components/Card/__tests__/ErrorCard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { describe, expect, it } from 'vitest';

import { render, screen } from 'test/test-utils';

import ErrorCard from '../ErrorCard';

describe('ErrorCard', () => {
it('should render successfully', async () => {
// ARRANGE
render(<ErrorCard />);
await screen.findByTestId('card-error');

// ASSERT
expect(screen.getByTestId('card-error')).toBeDefined();
});
});
15 changes: 15 additions & 0 deletions src/common/components/Card/__tests__/MessageCard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { render, screen } from 'test/test-utils';
import { describe, expect, it } from 'vitest';

import MessageCard from '../MessageCard';

describe('MessageCard', () => {
it('should render successfully', async () => {
// ARRANGE
render(<MessageCard />);
await screen.findByTestId('card-message');

// ASSERT
expect(screen.getByTestId('card-message')).toBeDefined();
});
});
Loading