diff --git a/README.md b/README.md index 931ddce3..86ec8625 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/OVERVIEW.md b/docs/OVERVIEW.md index e6ffe0b5..6b78c8ca 100644 --- a/docs/OVERVIEW.md +++ b/docs/OVERVIEW.md @@ -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) diff --git a/package-lock.json b/package-lock.json index 4920ec6e..c6fb7b48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,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", @@ -6353,11 +6355,15 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/dayjs": { - "version": "1.11.23", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", - "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", - "license": "MIT" + "node_modules/date-fns": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.4.0.tgz", + "integrity": "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } }, "node_modules/debounce-fn": { "version": "4.0.0", @@ -12439,7 +12445,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", @@ -12463,7 +12468,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", diff --git a/package.json b/package.json index 2c1d4db6..1d408a70 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/shared/package.json b/packages/shared/package.json index 9f48e9a3..dd98ae53 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -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", diff --git a/packages/shared/src/components/Text/Date.tsx b/packages/shared/src/components/Text/Date.tsx index e1830c24..4d4d5a4c 100644 --- a/packages/shared/src/components/Text/Date.tsx +++ b/packages/shared/src/components/Text/Date.tsx @@ -1,4 +1,4 @@ -import dayjs from 'dayjs'; +import { format } from 'date-fns'; import { DateFormat } from '@react-starter/shared/utils/constants'; @@ -6,12 +6,11 @@ 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; } /** @@ -19,8 +18,8 @@ export interface DateProps extends React.ComponentProps<'span'> { * property to apply a pattern to format the date. * @param {Date} props - Component properties, `DateProps`. */ -const Date = ({ date, format = DateFormat.DATE, ...props }: DateProps) => { - return {dayjs(date).format(format)}; +const Date = ({ date, formatStr = DateFormat.DATE, ...props }: DateProps) => { + return {format(date, formatStr)}; }; export { Date }; diff --git a/packages/shared/src/components/Text/DayOfTheWeek.test.tsx b/packages/shared/src/components/Text/DayOfTheWeek.test.tsx index 0ad281f3..bf4c7b99 100644 --- a/packages/shared/src/components/Text/DayOfTheWeek.test.tsx +++ b/packages/shared/src/components/Text/DayOfTheWeek.test.tsx @@ -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'; @@ -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(); await screen.findByTestId('day-of-the-week'); @@ -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(); await screen.findByTestId('day-of-the-week'); @@ -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(); await screen.findByTestId('day-of-the-week'); diff --git a/packages/shared/src/components/Text/DayOfTheWeek.tsx b/packages/shared/src/components/Text/DayOfTheWeek.tsx index 4fe3c9dd..bc705d92 100644 --- a/packages/shared/src/components/Text/DayOfTheWeek.tsx +++ b/packages/shared/src/components/Text/DayOfTheWeek.tsx @@ -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'; @@ -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; } @@ -46,7 +38,7 @@ const DayOfTheWeek = ({ date, relative = false, ...props }: DayOfTheWeekProps) = } } - return ; + return ; }; export { DayOfTheWeek }; diff --git a/packages/shared/src/utils/constants.ts b/packages/shared/src/utils/constants.ts index 513089f5..b25e3aea 100644 --- a/packages/shared/src/utils/constants.ts +++ b/packages/shared/src/utils/constants.ts @@ -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", } /** diff --git a/packages/web/package.json b/packages/web/package.json index a2a78dbd..8658ebd8 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -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", diff --git a/packages/web/src/common/api/useGetUserTokens.test.ts b/packages/web/src/common/api/useGetUserTokens.test.ts index 396c1097..be949d21 100644 --- a/packages/web/src/common/api/useGetUserTokens.test.ts +++ b/packages/web/src/common/api/useGetUserTokens.test.ts @@ -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'; @@ -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); }); @@ -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" diff --git a/packages/web/src/common/api/useGetUserTokens.ts b/packages/web/src/common/api/useGetUserTokens.ts index 83a9f679..fbd5aea0 100644 --- a/packages/web/src/common/api/useGetUserTokens.ts +++ b/packages/web/src/common/api/useGetUserTokens.ts @@ -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'; @@ -35,8 +35,8 @@ export const useGetUserTokens = (options?: Partial>) 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 { diff --git a/packages/web/src/common/components/Footer/Footer.test.tsx b/packages/web/src/common/components/Footer/Footer.test.tsx index 174af10a..49af3dae 100644 --- a/packages/web/src/common/components/Footer/Footer.test.tsx +++ b/packages/web/src/common/components/Footer/Footer.test.tsx @@ -6,19 +6,10 @@ import Footer from './Footer'; describe('Footer', () => { it('should render successfully', async () => { // ARRANGE - render(