diff --git a/apps/e2e/package.json b/apps/e2e/package.json index 1eb4b7f6c..3bd24eb8a 100644 --- a/apps/e2e/package.json +++ b/apps/e2e/package.json @@ -13,7 +13,7 @@ "license": "ISC", "devDependencies": { "@playwright/test": "^1.48.2", - "@types/node": "^22.8.6", + "@types/node": "^22.8.7", "node-html-parser": "^6.1.13" } } diff --git a/apps/web/app/admin/layout.tsx b/apps/web/app/admin/layout.tsx index 67caeab9c..a0abfc634 100644 --- a/apps/web/app/admin/layout.tsx +++ b/apps/web/app/admin/layout.tsx @@ -7,6 +7,8 @@ export const metadata = { title: `Super Admin`, }; +export const dynamic = 'force-dynamic'; + export default function AdminLayout(props: React.PropsWithChildren) { return ( diff --git a/apps/web/components/analytics-provider.tsx b/apps/web/components/analytics-provider.tsx index 01355822b..69200fea5 100644 --- a/apps/web/components/analytics-provider.tsx +++ b/apps/web/components/analytics-provider.tsx @@ -11,6 +11,7 @@ import { ConsumerProvidedEventTypes, useAppEvents, } from '@kit/shared/events'; +import { isBrowser } from '@kit/shared/utils'; type AnalyticsMapping< T extends ConsumerProvidedEventTypes = NonNullable, @@ -65,10 +66,7 @@ const analyticsMapping: AnalyticsMapping = { }, }; -/** - * Provider for the analytics service - */ -export function AnalyticsProvider(props: React.PropsWithChildren) { +function AnalyticsProviderBrowser(props: React.PropsWithChildren) { // Subscribe to app events and map them to analytics actions useAnalyticsMapping(analyticsMapping); @@ -79,6 +77,17 @@ export function AnalyticsProvider(props: React.PropsWithChildren) { return props.children; } +/** + * Provider for the analytics service + */ +export function AnalyticsProvider(props: React.PropsWithChildren) { + if (!isBrowser()) { + return props.children; + } + + return {props.children}; +} + /** * Hook to report page views to the analytics service * @param reportAnalyticsFn diff --git a/apps/web/package.json b/apps/web/package.json index 638146558..4a34e92dd 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -79,7 +79,7 @@ "@kit/tsconfig": "workspace:*", "@next/bundle-analyzer": "15.0.2", "@types/mdx": "^2.0.13", - "@types/node": "^22.8.6", + "@types/node": "^22.8.7", "@types/react": "npm:types-react@19.0.0-rc.1", "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1", "autoprefixer": "^10.4.20", diff --git a/package.json b/package.json index ef0cd834e..74e28b13f 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "@types/react": "npm:types-react@19.0.0-rc.1", "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1", "i18next": "23.16.4", - "react-i18next": "15.1.0" + "react-i18next": "^15.1.0" } } } diff --git a/packages/analytics/package.json b/packages/analytics/package.json index 0150b0fc6..7d1de13dc 100644 --- a/packages/analytics/package.json +++ b/packages/analytics/package.json @@ -17,7 +17,7 @@ "@kit/prettier-config": "workspace:*", "@kit/tailwind-config": "workspace:*", "@kit/tsconfig": "workspace:*", - "@types/node": "^22.8.6" + "@types/node": "^22.8.7" }, "eslintConfig": { "root": true, diff --git a/packages/cms/core/package.json b/packages/cms/core/package.json index 38d9337e2..eedb5b3f7 100644 --- a/packages/cms/core/package.json +++ b/packages/cms/core/package.json @@ -19,7 +19,7 @@ "@kit/prettier-config": "workspace:*", "@kit/tsconfig": "workspace:*", "@kit/wordpress": "workspace:*", - "@types/node": "^22.8.6" + "@types/node": "^22.8.7" }, "eslintConfig": { "root": true, diff --git a/packages/cms/keystatic/package.json b/packages/cms/keystatic/package.json index 8be0a48b2..b0c5d0231 100644 --- a/packages/cms/keystatic/package.json +++ b/packages/cms/keystatic/package.json @@ -26,7 +26,7 @@ "@kit/prettier-config": "workspace:*", "@kit/tsconfig": "workspace:*", "@kit/ui": "workspace:*", - "@types/node": "^22.8.6", + "@types/node": "^22.8.7", "@types/react": "npm:types-react@19.0.0-rc.1", "react": "19.0.0-rc-45804af1-20241021", "zod": "^3.23.8" diff --git a/packages/cms/wordpress/package.json b/packages/cms/wordpress/package.json index 1c5787d7d..4da7f9846 100644 --- a/packages/cms/wordpress/package.json +++ b/packages/cms/wordpress/package.json @@ -20,7 +20,7 @@ "@kit/prettier-config": "workspace:*", "@kit/tsconfig": "workspace:*", "@kit/ui": "workspace:*", - "@types/node": "^22.8.6", + "@types/node": "^22.8.7", "@types/react": "npm:types-react@19.0.0-rc.1", "wp-types": "^4.66.1" }, diff --git a/packages/i18n/src/i18n.server.ts b/packages/i18n/src/i18n.server.ts index 106ef3b10..ec33e0344 100644 --- a/packages/i18n/src/i18n.server.ts +++ b/packages/i18n/src/i18n.server.ts @@ -15,26 +15,52 @@ export async function initializeServerI18n( const i18nInstance = createInstance(); const loadedNamespaces = new Set(); - await i18nInstance - .use( - resourcesToBackend(async (language, namespace, callback) => { - try { - const data = await resolver(language, namespace); - loadedNamespaces.add(namespace); + await new Promise((resolve) => { + void i18nInstance + .use( + resourcesToBackend(async (language, namespace, callback) => { + try { + const data = await resolver(language, namespace); + loadedNamespaces.add(namespace); - return callback(null, data); - } catch (error) { - console.log( - `Error loading i18n file: locales/${language}/${namespace}.json`, - error, - ); + return callback(null, data); + } catch (error) { + console.log( + `Error loading i18n file: locales/${language}/${namespace}.json`, + error, + ); - return callback(null, {}); - } - }), - ) - .use(initReactI18next) - .init(settings); + return callback(null, {}); + } + }), + ) + .use({ + type: '3rdParty', + init: async (i18next: typeof i18nInstance) => { + let iterations = 0; + const maxIterations = 100; + + // do not bind this to the i18next instance until it's initialized + while (i18next.isInitializing) { + iterations++; + + if (iterations > maxIterations) { + console.error( + `i18next is not initialized after ${maxIterations} iterations`, + ); + + break; + } + + await new Promise((resolve) => setTimeout(resolve, 1)); + } + + initReactI18next.init(i18next); + resolve(i18next); + }, + }) + .init(settings); + }); const namespaces = settings.ns as string[]; diff --git a/packages/mailers/core/package.json b/packages/mailers/core/package.json index 3d3e30f41..6a5f379d1 100644 --- a/packages/mailers/core/package.json +++ b/packages/mailers/core/package.json @@ -19,7 +19,7 @@ "@kit/resend": "workspace:*", "@kit/tailwind-config": "workspace:*", "@kit/tsconfig": "workspace:*", - "@types/node": "^22.8.6", + "@types/node": "^22.8.7", "zod": "^3.23.8" }, "eslintConfig": { diff --git a/packages/mailers/resend/package.json b/packages/mailers/resend/package.json index c207df791..a6be99793 100644 --- a/packages/mailers/resend/package.json +++ b/packages/mailers/resend/package.json @@ -18,7 +18,7 @@ "@kit/prettier-config": "workspace:*", "@kit/tailwind-config": "workspace:*", "@kit/tsconfig": "workspace:*", - "@types/node": "^22.8.6", + "@types/node": "^22.8.7", "zod": "^3.23.8" }, "eslintConfig": { diff --git a/packages/supabase/src/hooks/use-auth-change-listener.ts b/packages/supabase/src/hooks/use-auth-change-listener.ts index c86f40a5b..94091267e 100644 --- a/packages/supabase/src/hooks/use-auth-change-listener.ts +++ b/packages/supabase/src/hooks/use-auth-change-listener.ts @@ -14,6 +14,12 @@ import { useSupabase } from './use-supabase'; */ const PRIVATE_PATH_PREFIXES = ['/home', '/admin', '/join', '/update-password']; +/** + * @name AUTH_PATHS + * @description A list of auth paths + */ +const AUTH_PATHS = ['/auth']; + /** * @name useAuthChangeListener * @param privatePathPrefixes - A list of private path prefixes @@ -53,6 +59,12 @@ export function useAuthChangeListener({ // revalidate user session when user signs in or out if (event === 'SIGNED_OUT') { + // sometimes Supabase sends SIGNED_OUT event + // but in the auth path, so we ignore it + if (AUTH_PATHS.some((path) => pathName.startsWith(path))) { + return; + } + window.location.reload(); } }); diff --git a/packages/ui/src/makerkit/data-table.tsx b/packages/ui/src/makerkit/data-table.tsx index d65b72f56..84d9740c9 100644 --- a/packages/ui/src/makerkit/data-table.tsx +++ b/packages/ui/src/makerkit/data-table.tsx @@ -7,6 +7,7 @@ import { useRouter } from 'next/navigation'; import { flexRender, getCoreRowModel, + getSortedRowModel, useReactTable, } from '@tanstack/react-table'; import type { @@ -45,6 +46,8 @@ interface ReactTableProps { pageSize?: number; pageCount?: number; onPaginationChange?: (pagination: PaginationState) => void; + manualPagination?: boolean; + manualSorting?: boolean; tableProps?: React.ComponentProps & Record<`data-${string}`, string>; } @@ -57,6 +60,8 @@ export function DataTable({ pageCount, onPaginationChange, tableProps, + manualPagination = true, + manualSorting = false, }: ReactTableProps) { const [pagination, setPagination] = useState({ pageIndex: pageIndex ?? 0, @@ -74,7 +79,9 @@ export function DataTable({ data, columns, getCoreRowModel: getCoreRowModel(), - manualPagination: true, + getSortedRowModel: getSortedRowModel(), + manualPagination, + manualSorting, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, onColumnVisibilityChange: setColumnVisibility, diff --git a/packages/ui/src/makerkit/navigation-config.schema.ts b/packages/ui/src/makerkit/navigation-config.schema.ts index 5b6786760..37082d8a8 100644 --- a/packages/ui/src/makerkit/navigation-config.schema.ts +++ b/packages/ui/src/makerkit/navigation-config.schema.ts @@ -9,25 +9,32 @@ const Divider = z.object({ divider: z.literal(true), }); -const RouteChildren = z.array( - z.object({ - label: z.string(), - path: z.string(), - Icon: z.custom(), - end: RouteMatchingEnd, - children: z - .array( - z.object({ - label: z.string(), - path: z.string(), - Icon: z.custom(), - end: RouteMatchingEnd, - }), - ) - .default([]) - .optional(), - }), -); +const RouteSubChild = z.object({ + label: z.string(), + path: z.string(), + Icon: z.custom().optional(), + end: RouteMatchingEnd, + renderAction: z.custom().optional(), +}); + +const RouteChild = z.object({ + label: z.string(), + path: z.string(), + Icon: z.custom().optional(), + end: RouteMatchingEnd, + children: z.array(RouteSubChild).default([]).optional(), + collapsible: z.boolean().default(false).optional(), + collapsed: z.boolean().default(false).optional(), + renderAction: z.custom().optional(), +}); + +const RouteGroup = z.object({ + label: z.string(), + collapsible: z.boolean().optional(), + collapsed: z.boolean().optional(), + children: z.array(RouteChild), + renderAction: z.custom().optional(), +}); export const NavigationConfigSchema = z.object({ style: z.enum(['custom', 'sidebar', 'header']).default('sidebar'), @@ -36,16 +43,5 @@ export const NavigationConfigSchema = z.object({ .default('false') .optional() .transform((value) => value === `true`), - routes: z.array( - z.union([ - z.object({ - label: z.string(), - collapsible: z.boolean().optional(), - collapsed: z.boolean().optional(), - children: RouteChildren, - renderAction: z.custom().optional(), - }), - Divider, - ]), - ), + routes: z.array(z.union([RouteGroup, Divider])), }); diff --git a/packages/ui/src/makerkit/sidebar.tsx b/packages/ui/src/makerkit/sidebar.tsx index bf082f77a..393bc98b6 100644 --- a/packages/ui/src/makerkit/sidebar.tsx +++ b/packages/ui/src/makerkit/sidebar.tsx @@ -329,16 +329,24 @@ export function SidebarNavigation({ collapsed={item.collapsed} > {item.children.map((child) => { - return ( - - - - ); + if ('collapsible' in child && child.collapsible) { + throw new Error( + 'Collapsible groups are not supported in the old Sidebar. Please migrate to the new Sidebar.', + ); + } + + if ('path' in child) { + return ( + + + + ); + } })} ); diff --git a/packages/ui/src/shadcn/sidebar.tsx b/packages/ui/src/shadcn/sidebar.tsx index 5b8ddde81..6031f669d 100644 --- a/packages/ui/src/shadcn/sidebar.tsx +++ b/packages/ui/src/shadcn/sidebar.tsx @@ -910,18 +910,96 @@ export function SidebarNavigation({ - - - - {item.children.map((child) => { - const isActive = isRouteActive( - child.path, - currentPath, - child.end, - ); + + + + {item.children.map((child, childIndex) => { + if (child.renderAction) { + return ( + + {child.renderAction} + + ); + } - return ( - + const Container = (props: React.PropsWithChildren) => { + if ('collapsible' in child && child.collapsible) { + return ( + + {props.children} + + ); + } + + return props.children; + }; + + const ContentContainer = ( + props: React.PropsWithChildren, + ) => { + if ('collapsible' in child && child.collapsible) { + return ( + + {props.children} + + ); + } + + return props.children; + }; + + const TriggerItem = () => { + if ('collapsible' in child && child.collapsible) { + return ( + + +
svg]:flex-1 [&>svg]:shrink-0': + minimized, + })} + > + {child.Icon} + + + + +
+
+
+ ); + } + + const path = 'path' in child ? child.path : ''; + const end = 'end' in child ? child.end : false; + + const isActive = isRouteActive( + path, + currentPath, + end, + ); + + return ( svg]:flex-1': minimized, })} - href={child.path} + href={path} > {child.Icon} - + ); + }; - - {(children) => ( - - {children.map((child) => { - const isActive = isRouteActive( - child.path, - currentPath, - child.end, - ); + return ( + + + - return ( - - - svg]:flex-1': - minimized, - })} - href={child.path} - > - {child.Icon} - - + + {(children) => ( + + {children.map((child) => { + if (child.renderAction) { + return ( + - - - - - - ); - })} - - )} - -
+ {child.renderAction} + + ); + } + + const isActive = isRouteActive( + child.path, + currentPath, + child.end, + ); + + return ( + + + svg]:flex-1': + minimized, + }, + )} + href={child.path} + > + {child.Icon} + + + + + + + ); + })} + + )} + +
+ + ); })} -
-
-
+ + + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 63f66fc52..158a94a06 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,7 +11,7 @@ overrides: '@types/react': npm:types-react@19.0.0-rc.1 '@types/react-dom': npm:types-react-dom@19.0.0-rc.1 i18next: 23.16.4 - react-i18next: 15.1.0 + react-i18next: ^15.1.0 importers: @@ -42,7 +42,7 @@ importers: specifier: ^1.48.2 version: 1.48.2 '@types/node': - specifier: ^22.8.6 + specifier: ^22.8.7 version: 22.9.0 node-html-parser: specifier: ^6.1.13 @@ -156,7 +156,7 @@ importers: specifier: ^7.53.1 version: 7.53.1(react@19.0.0-rc-45804af1-20241021) react-i18next: - specifier: 15.1.0 + specifier: ^15.1.0 version: 15.1.0(i18next@23.16.4)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) recharts: specifier: 2.13.3 @@ -190,7 +190,7 @@ importers: specifier: ^2.0.13 version: 2.0.13 '@types/node': - specifier: ^22.8.6 + specifier: ^22.8.7 version: 22.9.0 '@types/react': specifier: npm:types-react@19.0.0-rc.1 @@ -247,7 +247,7 @@ importers: specifier: workspace:* version: link:../../tooling/typescript '@types/node': - specifier: ^22.8.6 + specifier: ^22.8.7 version: 22.9.0 packages/billing/core: @@ -331,7 +331,7 @@ importers: specifier: ^7.53.1 version: 7.53.1(react@19.0.0-rc-45804af1-20241021) react-i18next: - specifier: 15.1.0 + specifier: ^15.1.0 version: 15.1.0(i18next@23.16.4)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) zod: specifier: ^3.23.8 @@ -453,7 +453,7 @@ importers: specifier: workspace:* version: link:../wordpress '@types/node': - specifier: ^22.8.6 + specifier: ^22.8.7 version: 22.9.0 packages/cms/keystatic: @@ -484,7 +484,7 @@ importers: specifier: workspace:* version: link:../../ui '@types/node': - specifier: ^22.8.6 + specifier: ^22.8.7 version: 22.9.0 '@types/react': specifier: npm:types-react@19.0.0-rc.1 @@ -526,7 +526,7 @@ importers: specifier: workspace:* version: link:../../ui '@types/node': - specifier: ^22.8.6 + specifier: ^22.8.7 version: 22.9.0 '@types/react': specifier: npm:types-react@19.0.0-rc.1 @@ -675,7 +675,7 @@ importers: specifier: ^7.53.1 version: 7.53.1(react@19.0.0-rc-45804af1-20241021) react-i18next: - specifier: 15.1.0 + specifier: ^15.1.0 version: 15.1.0(i18next@23.16.4)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) sonner: specifier: ^1.7.0 @@ -801,7 +801,7 @@ importers: specifier: ^7.53.1 version: 7.53.1(react@19.0.0-rc-45804af1-20241021) react-i18next: - specifier: 15.1.0 + specifier: ^15.1.0 version: 15.1.0(i18next@23.16.4)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) sonner: specifier: ^1.7.0 @@ -849,7 +849,7 @@ importers: specifier: 19.0.0-rc-45804af1-20241021 version: 19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021) react-i18next: - specifier: 15.1.0 + specifier: ^15.1.0 version: 15.1.0(i18next@23.16.4)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) packages/features/team-accounts: @@ -937,7 +937,7 @@ importers: specifier: ^7.53.1 version: 7.53.1(react@19.0.0-rc-45804af1-20241021) react-i18next: - specifier: 15.1.0 + specifier: ^15.1.0 version: 15.1.0(i18next@23.16.4)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) sonner: specifier: ^1.7.0 @@ -986,7 +986,7 @@ importers: specifier: 19.0.0-rc-45804af1-20241021 version: 19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021) react-i18next: - specifier: 15.1.0 + specifier: ^15.1.0 version: 15.1.0(i18next@23.16.4)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) packages/mailers/core: @@ -1010,7 +1010,7 @@ importers: specifier: workspace:* version: link:../../../tooling/typescript '@types/node': - specifier: ^22.8.6 + specifier: ^22.8.7 version: 22.9.0 zod: specifier: ^3.23.8 @@ -1062,7 +1062,7 @@ importers: specifier: workspace:* version: link:../../../tooling/typescript '@types/node': - specifier: ^22.8.6 + specifier: ^22.8.7 version: 22.9.0 zod: specifier: ^3.23.8 @@ -1429,7 +1429,7 @@ importers: specifier: ^7.53.1 version: 7.53.1(react@19.0.0-rc-45804af1-20241021) react-i18next: - specifier: 15.1.0 + specifier: ^15.1.0 version: 15.1.0(i18next@23.16.4)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) sonner: specifier: ^1.7.0 @@ -6680,8 +6680,8 @@ packages: react-native: optional: true - react-is@19.0.0-rc-4d577fd2-20241104: - resolution: {integrity: sha512-U52Z3ZQsfvw6cIbCUt+JAPn73TL+4hN4X5D9JvQ3M+M3nlibKXL1SwC90yD23TI2gsZghM+OFXW561pMpS+6yg==} + react-is@19.0.0-rc-33c7bd9a-20241104: + resolution: {integrity: sha512-MNHzi8NhHjNzoO4ADF2VMuEkaWX6qJNz+/J6CoHsKVhA7LBAO20oGe87FB2f/0gmtOldH9g7rW9nyaEo/NJjBg==} react-promise-suspense@0.3.4: resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==} @@ -12616,7 +12616,7 @@ snapshots: hoist-non-react-statics@3.3.2: dependencies: - react-is: 19.0.0-rc-4d577fd2-20241104 + react-is: 19.0.0-rc-33c7bd9a-20241104 html-escaper@2.0.2: {} @@ -13993,7 +13993,7 @@ snapshots: '@jest/types': 24.9.0 ansi-regex: 4.1.1 ansi-styles: 3.2.1 - react-is: 19.0.0-rc-4d577fd2-20241104 + react-is: 19.0.0-rc-33c7bd9a-20241104 prismjs@1.29.0: {} @@ -14009,7 +14009,7 @@ snapshots: dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 - react-is: 19.0.0-rc-4d577fd2-20241104 + react-is: 19.0.0-rc-33c7bd9a-20241104 prosemirror-commands@1.6.2: dependencies: @@ -14143,7 +14143,7 @@ snapshots: optionalDependencies: react-dom: 19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021) - react-is@19.0.0-rc-4d577fd2-20241104: {} + react-is@19.0.0-rc-33c7bd9a-20241104: {} react-promise-suspense@0.3.4: dependencies: @@ -14237,7 +14237,7 @@ snapshots: lodash: 4.17.21 react: 19.0.0-rc-45804af1-20241021 react-dom: 19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021) - react-is: 19.0.0-rc-4d577fd2-20241104 + react-is: 19.0.0-rc-33c7bd9a-20241104 react-smooth: 4.0.1(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021) recharts-scale: 0.4.5 tiny-invariant: 1.3.3