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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ The application production technology stack includes:
- React i18next - internationalization
- Zod - schema based validation
- Lodash - utility functions
- DayJS - date and time utility functions
- date-fns - date and time utility functions
- TanStack Table - advanced tables and datagrids
- Recharts - composable charting library for React

Expand Down
34 changes: 18 additions & 16 deletions docs/OVERVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,22 +217,24 @@ This is a **frontend-only application** with no server-side event system. All st

### Runtime Dependencies (Production)

| Package | Purpose | Version |
| -------------------------- | ----------------------------------- | ------- |
| `react` | UI component library | 19.2.8+ |
| `react-dom` | React DOM rendering | 19.2.8+ |
| `react-router-dom` | Declarative routing and navigation | 7.18.2+ |
| `@tanstack/react-query` | Server state management and caching | Latest |
| `axios` | HTTP client for API requests | Latest |
| `react-hook-form` | Performant form state management | Latest |
| `zod` | TypeScript-first schema validation | 4.4.3+ |
| `tailwindcss` | Utility-first CSS framework | Latest |
| `shadcn/ui` | Accessible component library | Latest |
| `class-variance-authority` | Type-safe component variants | 0.7.1+ |
| `lucide-react` | Icon component library | Latest |
| `react-i18next` | Internationalization framework | Latest |
| `@tanstack/react-table` | Headless table component | Latest |
| `recharts` | Composable charting library | Latest |
| Package | Purpose | Version |
| -------------------------- | --------------------------------------------------------------------- | ------- |
| `react` | UI component library | Latest |
| `react-dom` | React DOM rendering | Latest |
| `react-router-dom` | Declarative routing and navigation | Latest |
| `@tanstack/react-query` | Server state management and caching | Latest |
| `axios` | HTTP client for API requests | Latest |
| `react-hook-form` | Performant form state management | Latest |
| `zod` | TypeScript-first schema validation | Latest |
| `tailwindcss` | Utility-first CSS framework | Latest |
| `shadcn/ui` | Accessible component library | Latest |
| `class-variance-authority` | Type-safe component variants | latest |
| `lucide-react` | Icon component library | Latest |
| `react-i18next` | Internationalization framework | Latest |
| `@tanstack/react-table` | Headless table component | Latest |
| `recharts` | Composable charting library | Latest |
| `date-fns` | Date utility library | Latest |
| `lodash` | Modular utility functions for arrays, numbers, objects, strings, etc. | Latest |

### Development Dependencies (Build & Test)

