Next.js Supabase V3 (#463)

Version 3 of the kit:
- Radix UI replaced with Base UI (using the Shadcn UI patterns)
- next-intl replaces react-i18next
- enhanceAction deprecated; usage moved to next-safe-action
- main layout now wrapped with [locale] path segment
- Teams only mode
- Layout updates
- Zod v4
- Next.js 16.2
- Typescript 6
- All other dependencies updated
- Removed deprecated Edge CSRF
- Dynamic Github Action runner
This commit is contained in:
Giancarlo Buomprisco
2026-03-24 13:40:38 +08:00
committed by GitHub
parent 4912e402a3
commit 7ebff31475
840 changed files with 71395 additions and 20095 deletions

View File

@@ -1,3 +0,0 @@
import eslintConfigBase from '@kit/eslint-config/base.js';
export default eslintConfigBase;

View File

@@ -1,33 +1,7 @@
{
"name": "@kit/notifications",
"private": true,
"version": "0.1.0",
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"format": "prettier --check \"**/*.{ts,tsx}\"",
"lint": "eslint .",
"typecheck": "tsc --noEmit"
},
"exports": {
"./api": "./src/server/api.ts",
"./components": "./src/components/index.ts",
"./hooks": "./src/hooks/index.ts"
},
"devDependencies": {
"@kit/eslint-config": "workspace:*",
"@kit/prettier-config": "workspace:*",
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@supabase/supabase-js": "catalog:",
"@tanstack/react-query": "catalog:",
"@types/react": "catalog:",
"lucide-react": "catalog:",
"react": "catalog:",
"react-dom": "catalog:",
"react-i18next": "catalog:"
},
"prettier": "@kit/prettier-config",
"private": true,
"typesVersions": {
"*": {
"*": [
@@ -35,7 +9,28 @@
]
}
},
"exports": {
"./api": "./src/server/api.ts",
"./components": "./src/components/index.ts",
"./hooks": "./src/hooks/index.ts"
},
"scripts": {
"clean": "git clean -xdf .turbo node_modules",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@types/node": "catalog:"
},
"devDependencies": {
"@kit/supabase": "workspace:*",
"@kit/tsconfig": "workspace:*",
"@kit/ui": "workspace:*",
"@supabase/supabase-js": "catalog:",
"@tanstack/react-query": "catalog:",
"@types/react": "catalog:",
"lucide-react": "catalog:",
"next-intl": "catalog:",
"react": "catalog:",
"react-dom": "catalog:"
}
}

View File

@@ -3,7 +3,7 @@
import { useCallback, useEffect, useState } from 'react';
import { Bell, CircleAlert, Info, TriangleAlert, XIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useLocale, useTranslations } from 'next-intl';
import { Button } from '@kit/ui/button';
import { If } from '@kit/ui/if';
@@ -19,7 +19,8 @@ export function NotificationsPopover(params: {
accountIds: string[];
onClick?: (notification: Notification) => void;
}) {
const { i18n, t } = useTranslation();
const t = useTranslations();
const locale = useLocale();
const [open, setOpen] = useState(false);
const [notifications, setNotifications] = useState<Notification[]>([]);
@@ -53,7 +54,7 @@ export function NotificationsPopover(params: {
(new Date().getTime() - date.getTime()) / (1000 * 60 * 60 * 24),
);
const formatter = new Intl.RelativeTimeFormat(i18n.language, {
const formatter = new Intl.RelativeTimeFormat(locale, {
numeric: 'auto',
});
@@ -61,7 +62,7 @@ export function NotificationsPopover(params: {
time = Math.floor((new Date().getTime() - date.getTime()) / (1000 * 60));
if (time < 5) {
return t('common:justNow');
return t('common.justNow');
}
if (time < 60) {
@@ -110,46 +111,39 @@ export function NotificationsPopover(params: {
return (
<Popover modal open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button className={'relative h-9 w-9'} variant={'ghost'}>
<Bell className={'min-h-4 min-w-4'} />
<PopoverTrigger
render={<Button size="icon-lg" variant="ghost" className="relative" />}
>
<Bell className={'size-4 min-h-3 min-w-3'} />
<span
className={cn(
`fade-in animate-in zoom-in absolute top-1 right-1 mt-0 flex h-3.5 w-3.5 items-center justify-center rounded-full bg-red-500 text-[0.65rem] text-white`,
{
hidden: !notifications.length,
},
)}
>
{notifications.length}
</span>
</Button>
<span
className={cn(
`fade-in animate-in zoom-in absolute top-1 right-1 mt-0 flex h-3 w-3 items-center justify-center rounded-full bg-red-500 text-[0.6rem] text-white`,
{
hidden: !notifications.length,
},
)}
>
{notifications.length}
</span>
</PopoverTrigger>
<PopoverContent
className={'flex w-full max-w-96 flex-col p-0 lg:min-w-64'}
className={'flex w-full max-w-96 flex-col gap-0 lg:min-w-64'}
align={'start'}
collisionPadding={20}
sideOffset={10}
>
<div className={'flex items-center px-3 py-2 text-sm font-semibold'}>
{t('common:notifications')}
<div className={'flex items-center text-sm font-semibold'}>
{t('common.notifications')}
</div>
<Separator />
<If condition={!notifications.length}>
<div className={'px-3 py-2 text-sm'}>
{t('common:noNotifications')}
</div>
<div className={'text-sm'}>{t('common.noNotifications')}</div>
</If>
<div
className={
'flex max-h-[60vh] flex-col divide-y divide-gray-100 overflow-y-auto dark:divide-gray-800'
}
>
<div className={'flex max-h-[60vh] flex-col overflow-y-auto'}>
{notifications.map((notification) => {
const maxChars = 100;
@@ -164,11 +158,11 @@ export function NotificationsPopover(params: {
const Icon = () => {
switch (notification.type) {
case 'warning':
return <TriangleAlert className={'h-4 text-yellow-500'} />;
return <TriangleAlert className={'size-3 text-yellow-500'} />;
case 'error':
return <CircleAlert className={'text-destructive h-4'} />;
return <CircleAlert className={'text-destructive size-3'} />;
default:
return <Info className={'h-4 text-blue-500'} />;
return <Info className={'size-3 text-blue-500'} />;
}
};
@@ -176,7 +170,7 @@ export function NotificationsPopover(params: {
<div
key={notification.id.toString()}
className={cn(
'flex min-h-18 flex-col items-start justify-center gap-y-1 px-3 py-2',
'flex min-h-14 flex-col items-start justify-center gap-y-1 px-1',
)}
onClick={() => {
if (params.onClick) {
@@ -185,15 +179,11 @@ export function NotificationsPopover(params: {
}}
>
<div className={'flex w-full items-start justify-between'}>
<div
className={'flex items-start justify-start gap-x-3 py-2'}
>
<div className={'py-0.5'}>
<Icon />
</div>
<div className={'flex items-start justify-start gap-x-1.5'}>
<div className={'flex flex-col'}>
<div className={'flex items-center gap-x-2 text-sm'}>
<Icon />
<div className={'flex flex-col space-y-1'}>
<div className={'text-sm'}>
<If condition={notification.link} fallback={body}>
{(link) => (
<a href={link} className={'hover:underline'}>
@@ -209,7 +199,7 @@ export function NotificationsPopover(params: {
</div>
</div>
<div className={'py-2'}>
<div className={'ml-2'}>
<Button
className={'max-h-6 max-w-6'}
size={'icon'}

View File

@@ -1,5 +1,4 @@
import 'server-only';
import { SupabaseClient } from '@supabase/supabase-js';
import { Database } from '@kit/supabase/database';