Next.js 16, React 19.2, Identities page, Invitations identities step, PNPM Catalogs (#381)

* Upgraded to Next.js 16
* Refactored code to comply with React 19.2 ESLint rules
* Refactored some useEffect usages with the new useEffectEvent
* Added Identities page and added second step to set up an identity after accepting an invitation
* Updated all dependencies
* Introduced PNPM catalogs for some frequently updated dependencies
* Bugs fixing and improvements
This commit is contained in:
Giancarlo Buomprisco
2025-10-22 11:47:47 +09:00
committed by GitHub
parent ea0c1dde80
commit 2c0d0bf7a1
98 changed files with 4812 additions and 4394 deletions

View File

@@ -26,14 +26,14 @@
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@supabase/supabase-js": "2.58.0",
"@types/react": "19.1.16",
"@supabase/supabase-js": "2.76.1",
"@types/react": "catalog:",
"date-fns": "^4.1.0",
"lucide-react": "^0.544.0",
"next": "15.5.5",
"react": "19.1.1",
"react-hook-form": "^7.63.0",
"react-i18next": "^16.0.0",
"lucide-react": "^0.546.0",
"next": "16.0.0",
"react": "19.2.0",
"react-hook-form": "^7.65.0",
"react-i18next": "^16.1.4",
"zod": "^3.25.74"
},
"typesVersions": {

View File

@@ -1,11 +1,27 @@
import { Suspense, forwardRef, lazy, memo, useMemo } from 'react';
import { Suspense, lazy } from 'react';
import { Enums } from '@kit/supabase/database';
import { LoadingOverlay } from '@kit/ui/loading-overlay';
type BillingProvider = Enums<'billing_provider'>;
const Fallback = <LoadingOverlay fullPage={false} />;
// Create lazy components at module level (not during render)
const StripeCheckoutLazy = lazy(async () => {
const { StripeCheckout } = await import('@kit/stripe/components');
return { default: StripeCheckout };
});
const LemonSqueezyCheckoutLazy = lazy(async () => {
const { LemonSqueezyEmbeddedCheckout } = await import(
'@kit/lemon-squeezy/components'
);
return { default: LemonSqueezyEmbeddedCheckout };
});
type CheckoutProps = {
onClose: (() => unknown) | undefined;
checkoutToken: string;
};
export function EmbeddedCheckout(
props: React.PropsWithChildren<{
@@ -14,100 +30,54 @@ export function EmbeddedCheckout(
onClose?: () => void;
}>,
) {
const CheckoutComponent = useMemo(
() => loadCheckoutComponent(props.provider),
[props.provider],
);
return (
<>
<CheckoutComponent
onClose={props.onClose}
checkoutToken={props.checkoutToken}
/>
<Suspense fallback={<LoadingOverlay fullPage={false} />}>
<CheckoutSelector
provider={props.provider}
onClose={props.onClose}
checkoutToken={props.checkoutToken}
/>
</Suspense>
<BlurryBackdrop />
</>
);
}
function loadCheckoutComponent(provider: BillingProvider) {
switch (provider) {
case 'stripe': {
return buildLazyComponent(() => {
return import('@kit/stripe/components').then(({ StripeCheckout }) => {
return {
default: StripeCheckout,
};
});
});
}
case 'lemon-squeezy': {
return buildLazyComponent(() => {
return import('@kit/lemon-squeezy/components').then(
({ LemonSqueezyEmbeddedCheckout }) => {
return {
default: LemonSqueezyEmbeddedCheckout,
};
},
);
});
}
case 'paddle': {
throw new Error('Paddle is not yet supported');
}
default:
throw new Error(`Unsupported provider: ${provider as string}`);
}
}
function buildLazyComponent<
Component extends React.ComponentType<{
onClose: (() => unknown) | undefined;
checkoutToken: string;
}>,
>(
load: () => Promise<{
default: Component;
}>,
fallback = Fallback,
function CheckoutSelector(
props: CheckoutProps & { provider: BillingProvider },
) {
let LoadedComponent: ReturnType<typeof lazy<Component>> | null = null;
const LazyComponent = forwardRef<
React.ElementRef<'div'>,
{
onClose: (() => unknown) | undefined;
checkoutToken: string;
}
>(function LazyDynamicComponent(props, ref) {
if (!LoadedComponent) {
LoadedComponent = lazy(load);
}
return (
<Suspense fallback={fallback}>
{/* @ts-expect-error: weird TS */}
<LoadedComponent
switch (props.provider) {
case 'stripe':
return (
<StripeCheckoutLazy
onClose={props.onClose}
checkoutToken={props.checkoutToken}
ref={ref}
/>
</Suspense>
);
});
);
return memo(LazyComponent);
case 'lemon-squeezy':
return (
<LemonSqueezyCheckoutLazy
onClose={props.onClose}
checkoutToken={props.checkoutToken}
/>
);
case 'paddle':
throw new Error('Paddle is not yet supported');
default:
throw new Error(`Unsupported provider: ${props.provider as string}`);
}
}
function BlurryBackdrop() {
return (
<div
className={
'bg-background/30 fixed left-0 top-0 w-full backdrop-blur-sm' +
'bg-background/30 fixed top-0 left-0 w-full backdrop-blur-sm' +
' !m-0 h-full'
}
/>

View File

@@ -316,7 +316,7 @@ export function PlanPicker(
<div
className={
'flex flex-col gap-y-3 lg:flex-row lg:items-center lg:space-x-4 lg:space-y-0 lg:text-right'
'flex flex-col gap-y-3 lg:flex-row lg:items-center lg:space-y-0 lg:space-x-4 lg:text-right'
}
>
<div>
@@ -415,6 +415,7 @@ function PlanDetails({
const isRecurring = selectedPlan.paymentType === 'recurring';
// trick to force animation on re-render
// eslint-disable-next-line react-hooks/purity
const key = Math.random();
return (

View File

@@ -422,7 +422,7 @@ function PlanIntervalSwitcher(
const selected = plan === props.interval;
const className = cn(
'animate-in fade-in !outline-hidden rounded-full transition-all focus:!ring-0',
'animate-in fade-in rounded-full !outline-hidden transition-all focus:!ring-0',
{
'border-r-transparent': index === 0,
['hover:text-primary text-muted-foreground']: !selected,

View File

@@ -24,9 +24,9 @@
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@types/react": "19.1.16",
"next": "15.5.5",
"react": "19.1.1",
"@types/react": "catalog:",
"next": "16.0.0",
"react": "19.2.0",
"zod": "^3.25.74"
},
"typesVersions": {

View File

@@ -56,8 +56,6 @@ export class LemonSqueezyBillingStrategyService
const { data: response, error } = await createLemonSqueezyCheckout(params);
if (error ?? !response?.data.id) {
console.log(error);
logger.error(
{
...ctx,

View File

@@ -15,9 +15,9 @@
"./components": "./src/components/index.ts"
},
"dependencies": {
"@stripe/react-stripe-js": "^5.0.0",
"@stripe/stripe-js": "^8.0.0",
"stripe": "^19.0.0"
"@stripe/react-stripe-js": "^5.2.0",
"@stripe/stripe-js": "^8.1.0",
"stripe": "^19.1.0"
},
"devDependencies": {
"@kit/billing": "workspace:*",
@@ -27,10 +27,10 @@
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@types/react": "19.1.16",
"@types/react": "catalog:",
"date-fns": "^4.1.0",
"next": "15.5.5",
"react": "19.1.1",
"next": "16.0.0",
"react": "19.2.0",
"zod": "^3.25.74"
},
"typesVersions": {