Expand Down
18 changes: 11 additions & 7 deletions package-lock.json

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

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
},
"dependencies": {
"class-variance-authority": "0.7.1",
"cn": "0.2.6",
"date-fns": "4.4.0",
"lucide-react": "1.41.0",
"react": "19.2.8",
"react-dom": "19.2.8",
Expand Down
1 change: 0 additions & 1 deletion packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
"@fortawesome/react-fontawesome": "3.5.0",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"cn": "0.2.6",
"next-themes": "0.4.6",
"radix-ui": "1.6.7",
"react-syntax-highlighter": "16.1.1",
Expand Down
11 changes: 5 additions & 6 deletions packages/shared/src/components/Text/Date.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
import dayjs from 'dayjs';
import { format } from 'date-fns';

import { DateFormat } from '@react-starter/shared/utils/constants';

/**
* Properties for the `Date` component.
* @param {string|number} date - The date value expressed as an ISO 8601 date string or as a number of milliseconds.
* @param {DateFormat} [format] - Optional. The format of the Date. Default: `DATE`
* @see {@link BaseComponentProps}
* @see {@link https://en.wikipedia.org/wiki/ISO_8601 | ISO 8601}
*/
export interface DateProps extends React.ComponentProps<'span'> {
date: string | number;
format?: DateFormat;
date: string | number | Date;
formatStr?: string;
}

/**
* The `Date` React component formats and renders a date. Use the `format`
* property to apply a pattern to format the date.
* @param {Date} props - Component properties, `DateProps`.
*/
const Date = ({ date, format = DateFormat.DATE, ...props }: DateProps) => {
return <span {...props}>{dayjs(date).format(format)}</span>;
const Date = ({ date, formatStr = DateFormat.DATE, ...props }: DateProps) => {
return <span {...props}>{format(date, formatStr)}</span>;
};

export { Date };
8 changes: 4 additions & 4 deletions packages/shared/src/components/Text/DayOfTheWeek.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { render, screen } from '@testing-library/react';
import dayjs from 'dayjs';
import { addDays, subDays } from 'date-fns';

import { DayOfTheWeek } from './DayOfTheWeek';

Expand Down Expand Up @@ -34,7 +34,7 @@ describe('DayOfTheWeek', () => {

it('should render relative Tomorrow', async () => {
// ARRANGE
const tomorrow = dayjs().add(1, 'day');
const tomorrow = addDays(new Date(), 1);
render(<DayOfTheWeek date={tomorrow.toISOString()} relative data-testid="day-of-the-week" />);
await screen.findByTestId('day-of-the-week');

Expand All @@ -44,7 +44,7 @@ describe('DayOfTheWeek', () => {

it('should render relative Yesterday', async () => {
// ARRANGE
const yesterday = dayjs().subtract(1, 'day');
const yesterday = subDays(new Date(), 1);
render(<DayOfTheWeek date={yesterday.toISOString()} relative data-testid="day-of-the-week" />);
await screen.findByTestId('day-of-the-week');

Expand All @@ -54,7 +54,7 @@ describe('DayOfTheWeek', () => {

it('should render relative day of the week', async () => {
// ARRANGE
const dow = dayjs('09/01/2023');
const dow = new Date('09/01/2023');
render(<DayOfTheWeek date={dow.toISOString()} relative data-testid="day-of-the-week" />);
await screen.findByTestId('day-of-the-week');

Expand Down
18 changes: 5 additions & 13 deletions packages/shared/src/components/Text/DayOfTheWeek.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import dayjs from 'dayjs';
import isToday from 'dayjs/plugin/isToday';
import isTomorrow from 'dayjs/plugin/isTomorrow';
import isYesterday from 'dayjs/plugin/isYesterday';
import { isToday, isTomorrow, isYesterday } from 'date-fns';

import { DateFormat } from '@react-starter/shared/utils/constants';
import { Date, DateProps } from '@react-starter/shared/components/Text/Date';

dayjs.extend(isToday);
dayjs.extend(isTomorrow);
dayjs.extend(isYesterday);

const TODAY = 'Today';
const TOMORROW = 'Tomorrow';
const YESTERDAY = 'Yesterday';
Expand All @@ -32,12 +25,11 @@ interface DayOfTheWeekProps extends DateProps {
const DayOfTheWeek = ({ date, relative = false, ...props }: DayOfTheWeekProps) => {
if (relative) {
let relativeDayOfTheWeek: string | null = null;
const theDate = dayjs(date);
if (theDate.isYesterday()) {
if (isYesterday(date)) {
relativeDayOfTheWeek = YESTERDAY;
} else if (theDate.isToday()) {
} else if (isToday(date)) {
relativeDayOfTheWeek = TODAY;
} else if (theDate.isTomorrow()) {
} else if (isTomorrow(date)) {
relativeDayOfTheWeek = TOMORROW;
}

Expand All @@ -46,7 +38,7 @@ const DayOfTheWeek = ({ date, relative = false, ...props }: DayOfTheWeekProps) =
}
}

return <Date date={date} format={DateFormat.DAY_OF_WEEK} {...props} />;
return <Date date={date} formatStr={DateFormat.DAY_OF_WEEK} {...props} />;
};

export { DayOfTheWeek };
12 changes: 6 additions & 6 deletions packages/shared/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ export enum CurrencySign {
* The `DateFormat` enumerates patterns for formatting dates.
*/
export enum DateFormat {
DATE = 'MM/DD/YYYY',
DAY_OF_WEEK = 'dddd',
HOURS_AND_MINUTES = 'H[h] mm[m]',
TIME = 'h:mma',
TIMESTAMP_SHORT = 'h:mma ddd MMM D',
TIMESTAMP = 'dddd MMMM D [at] h:mma',
DATE = 'MM/dd/yyyy',
DAY_OF_WEEK = 'EEEE',
HOURS_AND_MINUTES = "H'h' mm'm'",
TIME = 'H:mmb',
TIMESTAMP_SHORT = 'h:mmb EEE MMM d',
TIMESTAMP = "EEEE MMMM d 'at' h:mmb",
}

/**
Expand Down
1 change: 0 additions & 1 deletion packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
"@tanstack/react-query": "5.102.8",
"@tanstack/react-query-devtools": "5.102.8",
"axios": "1.20.0",
"dayjs": "1.11.23",
"i18next": "26.4.2",
"i18next-browser-languagedetector": "8.2.1",
"lodash": "4.18.1",
Expand Down
6 changes: 3 additions & 3 deletions packages/web/src/common/api/useGetUserTokens.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import dayjs from 'dayjs';
import { addHours } from 'date-fns';

import { renderHook, waitFor } from '@/test/test-utils';
import WithQueryClientProvider from '@/test/wrappers/WithQueryClientProvider';
Expand All @@ -14,7 +14,7 @@ describe('useGetTokens', () => {
beforeEach(() => {
const token: UserTokens = {
...userTokensFixture,
expires_at: dayjs().add(1, 'hours').toISOString(),
expires_at: addHours(new Date(), 1).toISOString(),
};
getItemSpy.mockReturnValue(token);
});
Expand All @@ -35,7 +35,7 @@ describe('useGetTokens', () => {
// ARRANGE
const token: UserTokens = {
...userTokensFixture,
expires_at: dayjs('2024-01-01').toISOString(),
expires_at: new Date('2024-01-01').toISOString(),
};
getItemSpy.mockReturnValue(token);
// use a specific wrapper to avoid test side effects from "AuthProvider"
Expand Down
6 changes: 3 additions & 3 deletions packages/web/src/common/api/useGetUserTokens.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { UseQueryOptions, UseQueryResult, useQuery } from '@tanstack/react-query';
import dayjs from 'dayjs';
import { isBefore } from 'date-fns';

import { QueryKey, StorageKey } from '@/common/utils/constants';
import storage from '@/common/utils/storage';
Expand Down Expand Up @@ -35,8 +35,8 @@ export const useGetUserTokens = (options?: Partial<UseQueryOptions<UserTokens>>)

if (storedTokens) {
// tokens found
const now = dayjs();
if (now.isBefore(storedTokens.expires_at)) {
const now = new Date();
if (isBefore(now, new Date(storedTokens.expires_at))) {
// tokens not expired
return resolve(storedTokens);
} else {
Expand Down
15 changes: 3 additions & 12 deletions packages/web/src/common/components/Footer/Footer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,10 @@ import Footer from './Footer';
describe('Footer', () => {
it('should render successfully', async () => {
// ARRANGE
render(<Footer />);
await screen.findByTestId('footer');
render(<Footer data-testid="footer" />);
const footer = await screen.findByTestId('footer');

// ASSERT
expect(screen.getByTestId('footer')).toBeDefined();
});

it('should use test id', async () => {
// ARRANGE
render(<Footer testId="test" />);
await screen.findByTestId('test');

// ASSERT
expect(screen.getByTestId('test')).toBeDefined();
expect(footer).toBeDefined();
});
});
25 changes: 11 additions & 14 deletions packages/web/src/common/components/Footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import dayjs from 'dayjs';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';

import { cn } from '@react-starter/shared/utils/css';
import type { BaseComponentProps } from '@react-starter/shared/types/components';
import { Link } from 'react-router-dom';
import { cn } from 'cn';

/**
* The `Footer` React component renders the standard page footer content used
* throughout the application.
* @param {FooterProps} props - Component properties, `FooterProps`.
* @see {@link FooterProps}
* @param {React.ComponentProps<'footer'>} props - Component properties, including `className` and other
* standard footer attributes.
*/
const Footer = ({ className, testId = 'footer' }: BaseComponentProps) => {
const Footer = ({ className, ...props }: React.ComponentProps<'footer'>) => {
const { t } = useTranslation();
const year = dayjs().format('YYYY');
const year = new Date().getFullYear();

return (
<footer className={cn('px-4 pt-16 pb-8', className)} data-testid={testId}>
<div className="flex flex-wrap items-center justify-center text-xs">
<div className="mx-2">&copy; {year} LeanStacks</div>
<div className="mx-2">
<footer className={cn('px-4 pt-16 pb-8', className)} {...props}>
<div className="flex flex-wrap items-center justify-center gap-4 text-xs">
<div>&copy; {year} LeanStacks</div>
<div>
<Link
to="https://leanstacks.net/privacy.html"
title={t('privacyPolicy', { ns: 'common' })}
Expand All @@ -29,7 +26,7 @@ const Footer = ({ className, testId = 'footer' }: BaseComponentProps) => {
{t('privacy', { ns: 'common' })}
</Link>
</div>
<div className="mx-2">
<div>
<Link
to="https://leanstacks.net/terms.html"
title={t('termsAndConditions', { ns: 'common' })}
Expand Down
Loading