Perf improvements and billing updates

This commit is contained in:
giancarlo
2024-03-26 16:49:11 +08:00
parent 8626ea30c7
commit 4032aed827
39 changed files with 1261 additions and 1090 deletions

View File

@@ -1,3 +1,5 @@
import { GlobalLoader } from '@kit/ui/global-loader'; import { GlobalLoader } from '@kit/ui/global-loader';
export default GlobalLoader; import { withI18n } from '~/lib/i18n/with-i18n';
export default withI18n(GlobalLoader);

View File

@@ -26,6 +26,7 @@ export function TeamAccountCheckoutForm(params: { accountId: string }) {
if (checkoutToken) { if (checkoutToken) {
return ( return (
<EmbeddedCheckout <EmbeddedCheckout
load
checkoutToken={checkoutToken} checkoutToken={checkoutToken}
provider={billingConfig.provider} provider={billingConfig.provider}
/> />

View File

@@ -16,11 +16,18 @@ interface SessionPageProps {
}; };
} }
const LazyEmbeddedCheckout = dynamic(async () => { const LazyEmbeddedCheckout = dynamic(
const { EmbeddedCheckout } = await import('@kit/billing-gateway/components'); async () => {
const { EmbeddedCheckout } = await import(
'@kit/billing-gateway/components'
);
return EmbeddedCheckout; return EmbeddedCheckout;
}); },
{
ssr: false,
},
);
async function ReturnStripeSessionPage({ searchParams }: SessionPageProps) { async function ReturnStripeSessionPage({ searchParams }: SessionPageProps) {
const { customerEmail, checkoutToken } = await loadCheckoutSession( const { customerEmail, checkoutToken } = await loadCheckoutSession(

View File

@@ -1,3 +1,5 @@
import { GlobalLoader } from '@kit/ui/global-loader'; import { GlobalLoader } from '@kit/ui/global-loader';
export default GlobalLoader; import { withI18n } from '~/lib/i18n/with-i18n';
export default withI18n(GlobalLoader);

View File

@@ -1,3 +1,5 @@
import { GlobalLoader } from '@kit/ui/global-loader'; import { GlobalLoader } from '@kit/ui/global-loader';
export default GlobalLoader; import { withI18n } from '~/lib/i18n/with-i18n';
export default withI18n(GlobalLoader);

View File

@@ -44,7 +44,7 @@ export function SiteNavigation() {
<> <>
<div className={'hidden items-center lg:flex'}> <div className={'hidden items-center lg:flex'}>
<NavigationMenu> <NavigationMenu>
<NavigationMenuList className={'space-x-2.5'}> <NavigationMenuList className={'space-x-3'}>
<NavigationMenuItem> <NavigationMenuItem>
<Link className={className} href={links.Blog.path}> <Link className={className} href={links.Blog.path}>
{links.Blog.label} {links.Blog.label}

View File

@@ -9,7 +9,7 @@ import Post from '~/(marketing)/blog/_components/post';
import appConfig from '~/config/app.config'; import appConfig from '~/config/app.config';
import { withI18n } from '~/lib/i18n/with-i18n'; import { withI18n } from '~/lib/i18n/with-i18n';
export async function generateMetadata({ export function generateMetadata({
params, params,
}: { }: {
params: { slug: string }; params: { slug: string };
@@ -49,7 +49,7 @@ export async function generateMetadata({
}; };
} }
async function BlogPost({ params }: { params: { slug: string } }) { function BlogPost({ params }: { params: { slug: string } }) {
const post = allPosts.find((post) => post.slug === params.slug); const post = allPosts.find((post) => post.slug === params.slug);
if (!post) { if (!post) {

View File

@@ -1,11 +1,13 @@
import React from 'react'; import dynamic from 'next/dynamic';
import type { Post as PostType } from 'contentlayer/generated'; import type { Post as PostType } from 'contentlayer/generated';
import { Mdx } from '@kit/ui/mdx';
import { PostHeader } from './post-header'; import { PostHeader } from './post-header';
const Mdx = dynamic(() =>
import('@kit/ui/mdx').then((mod) => ({ default: mod.Mdx })),
);
export const Post: React.FC<{ export const Post: React.FC<{
post: PostType; post: PostType;
content: string; content: string;

View File

@@ -1,3 +1,5 @@
import { GlobalLoader } from '@kit/ui/global-loader'; import { GlobalLoader } from '@kit/ui/global-loader';
export default GlobalLoader; import { withI18n } from '~/lib/i18n/with-i18n';
export default withI18n(GlobalLoader);

5
apps/web/app/loading.tsx Normal file
View File

@@ -0,0 +1,5 @@
import { GlobalLoader } from '@kit/ui/global-loader';
import { withI18n } from '~/lib/i18n/with-i18n';
export default withI18n(GlobalLoader);

View File

@@ -2,6 +2,7 @@ import Link from 'next/link';
import { ArrowLeft } from 'lucide-react'; import { ArrowLeft } from 'lucide-react';
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
import { Button } from '@kit/ui/button'; import { Button } from '@kit/ui/button';
import { Heading } from '@kit/ui/heading'; import { Heading } from '@kit/ui/heading';
import { Trans } from '@kit/ui/trans'; import { Trans } from '@kit/ui/trans';
@@ -14,10 +15,16 @@ export const metadata = {
title: `Page not found - ${appConfig.name}`, title: `Page not found - ${appConfig.name}`,
}; };
const NotFoundPage = () => { const NotFoundPage = async () => {
const client = getSupabaseServerComponentClient();
const {
data: { session },
} = await client.auth.getSession();
return ( return (
<div className={'flex h-screen flex-1 flex-col'}> <div className={'flex h-screen flex-1 flex-col'}>
<SiteHeader session={null} /> <SiteHeader session={session} />
<div <div
className={ className={

View File

@@ -143,12 +143,14 @@ function getPatterns() {
const supabase = createMiddlewareClient(req, res); const supabase = createMiddlewareClient(req, res);
const { data, error } = await supabase.auth.getSession(); const { data, error } = await supabase.auth.getSession();
const origin = req.nextUrl.origin; const origin = req.nextUrl.origin;
const next = req.nextUrl.pathname;
// If user is not logged in, redirect to sign in page. // If user is not logged in, redirect to sign in page.
if (!data.session || error) { if (!data.session || error) {
return NextResponse.redirect( const signIn = pathsConfig.auth.signIn;
new URL(pathsConfig.auth.signIn, origin).href, const redirectPath = `${signIn}?next=${next}`;
);
return NextResponse.redirect(new URL(redirectPath, origin).href);
} }
const requiresMultiFactorAuthentication = const requiresMultiFactorAuthentication =

View File

@@ -3,35 +3,49 @@ import withBundleAnalyzer from '@next/bundle-analyzer';
const IS_PRODUCTION = process.env.NODE_ENV === 'production'; const IS_PRODUCTION = process.env.NODE_ENV === 'production';
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL; const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL;
const INTERNAL_PACKAGES = [
'@kit/ui',
'@kit/auth',
'@kit/accounts',
'@kit/team-accounts',
'@kit/shared',
'@kit/supabase',
'@kit/i18n',
'@kit/mailers',
'@kit/billing',
'@kit/billing-gateway',
'@kit/stripe',
];
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const config = { const config = {
reactStrictMode: true, reactStrictMode: true,
swcMinify: true,
/** Enables hot reloading for local packages without a build step */ /** Enables hot reloading for local packages without a build step */
transpilePackages: [ transpilePackages: INTERNAL_PACKAGES,
'@kit/ui',
'@kit/auth',
'@kit/accounts',
'@kit/team-accounts',
'@kit/shared',
'@kit/supabase',
'@kit/i18n',
'@kit/mailers',
'@kit/billing',
'@kit/billing-gateway',
'@kit/stripe',
],
pageExtensions: ['ts', 'tsx'], pageExtensions: ['ts', 'tsx'],
images: { images: {
remotePatterns: getRemotePatterns(), remotePatterns: getRemotePatterns(),
}, },
experimental: { experimental: {
mdxRs: true, mdxRs: true,
optimizePackageImports: [] optimizePackageImports: [
'recharts',
'lucide-react',
'@radix-ui/react-icons',
'@radix-ui/react-avatar',
'@radix-ui/react-select',
'date-fns',
...INTERNAL_PACKAGES,
],
}, },
modularizeImports: { modularizeImports: {
"lucide-react": { 'lucide-react': {
transform: "lucide-react/dist/esm/icons/{{ kebabCase member }}", transform: 'lucide-react/dist/esm/icons/{{ kebabCase member }}',
} },
lodash: {
transform: 'lodash/{{member}}',
},
}, },
/** We already do linting and typechecking as separate tasks in CI */ /** We already do linting and typechecking as separate tasks in CI */
eslint: { ignoreDuringBuilds: true }, eslint: { ignoreDuringBuilds: true },

View File

@@ -2,11 +2,12 @@
"name": "web", "name": "web",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"sideEffects": false,
"scripts": { "scripts": {
"analyze": "ANALYZE=true pnpm run build", "analyze": "ANALYZE=true pnpm run build",
"build": "pnpm with-env next build", "build": "pnpm with-env next build",
"clean": "git clean -xdf .next .turbo node_modules", "clean": "git clean -xdf .next .turbo node_modules",
"dev": "pnpm with-env next dev", "dev": "pnpm with-env next dev --turbo",
"lint": "next lint", "lint": "next lint",
"format": "prettier --check \"**/*.{js,cjs,mjs,ts,tsx,md,json}\"", "format": "prettier --check \"**/*.{js,cjs,mjs,ts,tsx,md,json}\"",
"start": "pnpm with-env next start", "start": "pnpm with-env next start",
@@ -44,7 +45,7 @@
"next-contentlayer": "0.3.4", "next-contentlayer": "0.3.4",
"react-i18next": "^14.1.0", "react-i18next": "^14.1.0",
"date-fns": "^3.2.0", "date-fns": "^3.2.0",
"next": "^14.2.0-canary.41", "next": "canary",
"next-sitemap": "^4.2.3", "next-sitemap": "^4.2.3",
"next-themes": "^0.2.1", "next-themes": "^0.2.1",
"react": "18.2.0", "react": "18.2.0",
@@ -60,7 +61,7 @@
"@kit/prettier-config": "^0.1.0", "@kit/prettier-config": "^0.1.0",
"@kit/tailwind-config": "^0.1.0", "@kit/tailwind-config": "^0.1.0",
"@kit/tsconfig": "^0.1.0", "@kit/tsconfig": "^0.1.0",
"@next/bundle-analyzer": "^14.2.0-canary.41", "@next/bundle-analyzer": "canary",
"@types/mdx": "^2.0.10", "@types/mdx": "^2.0.10",
"@types/node": "^20.11.5", "@types/node": "^20.11.5",
"@types/react": "^18.2.48", "@types/react": "^18.2.48",

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { ArrowRightIcon } from '@radix-ui/react-icons'; import { ArrowUpRight } from 'lucide-react';
import { Button } from '@kit/ui/button'; import { Button } from '@kit/ui/button';
import { import {
@@ -15,7 +15,7 @@ export function BillingPortalCard() {
return ( return (
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Manage your Subscription</CardTitle> <CardTitle>Manage your Billing Details</CardTitle>
<CardDescription> <CardDescription>
You can change your plan or cancel your subscription at any time. You can change your plan or cancel your subscription at any time.
@@ -23,15 +23,13 @@ export function BillingPortalCard() {
</CardHeader> </CardHeader>
<CardContent className={'space-y-2'}> <CardContent className={'space-y-2'}>
<Button className={'w-full'}> <div>
<span>Manage your Billing Settings</span> <Button>
<ArrowRightIcon className={'ml-2 h-4'} /> <span>Visit the billing portal</span>
</Button>
<p className={'text-sm'}> <ArrowUpRight className={'h-4'} />
Visit the billing portal to manage your subscription (update payment </Button>
method, cancel subscription, etc.) </div>
</p>
</CardContent> </CardContent>
</Card> </Card>
); );

View File

@@ -0,0 +1,63 @@
import { Database } from '@kit/supabase/database';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
export function CurrentPlanAlert(
props: React.PropsWithoutRef<{
status: Database['public']['Enums']['subscription_status'];
}>,
) {
let variant: 'success' | 'warning' | 'destructive';
let text: string;
let title: string;
switch (props.status) {
case 'active':
variant = 'success';
title = 'Active';
text = 'Your subscription is active';
break;
case 'trialing':
variant = 'success';
title = 'Trial';
text = 'You are currently on a trial';
break;
case 'past_due':
variant = 'destructive';
title = 'Past Due';
text = 'Your subscription payment is past due';
break;
case 'canceled':
variant = 'destructive';
title = 'Canceled';
text = 'You have canceled your subscription';
break;
case 'unpaid':
variant = 'destructive';
title = 'Unpaid';
text = 'Your subscription payment is unpaid';
break;
case 'incomplete':
variant = 'warning';
title = 'Incomplete';
text = 'We are processing your subscription payment';
break;
case 'incomplete_expired':
variant = 'destructive';
title = 'Incomplete Expired';
text = 'Your subscription payment has expired';
break;
case 'paused':
variant = 'warning';
title = 'Paused';
text = 'Your subscription is paused';
break;
}
return (
<Alert variant={variant}>
<AlertTitle>{title}</AlertTitle>
<AlertDescription>{text}</AlertDescription>
</Alert>
);
}

View File

@@ -0,0 +1,48 @@
import { Database } from '@kit/supabase/database';
import { Badge } from '@kit/ui/badge';
export function CurrentPlanBadge(
props: React.PropsWithoutRef<{
status: Database['public']['Enums']['subscription_status'];
}>,
) {
let variant: 'success' | 'warning' | 'destructive';
let text: string;
switch (props.status) {
case 'active':
variant = 'success';
text = 'Active';
break;
case 'trialing':
variant = 'success';
text = 'Trialing';
break;
case 'past_due':
variant = 'destructive';
text = 'Past due';
break;
case 'canceled':
variant = 'destructive';
text = 'Canceled';
break;
case 'unpaid':
variant = 'destructive';
text = 'Unpaid';
break;
case 'incomplete':
variant = 'warning';
text = 'Incomplete';
break;
case 'incomplete_expired':
variant = 'destructive';
text = 'Incomplete expired';
break;
case 'paused':
variant = 'warning';
text = 'Paused';
break;
}
return <Badge variant={variant}>{text}</Badge>;
}

View File

@@ -1,8 +1,15 @@
import { formatDate } from 'date-fns'; import { formatDate } from 'date-fns';
import { BadgeCheck, CheckCircle2 } from 'lucide-react';
import { z } from 'zod'; import { z } from 'zod';
import { BillingSchema, getProductPlanPairFromId } from '@kit/billing'; import { BillingSchema, getProductPlanPairFromId } from '@kit/billing';
import { Database } from '@kit/supabase/database'; import { Database } from '@kit/supabase/database';
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from '@kit/ui/accordion';
import { import {
Card, Card,
CardContent, CardContent,
@@ -11,6 +18,10 @@ import {
CardTitle, CardTitle,
} from '@kit/ui/card'; } from '@kit/ui/card';
import { If } from '@kit/ui/if'; import { If } from '@kit/ui/if';
import { Trans } from '@kit/ui/trans';
import { CurrentPlanAlert } from './current-plan-alert';
import { CurrentPlanBadge } from './current-plan-badge';
export function CurrentPlanCard({ export function CurrentPlanCard({
subscription, subscription,
@@ -27,41 +38,103 @@ export function CurrentPlanCard({
return ( return (
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>{product.name}</CardTitle> <CardTitle>Your Plan</CardTitle>
<CardDescription>{product.description}</CardDescription> <CardDescription>
You can change your plan or cancel your subscription at any time.
</CardDescription>
</CardHeader> </CardHeader>
<CardContent className={'space-y-4 text-sm'}> <CardContent className={'space-y-2.5 text-sm'}>
<div> <div className={'flex flex-col space-y-1'}>
<div className={'font-semibold'}> <div className={'flex items-center space-x-2 text-lg font-semibold'}>
Your Current Plan: <span>{plan.name}</span> <BadgeCheck
className={
's-6 fill-green-500 text-white dark:fill-white dark:text-black'
}
/>
<span>{product.name}</span>
<CurrentPlanBadge status={subscription.status} />
</div>
<div className={'text-muted-foreground'}>
Renews every {subscription.interval} at{' '}
<span>{product.currency}</span> <span>{plan.price}</span>
</div> </div>
</div> </div>
<div> <div>
<div className={'font-semibold'}> <CurrentPlanAlert status={subscription.status} />
Your Subscription is currently <span>{subscription.status}</span>
</div>
</div> </div>
<If condition={subscription.cancel_at_period_end}> <div>
<div> <Accordion type="single" collapsible>
<div className={'font-semibold'}> <AccordionItem value="features">
Cancellation Date:{' '} <AccordionTrigger>Plan details</AccordionTrigger>
<span>{formatDate(subscription.period_ends_at, 'P')}</span>
</div>
</div>
</If>
<If condition={!subscription.cancel_at_period_end}> <AccordionContent className="space-y-2.5">
<div> <If condition={subscription.status === 'trialing'}>
<div className={'font-semibold'}> <div className="flex flex-col">
Next Billing Date:{' '} <span className="font-medium">Your trial ends on</span>
<span>{formatDate(subscription.period_ends_at, 'P')}</span>{' '}
</div> <div className={'text-muted-foreground'}>
</div> <span>
</If> {subscription.trial_ends_at
? formatDate(subscription.trial_ends_at, 'P')
: ''}
</span>
</div>
</div>
</If>
<If condition={subscription.cancel_at_period_end}>
<div className="flex flex-col">
<span className="font-medium">
Your subscription will be cancelled at the end of the
period
</span>
<div className={'text-muted-foreground'}>
<span>
{formatDate(subscription.period_ends_at ?? '', 'P')}
</span>
</div>
</div>
</If>
<If condition={!subscription.cancel_at_period_end}>
<div className="flex flex-col space-y-1">
<span className="font-medium">Your next bill</span>
<div className={'text-muted-foreground'}>
Your next bill is for {product.currency} {plan.price} on{' '}
<span>
{formatDate(subscription.period_ends_at ?? '', 'P')}
</span>{' '}
</div>
</div>
</If>
<div className="flex flex-col space-y-1">
<span className="font-medium">Features</span>
<ul>
{product.features.map((item) => {
return (
<li key={item} className="flex items-center space-x-0.5">
<CheckCircle2 className="h-4 text-green-500" />
<span>
<Trans i18nKey={item} defaults={item} />
</span>
</li>
);
})}
</ul>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
</CardContent> </CardContent>
</Card> </Card>
); );

View File

@@ -15,10 +15,22 @@ export function EmbeddedCheckout(
provider: BillingProvider; provider: BillingProvider;
onClose?: () => void; onClose?: () => void;
}>, }>,
) {
return (
<LazyCheckout onClose={props.onClose} checkoutToken={props.checkoutToken} />
);
}
function LazyCheckout(
props: React.PropsWithChildren<{
checkoutToken: string;
provider: BillingProvider;
onClose?: () => void;
}>,
) { ) {
const CheckoutComponent = useMemo( const CheckoutComponent = useMemo(
() => memo(loadCheckoutComponent(props.provider)), () => memo(loadCheckoutComponent(props.provider)),
[], [props.provider],
); );
return ( return (
@@ -69,7 +81,7 @@ function buildLazyComponent<
) { ) {
let LoadedComponent: ReturnType<typeof lazy> | null = null; let LoadedComponent: ReturnType<typeof lazy> | null = null;
const LazyComponent = forwardRef((props, ref) => { const LazyComponent = forwardRef(function LazyDynamicComponent(props, ref) {
if (!LoadedComponent) { if (!LoadedComponent) {
LoadedComponent = lazy(load); LoadedComponent = lazy(load);
} }

View File

@@ -105,5 +105,5 @@ export function getProductPlanPairFromId(
} }
} }
return undefined; throw new Error(`Plan with ID ${planId} not found`);
} }

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useRouter } from 'next/navigation'; import { useRouter, useSearchParams } from 'next/navigation';
import type { Provider } from '@supabase/supabase-js'; import type { Provider } from '@supabase/supabase-js';
@@ -27,13 +27,17 @@ export function SignInMethodsContainer(props: {
oAuth: Provider[]; oAuth: Provider[];
}; };
}) { }) {
const router = useRouter();
const nextPath = useSearchParams().get('next') ?? props.paths.home;
const redirectUrl = new URL( const redirectUrl = new URL(
props.paths.callback, props.paths.callback,
isBrowser() ? window?.location.origin : '', isBrowser() ? window?.location.origin : '',
).toString(); ).toString();
const router = useRouter(); const onSignIn = () => {
const onSignIn = () => router.replace(props.paths.home); router.replace(nextPath);
};
return ( return (
<> <>

View File

@@ -23,7 +23,7 @@ export function initializeI18nClient(
.use( .use(
resourcesToBackend(async (language, namespace, callback) => { resourcesToBackend(async (language, namespace, callback) => {
const data = await i18nResolver(language, namespace); const data = await i18nResolver(language, namespace);
console.log(data);
return callback(null, data); return callback(null, data);
}), }),
) )

View File

@@ -30,7 +30,7 @@ export class StripeWebhookHandlerService
async verifyWebhookSignature(request: Request) { async verifyWebhookSignature(request: Request) {
const body = await request.clone().text(); const body = await request.clone().text();
const signatureKey = `stripe-signature`; const signatureKey = `stripe-signature`;
const signature = request.headers.get(signatureKey) as string; const signature = request.headers.get(signatureKey)!;
const { STRIPE_WEBHOOK_SECRET } = StripeServerEnvSchema.parse({ const { STRIPE_WEBHOOK_SECRET } = StripeServerEnvSchema.parse({
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY, STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,

View File

@@ -28,6 +28,7 @@ function AuthRedirectListener({
useEffect(() => { useEffect(() => {
// keep this running for the whole session unless the component was unmounted // keep this running for the whole session unless the component was unmounted
const listener = client.auth.onAuthStateChange((_, user) => { const listener = client.auth.onAuthStateChange((_, user) => {
console.log(_, user);
// log user out if user is falsy // log user out if user is falsy
// and if the current path is a private route // and if the current path is a private route
const shouldRedirectUser = !user && isPrivateRoute(pathName); const shouldRedirectUser = !user && isPrivateRoute(pathName);

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,7 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@radix-ui/react-accordion": "1.1.2",
"@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dialog": "^1.0.5",
@@ -44,7 +45,6 @@
"@kit/prettier-config": "0.1.0", "@kit/prettier-config": "0.1.0",
"@kit/tailwind-config": "0.1.0", "@kit/tailwind-config": "0.1.0",
"@kit/tsconfig": "0.1.0", "@kit/tsconfig": "0.1.0",
"@tanstack/react-table": "^8.11.3",
"@types/react": "^18.2.48", "@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18", "@types/react-dom": "^18.2.18",
"date-fns": "^3.2.0", "date-fns": "^3.2.0",
@@ -67,6 +67,7 @@
}, },
"prettier": "@kit/prettier-config", "prettier": "@kit/prettier-config",
"exports": { "exports": {
"./accordion": "./src/shadcn/accordion.tsx",
"./avatar": "./src/shadcn/avatar.tsx", "./avatar": "./src/shadcn/avatar.tsx",
"./button": "./src/shadcn/button.tsx", "./button": "./src/shadcn/button.tsx",
"./calendar": "./src/shadcn/calendar.tsx", "./calendar": "./src/shadcn/calendar.tsx",

View File

@@ -1,3 +1,5 @@
'use client';
import { useMemo } from 'react'; import { useMemo } from 'react';
import Link from 'next/link'; import Link from 'next/link';

View File

@@ -1,15 +0,0 @@
import { cn } from './utils';
const NavigationContainer: React.FC<{
className?: string;
}> = ({ children, className }) => {
return (
<div
className={cn(`dark:border-dark-800 border-b border-gray-50`, className)}
>
{children}
</div>
);
};
export default NavigationContainer;

View File

@@ -1,124 +0,0 @@
'use client';
import { useContext } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import Trans from '@/components/app/Trans';
import { cva } from 'class-variance-authority';
import isRouteActive from '@kit/generic/is-route-active';
import { NavigationMenuContext } from './navigation-menu-context';
import { cn } from './utils';
interface Link {
path: string;
label?: string;
}
const NavigationMenuItem: React.FC<{
link: Link;
depth?: number;
disabled?: boolean;
shallow?: boolean;
className?: string;
}> = ({ link, disabled, shallow, depth, ...props }) => {
const pathName = usePathname() ?? '';
const active = isRouteActive(link.path, pathName, depth ?? 3);
const menuProps = useContext(NavigationMenuContext);
const label = link.label;
const itemClassName = getNavigationMenuItemClassBuilder()({
active,
...menuProps,
});
const className = cn(itemClassName, props.className ?? ``);
return (
<li className={className}>
<Link
className={
'justify-center transition-transform duration-500 lg:justify-start'
}
aria-disabled={disabled}
href={disabled ? '' : link.path}
shallow={shallow ?? active}
>
<Trans i18nKey={label} defaults={label} />
</Link>
</li>
);
};
export default NavigationMenuItem;
function getNavigationMenuItemClassBuilder() {
return cva(
[
`flex items-center justify-center font-medium lg:justify-start rounded-md text-sm transition colors transform *:active:translate-y-[2px]`,
'*:p-1 *:lg:px-2.5 *:s-full *:flex *:items-center',
'aria-disabled:cursor-not-allowed aria-disabled:opacity-50',
],
{
compoundVariants: [
// not active - shared
{
active: false,
className: `font-medium hover:underline`,
},
// active - shared
{
active: true,
className: `font-semibold`,
},
// active - pill
{
active: true,
pill: true,
className: `bg-gray-50 text-gray-800 dark:bg-primary-300/10`,
},
// not active - pill
{
active: false,
pill: true,
className: `hover:bg-gray-50 active:bg-gray-100 text-gray-500 dark:text-gray-300 dark:hover:bg-background dark:active:bg-dark-900/90`,
},
// not active - bordered
{
active: false,
bordered: true,
className: `hover:bg-gray-50 active:bg-gray-100 dark:active:bg-dark-800 dark:hover:bg-dark/90 transition-colors rounded-lg border-transparent`,
},
// active - bordered
{
active: true,
bordered: true,
className: `top-[0.4rem] border-b-[0.25rem] rounded-none border-primary bg-transparent pb-[0.55rem] text-primary-700 dark:text-white`,
},
// active - secondary
{
active: true,
secondary: true,
className: `bg-transparent font-semibold`,
},
],
variants: {
active: {
true: ``,
},
pill: {
true: `[&>*]:py-2`,
},
bordered: {
true: `relative h-10`,
},
secondary: {
true: ``,
},
},
},
);
}

View File

@@ -1,5 +0,0 @@
import { createContext } from 'react';
import type { NavigationMenuProps } from './navigation-menu';
export const NavigationMenuContext = createContext<NavigationMenuProps>({});

View File

@@ -1,49 +0,0 @@
'use client';
import type { PropsWithChildren } from 'react';
import { cva } from 'class-variance-authority';
import { NavigationMenuContext } from './navigation-menu-context';
type Vertical = {
vertical?: boolean;
};
type Bordered = {
bordered?: boolean;
};
type Pill = {
pill?: boolean;
};
export type NavigationMenuProps = Vertical & (Bordered | Pill);
function NavigationMenu(props: PropsWithChildren<NavigationMenuProps>) {
const className = getNavigationMenuClassBuilder()(props);
return (
<ul className={className}>
<NavigationMenuContext.Provider value={props}>
{props.children}
</NavigationMenuContext.Provider>
</ul>
);
}
export default NavigationMenu;
function getNavigationMenuClassBuilder() {
return cva(['w-full dark:text-gray-300 items-center flex-wrap flex'], {
variants: {
vertical: {
true: `flex items-start justify-between space-x-2
lg:flex-col lg:justify-start lg:space-x-0 lg:space-y-1.5 [&>li>a]:w-full`,
},
bordered: {
true: `lg:space-x-3 border-b border-gray-100 dark:border-dark-800 pb-1.5`,
},
},
});
}

View File

@@ -21,7 +21,6 @@ function Spinner(
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor" fill="currentColor"
/> />
<path <path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill" fill="currentFill"

View File

@@ -0,0 +1,58 @@
'use client';
import * as React from 'react';
import * as AccordionPrimitive from '@radix-ui/react-accordion';
import { ChevronDownIcon } from '@radix-ui/react-icons';
import { cn } from '../utils';
const Accordion = AccordionPrimitive.Root;
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn('border-b', className)}
{...props}
/>
));
AccordionItem.displayName = 'AccordionItem';
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
'flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180',
className,
)}
{...props}
>
{children}
<ChevronDownIcon className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn('pb-4 pt-0', className)}>{children}</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };

View File

@@ -17,7 +17,7 @@ const badgeVariants = cva(
'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80', 'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
outline: 'text-foreground', outline: 'text-foreground',
success: success:
'border-transparent bg-green-50 text-green-500 dark:bg-transparent', 'border-transparent bg-green-50 text-green-500 dark:bg-green-500/20',
warning: warning:
'border-transparent bg-orange-50 text-orange-500 dark:bg-transparent', 'border-transparent bg-orange-50 text-orange-500 dark:bg-transparent',
info: 'border-transparent bg-blue-50 text-blue-500 dark:bg-transparent', info: 'border-transparent bg-blue-50 text-blue-500 dark:bg-transparent',

153
pnpm-lock.yaml generated
View File

@@ -88,7 +88,7 @@ importers:
version: 5.28.6(react@18.2.0) version: 5.28.6(react@18.2.0)
'@tanstack/react-query-next-experimental': '@tanstack/react-query-next-experimental':
specifier: ^5.28.6 specifier: ^5.28.6
version: 5.28.6(@tanstack/react-query@5.28.6)(next@14.2.0-canary.41)(react@18.2.0) version: 5.28.6(@tanstack/react-query@5.28.6)(next@14.2.0-canary.43)(react@18.2.0)
'@tanstack/react-table': '@tanstack/react-table':
specifier: ^8.11.3 specifier: ^8.11.3
version: 8.14.0(react-dom@18.2.0)(react@18.2.0) version: 8.14.0(react-dom@18.2.0)(react@18.2.0)
@@ -100,7 +100,7 @@ importers:
version: 3.6.0 version: 3.6.0
edge-csrf: edge-csrf:
specifier: ^1.0.9 specifier: ^1.0.9
version: 1.0.9(next@14.2.0-canary.41) version: 1.0.9(next@14.2.0-canary.43)
i18next: i18next:
specifier: ^23.10.1 specifier: ^23.10.1
version: 23.10.1 version: 23.10.1
@@ -108,17 +108,17 @@ importers:
specifier: ^1.2.0 specifier: ^1.2.0
version: 1.2.0 version: 1.2.0
next: next:
specifier: ^14.2.0-canary.41 specifier: canary
version: 14.2.0-canary.41(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0) version: 14.2.0-canary.43(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0)
next-contentlayer: next-contentlayer:
specifier: 0.3.4 specifier: 0.3.4
version: 0.3.4(contentlayer@0.3.4)(esbuild@0.19.11)(next@14.2.0-canary.41)(react-dom@18.2.0)(react@18.2.0) version: 0.3.4(contentlayer@0.3.4)(esbuild@0.19.11)(next@14.2.0-canary.43)(react-dom@18.2.0)(react@18.2.0)
next-sitemap: next-sitemap:
specifier: ^4.2.3 specifier: ^4.2.3
version: 4.2.3(next@14.2.0-canary.41) version: 4.2.3(next@14.2.0-canary.43)
next-themes: next-themes:
specifier: ^0.2.1 specifier: ^0.2.1
version: 0.2.1(next@14.2.0-canary.41)(react-dom@18.2.0)(react@18.2.0) version: 0.2.1(next@14.2.0-canary.43)(react-dom@18.2.0)(react@18.2.0)
react: react:
specifier: 18.2.0 specifier: 18.2.0
version: 18.2.0 version: 18.2.0
@@ -163,8 +163,8 @@ importers:
specifier: ^0.1.0 specifier: ^0.1.0
version: link:../../tooling/typescript version: link:../../tooling/typescript
'@next/bundle-analyzer': '@next/bundle-analyzer':
specifier: ^14.2.0-canary.41 specifier: canary
version: 14.2.0-canary.41 version: 14.2.0-canary.43
'@types/mdx': '@types/mdx':
specifier: ^2.0.10 specifier: ^2.0.10
version: 2.0.11 version: 2.0.11
@@ -333,6 +333,10 @@ importers:
version: link:../../../tooling/typescript version: link:../../../tooling/typescript
packages/features/admin: packages/features/admin:
dependencies:
'@kit/ui':
specifier: 0.1.0
version: link:../../ui
devDependencies: devDependencies:
'@kit/eslint-config': '@kit/eslint-config':
specifier: 0.2.0 specifier: 0.2.0
@@ -568,6 +572,9 @@ importers:
packages/ui: packages/ui:
dependencies: dependencies:
'@radix-ui/react-accordion':
specifier: 1.1.2
version: 1.1.2(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-alert-dialog': '@radix-ui/react-alert-dialog':
specifier: ^1.0.5 specifier: ^1.0.5
version: 1.0.5(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0) version: 1.0.5(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
@@ -619,6 +626,9 @@ importers:
'@radix-ui/react-tooltip': '@radix-ui/react-tooltip':
specifier: 1.0.7 specifier: 1.0.7
version: 1.0.7(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0) version: 1.0.7(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
'@tanstack/react-table':
specifier: ^8.10.7
version: 8.14.0(react-dom@18.2.0)(react@18.2.0)
class-variance-authority: class-variance-authority:
specifier: ^0.7.0 specifier: ^0.7.0
version: 0.7.0 version: 0.7.0
@@ -659,9 +669,6 @@ importers:
'@kit/tsconfig': '@kit/tsconfig':
specifier: 0.1.0 specifier: 0.1.0
version: link:../../tooling/typescript version: link:../../tooling/typescript
'@tanstack/react-table':
specifier: ^8.11.3
version: 8.14.0(react-dom@18.2.0)(react@18.2.0)
'@types/react': '@types/react':
specifier: ^18.2.48 specifier: ^18.2.48
version: 18.2.67 version: 18.2.67
@@ -1770,8 +1777,8 @@ packages:
- supports-color - supports-color
dev: false dev: false
/@next/bundle-analyzer@14.2.0-canary.41: /@next/bundle-analyzer@14.2.0-canary.43:
resolution: {integrity: sha512-1+PP3XaC3lz0oE49D0jxGsiEJZOmwlDgqV3yantl64vXRNX8Ae3Gsk1KcDhp7JHKKPvFx4AF13/LF48sbH0zkw==} resolution: {integrity: sha512-5GYBb99OLnmg5xZDrUUD0ILB/gJDN4MxJTG5fU5JQXIDc6Ew+jJgMzjdqptJduvlExorAWNNpQnjdnRlnZCQfg==}
dependencies: dependencies:
webpack-bundle-analyzer: 4.10.1 webpack-bundle-analyzer: 4.10.1
transitivePeerDependencies: transitivePeerDependencies:
@@ -1791,8 +1798,8 @@ packages:
resolution: {integrity: sha512-e7X7bbn3Z6DWnDi75UWn+REgAbLEqxI8Tq2pkFOFAMpWAWApz/YCUhtWMWn410h8Q2fYiYL7Yg5OlxMOCfFjJQ==} resolution: {integrity: sha512-e7X7bbn3Z6DWnDi75UWn+REgAbLEqxI8Tq2pkFOFAMpWAWApz/YCUhtWMWn410h8Q2fYiYL7Yg5OlxMOCfFjJQ==}
dev: false dev: false
/@next/env@14.2.0-canary.41: /@next/env@14.2.0-canary.43:
resolution: {integrity: sha512-6bd8zNDEferyJ9qkJrCB0pTgGFaJ9XttMI+uj5jrSeQ88kxsgPcoOFqBEMDtDOEzWqi+17B29alThei0Cmw0dA==} resolution: {integrity: sha512-jBjfC5J053shwv+g4kplFG+iH1TqWwMtLCIpDSplOmRDLdGeai6s3oKmWIxd+MbG5ETSZOl1vCN5A3nMgGkXfg==}
dev: false dev: false
/@next/eslint-plugin-next@14.1.4: /@next/eslint-plugin-next@14.1.4:
@@ -1833,8 +1840,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-darwin-arm64@14.2.0-canary.41: /@next/swc-darwin-arm64@14.2.0-canary.43:
resolution: {integrity: sha512-cplMeo/uQcfSHwZH2naB87IWzJouOSPXFQqRk1/nwuQNPxLFT2xO2v7zP4L4W98qukvLylFxWQhqpIQqZnohIA==} resolution: {integrity: sha512-M9Asj8J6GMVNdMRnDnR+hELiyjgaHSUYAZz4M7ro5Vd1X8wpg3jygd/RnkTv+hhHn3rqwV9jWyZ4xdyG3SORrg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
@@ -1860,8 +1867,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-darwin-x64@14.2.0-canary.41: /@next/swc-darwin-x64@14.2.0-canary.43:
resolution: {integrity: sha512-mnqPeUMFUg73DXEsNOxrKXy09ikcErLeFEzYVqn5XwaewVyqny9xwf8f02BYBcLEBrmk565xkocnTs7su45qqA==} resolution: {integrity: sha512-3BQ5FirbYZgXAFOCUynDr/Sl0fcFfEiLiDVdGMaJO7754fuWJShcj5tODiFC2B7MgLsVkri/84prBzsjkg76jA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
@@ -1887,8 +1894,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-arm64-gnu@14.2.0-canary.41: /@next/swc-linux-arm64-gnu@14.2.0-canary.43:
resolution: {integrity: sha512-ns0/YS9yqqS71h4xZNGn+IDZC7q3fLrF9mIaZ3aXhkHpXfeyjACOAEC6/0l0E49w4VkPv0XK64aRvsxsZfFr9g==} resolution: {integrity: sha512-VoCLYDTD2bkLsUkT0bACplrdpTw+IBKdFr5ih85atePrujCz6dMPUxeNMwH9aYL7r3PgzH6dR30r0Y5TFwUUSg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@@ -1914,8 +1921,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-arm64-musl@14.2.0-canary.41: /@next/swc-linux-arm64-musl@14.2.0-canary.43:
resolution: {integrity: sha512-22nsQMLgJH3ZbxbgQmiwGbFtW8KtDRQJU8OSyWcO8MFTgPdL3IcRd/lTsR5r9qliosoGgHzFSYDAq/7M5I/w0Q==} resolution: {integrity: sha512-8c35oylAS4Ggu155txTpOv7VG4BzG8BTluVbUZuaneZwsZi6VTbjVKMVnLYmmdcdRkkvRgPc83oUr2HGxwxFBw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@@ -1941,8 +1948,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-x64-gnu@14.2.0-canary.41: /@next/swc-linux-x64-gnu@14.2.0-canary.43:
resolution: {integrity: sha512-ZkcPQk+SV+i06i0k/UVmXKk+k70yKIwtTPrWl7JpeJ/Wc7sWoRqNCdpFqP227oOsy6dM0NKqJTEOATBYAefQLg==} resolution: {integrity: sha512-PHy7clJ+ChZzNJ3c9A2IrWJN4aNa+FZ+v39XNdcjdkdhPvwu1QSvtirWSbxqKpAqgA/3sMhAGCvwOx6yeBs4Ug==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@@ -1968,8 +1975,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-x64-musl@14.2.0-canary.41: /@next/swc-linux-x64-musl@14.2.0-canary.43:
resolution: {integrity: sha512-1Uofz0Bkec63oN7Xj9lHGevCqxZJrr6pruPx5JUb613CYqUVCVj6JA8H2JutYYwgcbwstjdA+9He37HlW1FGTw==} resolution: {integrity: sha512-pvma+GKwkDEzhQRrwl9P4oGu9A9NGJH/Za+SG/XwWph2i78+4OMDCKrmKEJ1T5BE6Bgo+Emfhdy8TmfqHPQQCg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@@ -1995,8 +2002,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-arm64-msvc@14.2.0-canary.41: /@next/swc-win32-arm64-msvc@14.2.0-canary.43:
resolution: {integrity: sha512-gMzwgq1ZnKwxYx2I4qhuRtaGZ/6WZD8XV0cObnaWXiu8O7NCK1kEKa6+IWy+Manax5qwamHKzWND5uKbeddToQ==} resolution: {integrity: sha512-b1npBheIu7/BgMZTCFkuNYv0Q/N9u6+7MYY5xjZDVIutW8ut2V93JZqeC2SYWFm03I+LNdYjplRhn3TVerz9Xg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
@@ -2022,8 +2029,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-ia32-msvc@14.2.0-canary.41: /@next/swc-win32-ia32-msvc@14.2.0-canary.43:
resolution: {integrity: sha512-U72bjcHUUoRToFTAouUqK9y/PaiKVo4rDX5/RwzIZThPghfHf5ALG6DYiw64sKWSRXSfFOv9DzhofjK1+CIgSA==} resolution: {integrity: sha512-1bZDCGyQzvdRNxVUUhsjBZOzBEEoQlh1r91ifjUz9nhcFYOlmP6IplPMjaLmG+GJMUiI36j5svdPYO3LP08b8g==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
@@ -2049,8 +2056,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-x64-msvc@14.2.0-canary.41: /@next/swc-win32-x64-msvc@14.2.0-canary.43:
resolution: {integrity: sha512-cBgEDwdxfyv9bBoo5dKBtfxd7S7Dv3dTRKYx7agprJ3BdGc8gXs2zdi08lR/fBU0kjpsC4gw/tg5p+IDkuZUaw==} resolution: {integrity: sha512-pU9gjLmp4yjYzBqCGa5bQ0iyJ5D73IRITEUFKrjZPi0XHUbFLrhcaaCsnVgMO4xfOQJgS7ODuQB7N0iPk7/EMw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
@@ -2386,6 +2393,35 @@ packages:
'@babel/runtime': 7.24.1 '@babel/runtime': 7.24.1
dev: false dev: false
/@radix-ui/react-accordion@1.1.2(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-fDG7jcoNKVjSK6yfmuAs0EnPDro0WMXIhMtXdTBWqEioVW206ku+4Lw07e+13lUkFkpoEQ2PdeMIAGpdqEAmDg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0
react-dom: ^16.8 || ^17.0 || ^18.0
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
dependencies:
'@babel/runtime': 7.24.1
'@radix-ui/primitive': 1.0.1
'@radix-ui/react-collapsible': 1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.67)(react@18.2.0)
'@radix-ui/react-context': 1.0.1(@types/react@18.2.67)(react@18.2.0)
'@radix-ui/react-direction': 1.0.1(@types/react@18.2.67)(react@18.2.0)
'@radix-ui/react-id': 1.0.1(@types/react@18.2.67)(react@18.2.0)
'@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.67)(react@18.2.0)
'@types/react': 18.2.67
'@types/react-dom': 18.2.22
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/@radix-ui/react-alert-dialog@1.0.5(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0): /@radix-ui/react-alert-dialog@1.0.5(@types/react-dom@18.2.22)(@types/react@18.2.67)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-OrVIOcZL0tl6xibeuGt5/+UxoT2N27KCFOPjFyfXMnchxSHZ/OW7cCX2nGlIYJrbHK/fczPcFzAwvNBB6XBNMA==} resolution: {integrity: sha512-OrVIOcZL0tl6xibeuGt5/+UxoT2N27KCFOPjFyfXMnchxSHZ/OW7cCX2nGlIYJrbHK/fczPcFzAwvNBB6XBNMA==}
peerDependencies: peerDependencies:
@@ -4187,7 +4223,7 @@ packages:
/@tanstack/query-core@5.28.6: /@tanstack/query-core@5.28.6:
resolution: {integrity: sha512-hnhotV+DnQtvtR3jPvbQMPNMW4KEK0J4k7c609zJ8muiNknm+yoDyMHmxTWM5ZnlZpsz0zOxYFr+mzRJNHWJsA==} resolution: {integrity: sha512-hnhotV+DnQtvtR3jPvbQMPNMW4KEK0J4k7c609zJ8muiNknm+yoDyMHmxTWM5ZnlZpsz0zOxYFr+mzRJNHWJsA==}
/@tanstack/react-query-next-experimental@5.28.6(@tanstack/react-query@5.28.6)(next@14.2.0-canary.41)(react@18.2.0): /@tanstack/react-query-next-experimental@5.28.6(@tanstack/react-query@5.28.6)(next@14.2.0-canary.43)(react@18.2.0):
resolution: {integrity: sha512-KHTR1nGChTXk/kELit2gaqF3cQuN68F5UJv0377Gz5DnllPnBegja6if2W9KtKxm3Z1xP0j8LQXplqlqny2SYw==} resolution: {integrity: sha512-KHTR1nGChTXk/kELit2gaqF3cQuN68F5UJv0377Gz5DnllPnBegja6if2W9KtKxm3Z1xP0j8LQXplqlqny2SYw==}
peerDependencies: peerDependencies:
'@tanstack/react-query': ^5.28.6 '@tanstack/react-query': ^5.28.6
@@ -4195,7 +4231,7 @@ packages:
react: ^18.0.0 react: ^18.0.0
dependencies: dependencies:
'@tanstack/react-query': 5.28.6(react@18.2.0) '@tanstack/react-query': 5.28.6(react@18.2.0)
next: 14.2.0-canary.41(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0) next: 14.2.0-canary.43(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0 react: 18.2.0
dev: false dev: false
@@ -4217,10 +4253,12 @@ packages:
'@tanstack/table-core': 8.14.0 '@tanstack/table-core': 8.14.0
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false
/@tanstack/table-core@8.14.0: /@tanstack/table-core@8.14.0:
resolution: {integrity: sha512-wDhpKJahGHWhmRt4RxtV3pES63CoeadljGWS/xeS9OJr1HBl2NB+OO44ht3sxDH5j5TRDAbQzC0NvSlsUfn7lQ==} resolution: {integrity: sha512-wDhpKJahGHWhmRt4RxtV3pES63CoeadljGWS/xeS9OJr1HBl2NB+OO44ht3sxDH5j5TRDAbQzC0NvSlsUfn7lQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
dev: false
/@tootallnate/quickjs-emscripten@0.23.0: /@tootallnate/quickjs-emscripten@0.23.0:
resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
@@ -6033,12 +6071,12 @@ packages:
/eastasianwidth@0.2.0: /eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
/edge-csrf@1.0.9(next@14.2.0-canary.41): /edge-csrf@1.0.9(next@14.2.0-canary.43):
resolution: {integrity: sha512-3F89YTh42UDdISr3s9AEcgJDLi4ysgjGfnybzF0LuZGaG2W31h1ZwgWwEQBLMj04lAklcP4XHZYi7vk9o8zcbg==} resolution: {integrity: sha512-3F89YTh42UDdISr3s9AEcgJDLi4ysgjGfnybzF0LuZGaG2W31h1ZwgWwEQBLMj04lAklcP4XHZYi7vk9o8zcbg==}
peerDependencies: peerDependencies:
next: ^13.0.0 || ^14.0.0 next: ^13.0.0 || ^14.0.0
dependencies: dependencies:
next: 14.2.0-canary.41(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0) next: 14.2.0-canary.43(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0)
dev: false dev: false
/editorconfig@1.0.4: /editorconfig@1.0.4:
@@ -8784,7 +8822,7 @@ packages:
engines: {node: '>= 0.4.0'} engines: {node: '>= 0.4.0'}
dev: true dev: true
/next-contentlayer@0.3.4(contentlayer@0.3.4)(esbuild@0.19.11)(next@14.2.0-canary.41)(react-dom@18.2.0)(react@18.2.0): /next-contentlayer@0.3.4(contentlayer@0.3.4)(esbuild@0.19.11)(next@14.2.0-canary.43)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-UtUCwgAl159KwfhNaOwyiI7Lg6sdioyKMeh+E7jxx0CJ29JuXGxBEYmCI6+72NxFGIFZKx8lvttbbQhbnYWYSw==} resolution: {integrity: sha512-UtUCwgAl159KwfhNaOwyiI7Lg6sdioyKMeh+E7jxx0CJ29JuXGxBEYmCI6+72NxFGIFZKx8lvttbbQhbnYWYSw==}
peerDependencies: peerDependencies:
contentlayer: 0.3.4 contentlayer: 0.3.4
@@ -8795,7 +8833,7 @@ packages:
'@contentlayer/core': 0.3.4(esbuild@0.19.11) '@contentlayer/core': 0.3.4(esbuild@0.19.11)
'@contentlayer/utils': 0.3.4 '@contentlayer/utils': 0.3.4
contentlayer: 0.3.4(esbuild@0.19.11) contentlayer: 0.3.4(esbuild@0.19.11)
next: 14.2.0-canary.41(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0) next: 14.2.0-canary.43(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
transitivePeerDependencies: transitivePeerDependencies:
@@ -8805,7 +8843,7 @@ packages:
- supports-color - supports-color
dev: false dev: false
/next-sitemap@4.2.3(next@14.2.0-canary.41): /next-sitemap@4.2.3(next@14.2.0-canary.43):
resolution: {integrity: sha512-vjdCxeDuWDzldhCnyFCQipw5bfpl4HmZA7uoo3GAaYGjGgfL4Cxb1CiztPuWGmS+auYs7/8OekRS8C2cjdAsjQ==} resolution: {integrity: sha512-vjdCxeDuWDzldhCnyFCQipw5bfpl4HmZA7uoo3GAaYGjGgfL4Cxb1CiztPuWGmS+auYs7/8OekRS8C2cjdAsjQ==}
engines: {node: '>=14.18'} engines: {node: '>=14.18'}
hasBin: true hasBin: true
@@ -8816,17 +8854,17 @@ packages:
'@next/env': 13.5.6 '@next/env': 13.5.6
fast-glob: 3.3.2 fast-glob: 3.3.2
minimist: 1.2.8 minimist: 1.2.8
next: 14.2.0-canary.41(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0) next: 14.2.0-canary.43(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0)
dev: false dev: false
/next-themes@0.2.1(next@14.2.0-canary.41)(react-dom@18.2.0)(react@18.2.0): /next-themes@0.2.1(next@14.2.0-canary.43)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==} resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==}
peerDependencies: peerDependencies:
next: '*' next: '*'
react: '*' react: '*'
react-dom: '*' react-dom: '*'
dependencies: dependencies:
next: 14.2.0-canary.41(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0) next: 14.2.0-canary.43(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0)
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
dev: false dev: false
@@ -8909,22 +8947,25 @@ packages:
- babel-plugin-macros - babel-plugin-macros
dev: false dev: false
/next@14.2.0-canary.41(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0): /next@14.2.0-canary.43(@opentelemetry/api@1.8.0)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-jqNSTq1COP04WXpj88Bzt8kkCLXFNpCU4tHzQrICAMVWeNtHTZpK2WPR8560SWBw614bW2qHYY85k0tez3YLiA==} resolution: {integrity: sha512-tL5fxsleOuRS7Momx5wRwkCOPLybQKwgJnpzgMGVReQs+kA9lkQiBANvlYdAsrvZ3vjzx2H+9mSqKDcKaC8UXQ==}
engines: {node: '>=18.17.0'} engines: {node: '>=18.17.0'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
'@opentelemetry/api': ^1.1.0 '@opentelemetry/api': ^1.1.0
'@playwright/test': ^1.41.2
react: ^18.2.0 react: ^18.2.0
react-dom: ^18.2.0 react-dom: ^18.2.0
sass: ^1.3.0 sass: ^1.3.0
peerDependenciesMeta: peerDependenciesMeta:
'@opentelemetry/api': '@opentelemetry/api':
optional: true optional: true
'@playwright/test':
optional: true
sass: sass:
optional: true optional: true
dependencies: dependencies:
'@next/env': 14.2.0-canary.41 '@next/env': 14.2.0-canary.43
'@opentelemetry/api': 1.8.0 '@opentelemetry/api': 1.8.0
'@swc/helpers': 0.5.5 '@swc/helpers': 0.5.5
busboy: 1.6.0 busboy: 1.6.0
@@ -8935,15 +8976,15 @@ packages:
react-dom: 18.2.0(react@18.2.0) react-dom: 18.2.0(react@18.2.0)
styled-jsx: 5.1.1(react@18.2.0) styled-jsx: 5.1.1(react@18.2.0)
optionalDependencies: optionalDependencies:
'@next/swc-darwin-arm64': 14.2.0-canary.41 '@next/swc-darwin-arm64': 14.2.0-canary.43
'@next/swc-darwin-x64': 14.2.0-canary.41 '@next/swc-darwin-x64': 14.2.0-canary.43
'@next/swc-linux-arm64-gnu': 14.2.0-canary.41 '@next/swc-linux-arm64-gnu': 14.2.0-canary.43
'@next/swc-linux-arm64-musl': 14.2.0-canary.41 '@next/swc-linux-arm64-musl': 14.2.0-canary.43
'@next/swc-linux-x64-gnu': 14.2.0-canary.41 '@next/swc-linux-x64-gnu': 14.2.0-canary.43
'@next/swc-linux-x64-musl': 14.2.0-canary.41 '@next/swc-linux-x64-musl': 14.2.0-canary.43
'@next/swc-win32-arm64-msvc': 14.2.0-canary.41 '@next/swc-win32-arm64-msvc': 14.2.0-canary.43
'@next/swc-win32-ia32-msvc': 14.2.0-canary.41 '@next/swc-win32-ia32-msvc': 14.2.0-canary.43
'@next/swc-win32-x64-msvc': 14.2.0-canary.41 '@next/swc-win32-x64-msvc': 14.2.0-canary.43
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
- babel-plugin-macros - babel-plugin-macros

View File

@@ -19,7 +19,6 @@ const config = {
'^@supabase/supabase-js$', '^@supabase/supabase-js$',
'^@supabase/gotrue-js$', '^@supabase/gotrue-js$',
'<THIRD_PARTY_MODULES>', '<THIRD_PARTY_MODULES>',
'^@packages/(.*)$',
'^@kit/(.*)$', '^@kit/(.*)$',
'^~/(.*)$', // app-specific imports '^~/(.*)$', // app-specific imports
'^[./]', // relative imports '^[./]', // relative imports

View File

@@ -3,7 +3,7 @@ import { fontFamily } from 'tailwindcss/defaultTheme';
export default { export default {
darkMode: ['class'], darkMode: ['class'],
content: ['../../packages/**/*.{ts,tsx}', '../../apps/**/*.{ts,tsx}'], content: ['../../packages/**/*.tsx', '../../apps/**/*.tsx'],
theme: { theme: {
container: { container: {
center: true, center: true,

View File

@@ -16,5 +16,6 @@
"jsx": "preserve", "jsx": "preserve",
"incremental": true, "incremental": true,
"noUncheckedIndexedAccess": true "noUncheckedIndexedAccess": true
} },
"exclude": ["node_modules"]
} }

View File

@@ -1,31 +1,49 @@
{ {
"$schema": "https://turborepo.org/schema.json", "$schema": "https://turborepo.org/schema.json",
"globalDependencies": ["**/.env"], "globalDependencies": [
"**/.env"
],
"pipeline": { "pipeline": {
"topo": { "topo": {
"dependsOn": ["^topo"] "dependsOn": [
"^topo"
]
}, },
"build": { "build": {
"dependsOn": ["^build"], "dependsOn": [
"outputs": [".next/**", "!.next/cache/**", "next-env.d.ts"], "^build"
"dotEnv": [".env.production.local", ".env.local", ".env.production", ".env"] ],
"outputs": [
".next/**",
"!.next/cache/**",
"next-env.d.ts"
]
}, },
"dev": { "dev": {
"persistent": true, "persistent": true,
"cache": false, "cache": false
"dotEnv": [".env.development.local", ".env.local", ".env.development", ".env"]
}, },
"format": { "format": {
"outputs": ["node_modules/.cache/.prettiercache"], "outputs": [
"node_modules/.cache/.prettiercache"
],
"outputMode": "new-only" "outputMode": "new-only"
}, },
"lint": { "lint": {
"dependsOn": ["^topo"], "dependsOn": [
"outputs": ["node_modules/.cache/.eslintcache"] "^topo"
],
"outputs": [
"node_modules/.cache/.eslintcache"
]
}, },
"typecheck": { "typecheck": {
"dependsOn": ["^topo"], "dependsOn": [
"outputs": ["node_modules/.cache/tsbuildinfo.json"] "^topo"
],
"outputs": [
"node_modules/.cache/tsbuildinfo.json"
]
}, },
"clean": { "clean": {
"cache": false "cache": false