refator: biome lint part 2

This commit is contained in:
javayhu 2025-04-18 21:53:21 +08:00
parent 23cd59bbac
commit 27bc59354f
27 changed files with 377 additions and 390 deletions

View File

@ -22,6 +22,8 @@
"src/components/magicui/*.tsx",
"src/app/[[]locale]/preview/**",
"src/db/schema.ts",
"src/payment/types.ts",
"src/types/index.d.ts",
"public/sw.js"
]
},
@ -78,4 +80,4 @@
"semicolons": "always"
}
}
}
}

View File

@ -1,4 +1,4 @@
import path from 'path';
import path from 'node:path';
import { DEFAULT_LOCALE, LOCALES } from '@/i18n/routing';
import { defineCollection, defineConfig } from '@content-collections/core';
import {
@ -313,14 +313,14 @@ function extractLocaleAndBase(fileName: string): {
if (parts.length === 1) {
// Simple filename without locale: xxx
return { locale: DEFAULT_LOCALE, base: parts[0] };
} else if (parts.length === 2 && LOCALES.includes(parts[1])) {
}
if (parts.length === 2 && LOCALES.includes(parts[1])) {
// Filename with locale: xxx.zh
return { locale: parts[1], base: parts[0] };
} else {
// Unexpected format, use first part as base and default locale
console.warn(`Unexpected filename format: ${fileName}`);
return { locale: DEFAULT_LOCALE, base: parts[0] };
}
// Unexpected format, use first part as base and default locale
console.warn(`Unexpected filename format: ${fileName}`);
return { locale: DEFAULT_LOCALE, base: parts[0] };
}
export default defineConfig({

View File

@ -7,6 +7,7 @@
"build": "content-collections build && next build",
"start": "next start",
"lint": "biome check --write .",
"lint:fix": "biome check --fix --unsafe .",
"format": "biome format --write .",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",

View File

@ -1,4 +1,4 @@
import { randomUUID } from 'crypto';
import { randomUUID } from 'node:crypto';
import { getPresignedUploadUrl } from '@/storage';
import { StorageError } from '@/storage/types';
import { type NextRequest, NextResponse } from 'next/server';

View File

@ -48,7 +48,7 @@ export default function Features2Section() {
return (
<section className="py-16">
<div className="bg-linear-to-b absolute inset-0 -z-10 sm:inset-6 sm:rounded-b-3xl dark:block dark:to-[color-mix(in_oklab,var(--color-zinc-900)_75%,var(--color-background))]"></div>
<div className="bg-linear-to-b absolute inset-0 -z-10 sm:inset-6 sm:rounded-b-3xl dark:block dark:to-[color-mix(in_oklab,var(--color-zinc-900)_75%,var(--color-background))]" />
<div className="mx-auto max-w-6xl space-y-8 px-6 md:space-y-16 lg:space-y-20 dark:[--color-border:color-mix(in_oklab,var(--color-white)_10%,transparent)]">
<div className="relative z-10 mx-auto max-w-2xl space-y-6 text-center">
<h2 className="text-balance text-4xl lg:text-5xl font-semibold">

View File

@ -21,7 +21,7 @@ export default function Integration2Section() {
<div className="mx-auto max-w-5xl px-6">
<div className="grid items-center sm:grid-cols-2">
<div className="dark:bg-muted/50 relative mx-auto w-fit">
<div className="bg-radial to-muted dark:to-background absolute inset-0 z-10 from-transparent to-75%"></div>
<div className="bg-radial to-muted dark:to-background absolute inset-0 z-10 from-transparent to-75%" />
<div className="mx-auto mb-2 flex w-fit justify-center gap-2">
<IntegrationCard>
<Gemini />

View File

@ -127,7 +127,7 @@ export default function TestimonialsSection() {
width="120"
height="120"
/>
<AvatarFallback></AvatarFallback>
<AvatarFallback />
</Avatar>
<div>

View File

@ -69,7 +69,7 @@ export function ContactFormCard() {
// Submit form data using the contact server action
const result = await sendMessageAction(values);
if (result && result.data?.success) {
if (result?.data?.success) {
toast.success(t('success'));
form.reset();
} else {

View File

@ -607,16 +607,16 @@ export function DataTable({
value="past-performance"
className="flex flex-col px-4 lg:px-6"
>
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed" />
</TabsContent>
<TabsContent value="key-personnel" className="flex flex-col px-4 lg:px-6">
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed" />
</TabsContent>
<TabsContent
value="focus-documents"
className="flex flex-col px-4 lg:px-6"
>
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed" />
</TabsContent>
</Tabs>
);

View File

@ -16,7 +16,7 @@ export function DiscordIcon(props: SVGProps<SVGSVGElement>) {
<path
fill="currentColor"
d="M524.531 69.836a1.5 1.5 0 0 0-.764-.7A485 485 0 0 0 404.081 32.03a1.82 1.82 0 0 0-1.923.91a338 338 0 0 0-14.9 30.6a447.9 447.9 0 0 0-134.426 0a310 310 0 0 0-15.135-30.6a1.89 1.89 0 0 0-1.924-.91a483.7 483.7 0 0 0-119.688 37.107a1.7 1.7 0 0 0-.788.676C39.068 183.651 18.186 294.69 28.43 404.354a2.02 2.02 0 0 0 .765 1.375a487.7 487.7 0 0 0 146.825 74.189a1.9 1.9 0 0 0 2.063-.676A348 348 0 0 0 208.12 430.4a1.86 1.86 0 0 0-1.019-2.588a321 321 0 0 1-45.868-21.853a1.885 1.885 0 0 1-.185-3.126a251 251 0 0 0 9.109-7.137a1.82 1.82 0 0 1 1.9-.256c96.229 43.917 200.41 43.917 295.5 0a1.81 1.81 0 0 1 1.924.233a235 235 0 0 0 9.132 7.16a1.884 1.884 0 0 1-.162 3.126a301.4 301.4 0 0 1-45.89 21.83a1.875 1.875 0 0 0-1 2.611a391 391 0 0 0 30.014 48.815a1.86 1.86 0 0 0 2.063.7A486 486 0 0 0 610.7 405.729a1.88 1.88 0 0 0 .765-1.352c12.264-126.783-20.532-236.912-86.934-334.541M222.491 337.58c-28.972 0-52.844-26.587-52.844-59.239s23.409-59.241 52.844-59.241c29.665 0 53.306 26.82 52.843 59.239c0 32.654-23.41 59.241-52.843 59.241m195.38 0c-28.971 0-52.843-26.587-52.843-59.239s23.409-59.241 52.843-59.241c29.667 0 53.307 26.82 52.844 59.239c0 32.654-23.177 59.241-52.844 59.241"
></path>
/>
</svg>
);
}

View File

@ -16,7 +16,7 @@ export function FacebookIcon(props: SVGProps<SVGSVGElement>) {
<path
fill="currentColor"
d="M512 256C512 114.6 397.4 0 256 0S0 114.6 0 256c0 120 82.7 220.8 194.2 248.5V334.2h-52.8V256h52.8v-33.7c0-87.1 39.4-127.5 125-127.5c16.2 0 44.2 3.2 55.7 6.4V172c-6-.6-16.5-1-29.6-1c-42 0-58.2 15.9-58.2 57.2V256h83.6l-14.4 78.2H287v175.9C413.8 494.8 512 386.9 512 256"
></path>
/>
</svg>
);
}

View File

@ -16,7 +16,7 @@ export function InstagramIcon(props: SVGProps<SVGSVGElement>) {
<path
fill="currentColor"
d="M224.1 141c-63.6 0-114.9 51.3-114.9 114.9s51.3 114.9 114.9 114.9S339 319.5 339 255.9S287.7 141 224.1 141m0 189.6c-41.1 0-74.7-33.5-74.7-74.7s33.5-74.7 74.7-74.7s74.7 33.5 74.7 74.7s-33.6 74.7-74.7 74.7m146.4-194.3c0 14.9-12 26.8-26.8 26.8c-14.9 0-26.8-12-26.8-26.8s12-26.8 26.8-26.8s26.8 12 26.8 26.8m76.1 27.2c-1.7-35.9-9.9-67.7-36.2-93.9c-26.2-26.2-58-34.4-93.9-36.2c-37-2.1-147.9-2.1-184.9 0c-35.8 1.7-67.6 9.9-93.9 36.1s-34.4 58-36.2 93.9c-2.1 37-2.1 147.9 0 184.9c1.7 35.9 9.9 67.7 36.2 93.9s58 34.4 93.9 36.2c37 2.1 147.9 2.1 184.9 0c35.9-1.7 67.7-9.9 93.9-36.2c26.2-26.2 34.4-58 36.2-93.9c2.1-37 2.1-147.8 0-184.8M398.8 388c-7.8 19.6-22.9 34.7-42.6 42.6c-29.5 11.7-99.5 9-132.1 9s-102.7 2.6-132.1-9c-19.6-7.8-34.7-22.9-42.6-42.6c-11.7-29.5-9-99.5-9-132.1s-2.6-102.7 9-132.1c7.8-19.6 22.9-34.7 42.6-42.6c29.5-11.7 99.5-9 132.1-9s102.7-2.6 132.1 9c19.6 7.8 34.7 22.9 42.6 42.6c11.7 29.5 9 99.5 9 132.1s2.7 102.7-9 132.1"
></path>
/>
</svg>
);
}

View File

@ -16,7 +16,7 @@ export function LinkedInIcon(props: SVGProps<SVGSVGElement>) {
<path
fill="currentColor"
d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3M135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5c0 21.3-17.2 38.5-38.5 38.5m282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7c-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5c67.2 0 79.7 44.3 79.7 101.9z"
></path>
/>
</svg>
);
}

View File

@ -16,7 +16,7 @@ export function TikTokIcon(props: SVGProps<SVGSVGElement>) {
<path
fill="currentColor"
d="M448 209.91a210.06 210.06 0 0 1-122.77-39.25v178.72A162.55 162.55 0 1 1 185 188.31v89.89a74.62 74.62 0 1 0 52.23 71.18V0h88a121 121 0 0 0 1.86 22.17A122.18 122.18 0 0 0 381 102.39a121.43 121.43 0 0 0 67 20.14Z"
></path>
/>
</svg>
);
}

View File

@ -16,7 +16,7 @@ export function TwitterIcon(props: SVGProps<SVGSVGElement>) {
<path
fill="currentColor"
d="M459.37 151.716c.325 4.548.325 9.097.325 13.645c0 138.72-105.583 298.558-298.558 298.558c-59.452 0-114.68-17.219-161.137-47.106c8.447.974 16.568 1.299 25.34 1.299c49.055 0 94.213-16.568 130.274-44.832c-46.132-.975-84.792-31.188-98.112-72.772c6.498.974 12.995 1.624 19.818 1.624c9.421 0 18.843-1.3 27.614-3.573c-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319c-28.264-18.843-46.781-51.005-46.781-87.391c0-19.492 5.197-37.36 14.294-52.954c51.655 63.675 129.3 105.258 216.365 109.807c-1.624-7.797-2.599-15.918-2.599-24.04c0-57.828 46.782-104.934 104.934-104.934c30.213 0 57.502 12.67 76.67 33.137c23.715-4.548 46.456-13.32 66.599-25.34c-7.798 24.366-24.366 44.833-46.132 57.827c21.117-2.273 41.584-8.122 60.426-16.243c-14.292 20.791-32.161 39.308-52.628 54.253"
></path>
/>
</svg>
);
}

View File

@ -39,21 +39,20 @@ export function Footer({ className }: React.HTMLAttributes<HTMLElement>) {
{/* social links */}
<div className="flex items-center gap-4 py-2">
<div className="flex items-center gap-2">
{socialLinks &&
socialLinks.map((link) => (
<a
key={link.title}
href={link.href || '#'}
target="_blank"
rel="noreferrer"
aria-label={link.title}
className="border border-border inline-flex h-8 w-8 items-center
{socialLinks?.map((link) => (
<a
key={link.title}
href={link.href || '#'}
target="_blank"
rel="noreferrer"
aria-label={link.title}
className="border border-border inline-flex h-8 w-8 items-center
justify-center rounded-full hover:bg-accent hover:text-accent-foreground"
>
<span className="sr-only">{link.title}</span>
{link.icon ? link.icon : null}
</a>
))}
>
<span className="sr-only">{link.title}</span>
{link.icon ? link.icon : null}
</a>
))}
</div>
</div>
@ -63,33 +62,32 @@ export function Footer({ className }: React.HTMLAttributes<HTMLElement>) {
</div>
{/* footer links */}
{footerLinks &&
footerLinks.map((section) => (
<div
key={section.title}
className="col-span-1 md:col-span-1 items-start"
>
<span className="text-sm font-semibold uppercase">
{section.title}
</span>
<ul className="mt-4 list-inside space-y-3">
{section.items?.map(
(item) =>
item.href && (
<li key={item.title}>
<LocaleLink
href={item.href || '#'}
target={item.external ? '_blank' : undefined}
className="text-sm text-muted-foreground hover:text-primary"
>
{item.title}
</LocaleLink>
</li>
)
)}
</ul>
</div>
))}
{footerLinks?.map((section) => (
<div
key={section.title}
className="col-span-1 md:col-span-1 items-start"
>
<span className="text-sm font-semibold uppercase">
{section.title}
</span>
<ul className="mt-4 list-inside space-y-3">
{section.items?.map(
(item) =>
item.href && (
<li key={item.title}>
<LocaleLink
href={item.href || '#'}
target={item.external ? '_blank' : undefined}
className="text-sm text-muted-foreground hover:text-primary"
>
{item.title}
</LocaleLink>
</li>
)
)}
</ul>
</div>
))}
</div>
</Container>

View File

@ -177,104 +177,101 @@ function MainMobileMenu({ userLoggedIn, onLinkClicked }: MainMobileMenuProps) {
{/* main menu */}
<ul className="w-full px-4">
{menuLinks &&
menuLinks.map((item) => {
const isActive = item.href
? localePathname.startsWith(item.href)
: item.items?.some(
(subItem) =>
subItem.href && localePathname.startsWith(subItem.href)
);
{menuLinks?.map((item) => {
const isActive = item.href
? localePathname.startsWith(item.href)
: item.items?.some(
(subItem) =>
subItem.href && localePathname.startsWith(subItem.href)
);
return (
<li key={item.title} className="py-1">
{item.items ? (
<Collapsible
open={expanded[item.title.toLowerCase()]}
onOpenChange={(isOpen) =>
setExpanded((prev) => ({
...prev,
[item.title.toLowerCase()]: isOpen,
}))
}
>
<CollapsibleTrigger asChild>
<Button
type="button"
variant="ghost"
className={cn(
'flex w-full !pl-2 items-center justify-between text-left',
'bg-transparent text-muted-foreground cursor-pointer',
'hover:bg-transparent hover:text-foreground',
'focus:bg-transparent focus:text-foreground',
isActive &&
'font-semibold bg-transparent text-foreground'
)}
>
<span className="text-base">{item.title}</span>
{expanded[item.title.toLowerCase()] ? (
<ChevronDownIcon className="size-4" />
) : (
<ChevronRightIcon className="size-4" />
)}
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="pl-2">
<ul className="mt-2 space-y-2 pl-0">
{item.items.map((subItem) => {
const isSubItemActive =
subItem.href &&
localePathname.startsWith(subItem.href);
return (
<li key={item.title} className="py-1">
{item.items ? (
<Collapsible
open={expanded[item.title.toLowerCase()]}
onOpenChange={(isOpen) =>
setExpanded((prev) => ({
...prev,
[item.title.toLowerCase()]: isOpen,
}))
}
>
<CollapsibleTrigger asChild>
<Button
type="button"
variant="ghost"
className={cn(
'flex w-full !pl-2 items-center justify-between text-left',
'bg-transparent text-muted-foreground cursor-pointer',
'hover:bg-transparent hover:text-foreground',
'focus:bg-transparent focus:text-foreground',
isActive &&
'font-semibold bg-transparent text-foreground'
)}
>
<span className="text-base">{item.title}</span>
{expanded[item.title.toLowerCase()] ? (
<ChevronDownIcon className="size-4" />
) : (
<ChevronRightIcon className="size-4" />
)}
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="pl-2">
<ul className="mt-2 space-y-2 pl-0">
{item.items.map((subItem) => {
const isSubItemActive =
subItem.href &&
localePathname.startsWith(subItem.href);
return (
<li key={subItem.title}>
<LocaleLink
href={subItem.href || '#'}
target={
subItem.external ? '_blank' : undefined
}
rel={
subItem.external
? 'noopener noreferrer'
: undefined
}
return (
<li key={subItem.title}>
<LocaleLink
href={subItem.href || '#'}
target={subItem.external ? '_blank' : undefined}
rel={
subItem.external
? 'noopener noreferrer'
: undefined
}
className={cn(
buttonVariants({ variant: 'ghost' }),
'group h-auto w-full justify-start gap-4 p-1 !pl-0 !pr-3',
'bg-transparent text-muted-foreground cursor-pointer',
'hover:bg-transparent hover:text-foreground',
'focus:bg-transparent focus:text-foreground',
isSubItemActive &&
'font-semibold bg-transparent text-foreground'
)}
onClick={onLinkClicked}
>
<div
className={cn(
buttonVariants({ variant: 'ghost' }),
'group h-auto w-full justify-start gap-4 p-1 !pl-0 !pr-3',
'bg-transparent text-muted-foreground cursor-pointer',
'hover:bg-transparent hover:text-foreground',
'focus:bg-transparent focus:text-foreground',
'flex size-8 shrink-0 items-center justify-center transition-colors ml-0',
'bg-transparent text-muted-foreground',
'group-hover:bg-transparent group-hover:text-foreground',
'group-focus:bg-transparent group-focus:text-foreground',
isSubItemActive &&
'font-semibold bg-transparent text-foreground'
'bg-transparent text-foreground'
)}
onClick={onLinkClicked}
>
<div
{subItem.icon ? subItem.icon : null}
</div>
<div className="flex-1">
<span
className={cn(
'flex size-8 shrink-0 items-center justify-center transition-colors ml-0',
'bg-transparent text-muted-foreground',
'text-sm text-muted-foreground',
'group-hover:bg-transparent group-hover:text-foreground',
'group-focus:bg-transparent group-focus:text-foreground',
isSubItemActive &&
'bg-transparent text-foreground'
'font-semibold bg-transparent text-foreground'
)}
>
{subItem.icon ? subItem.icon : null}
</div>
<div className="flex-1">
<span
className={cn(
'text-sm text-muted-foreground',
'group-hover:bg-transparent group-hover:text-foreground',
'group-focus:bg-transparent group-focus:text-foreground',
isSubItemActive &&
'font-semibold bg-transparent text-foreground'
)}
>
{subItem.title}
</span>
{/* hide description for now */}
{/* {subItem.description && (
{subItem.title}
</span>
{/* hide description for now */}
{/* {subItem.description && (
<p
className={cn(
'text-xs text-muted-foreground',
@ -287,49 +284,48 @@ function MainMobileMenu({ userLoggedIn, onLinkClicked }: MainMobileMenuProps) {
{subItem.description}
</p>
)} */}
</div>
{subItem.external && (
<ArrowUpRightIcon
className={cn(
'size-4 shrink-0 text-muted-foreground items-center',
'group-hover:bg-transparent group-hover:text-foreground',
'group-focus:bg-transparent group-focus:text-foreground',
isSubItemActive &&
'bg-transparent text-foreground'
)}
/>
)}
</LocaleLink>
</li>
);
})}
</ul>
</CollapsibleContent>
</Collapsible>
) : (
<LocaleLink
href={item.href || '#'}
target={item.external ? '_blank' : undefined}
rel={item.external ? 'noopener noreferrer' : undefined}
className={cn(
buttonVariants({ variant: 'ghost' }),
'w-full !pl-2 justify-start cursor-pointer group',
'bg-transparent text-muted-foreground',
'hover:bg-transparent hover:text-foreground',
'focus:bg-transparent focus:text-foreground',
isActive &&
'font-semibold bg-transparent text-foreground'
)}
onClick={onLinkClicked}
>
<div className="flex items-center w-full pl-0">
<span className="text-base">{item.title}</span>
</div>
</LocaleLink>
)}
</li>
);
})}
</div>
{subItem.external && (
<ArrowUpRightIcon
className={cn(
'size-4 shrink-0 text-muted-foreground items-center',
'group-hover:bg-transparent group-hover:text-foreground',
'group-focus:bg-transparent group-focus:text-foreground',
isSubItemActive &&
'bg-transparent text-foreground'
)}
/>
)}
</LocaleLink>
</li>
);
})}
</ul>
</CollapsibleContent>
</Collapsible>
) : (
<LocaleLink
href={item.href || '#'}
target={item.external ? '_blank' : undefined}
rel={item.external ? 'noopener noreferrer' : undefined}
className={cn(
buttonVariants({ variant: 'ghost' }),
'w-full !pl-2 justify-start cursor-pointer group',
'bg-transparent text-muted-foreground',
'hover:bg-transparent hover:text-foreground',
'focus:bg-transparent focus:text-foreground',
isActive && 'font-semibold bg-transparent text-foreground'
)}
onClick={onLinkClicked}
>
<div className="flex items-center w-full pl-0">
<span className="text-base">{item.title}</span>
</div>
</LocaleLink>
)}
</li>
);
})}
</ul>
{/* bottom buttons */}

View File

@ -78,136 +78,132 @@ export function Navbar({ scroll }: NavBarProps) {
<div className="flex-1 flex items-center justify-center space-x-2">
<NavigationMenu className="relative">
<NavigationMenuList className="flex items-center">
{menuLinks &&
menuLinks.map((item, index) =>
item.items ? (
<NavigationMenuItem key={index} className="relative">
<NavigationMenuTrigger
data-active={
item.items.some((subItem) =>
subItem.href
? localePathname.startsWith(subItem.href)
: false
)
? 'true'
: undefined
}
className={customNavigationMenuTriggerStyle}
>
{item.title}
</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[400px] gap-4 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px]">
{item.items &&
item.items.map((subItem, subIndex) => {
const isSubItemActive =
subItem.href &&
localePathname.startsWith(subItem.href);
return (
<li key={subIndex}>
<NavigationMenuLink asChild>
<LocaleLink
href={subItem.href || '#'}
target={
subItem.external
? '_blank'
: undefined
}
rel={
subItem.external
? 'noopener noreferrer'
: undefined
}
{menuLinks?.map((item, index) =>
item.items ? (
<NavigationMenuItem key={index} className="relative">
<NavigationMenuTrigger
data-active={
item.items.some((subItem) =>
subItem.href
? localePathname.startsWith(subItem.href)
: false
)
? 'true'
: undefined
}
className={customNavigationMenuTriggerStyle}
>
{item.title}
</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-[400px] gap-4 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px]">
{item.items?.map((subItem, subIndex) => {
const isSubItemActive =
subItem.href &&
localePathname.startsWith(subItem.href);
return (
<li key={subIndex}>
<NavigationMenuLink asChild>
<LocaleLink
href={subItem.href || '#'}
target={
subItem.external ? '_blank' : undefined
}
rel={
subItem.external
? 'noopener noreferrer'
: undefined
}
className={cn(
'group flex select-none flex-row items-center gap-4 rounded-md',
'p-2 leading-none no-underline outline-hidden transition-colors',
'hover:bg-accent hover:text-accent-foreground',
'focus:bg-accent focus:text-accent-foreground',
isSubItemActive &&
'bg-accent text-accent-foreground'
)}
>
<div
className={cn(
'flex size-8 shrink-0 items-center justify-center transition-colors',
'bg-transparent text-muted-foreground',
'group-hover:bg-transparent group-hover:text-foreground',
'group-focus:bg-transparent group-focus:text-foreground',
isSubItemActive &&
'bg-transparent text-foreground'
)}
>
{subItem.icon ? subItem.icon : null}
</div>
<div className="flex-1">
<div
className={cn(
'group flex select-none flex-row items-center gap-4 rounded-md',
'p-2 leading-none no-underline outline-hidden transition-colors',
'hover:bg-accent hover:text-accent-foreground',
'focus:bg-accent focus:text-accent-foreground',
'text-sm font-medium text-muted-foreground',
'group-hover:bg-transparent group-hover:text-foreground',
'group-focus:bg-transparent group-focus:text-foreground',
isSubItemActive &&
'bg-accent text-accent-foreground'
'bg-transparent text-foreground'
)}
>
{subItem.title}
</div>
{subItem.description && (
<div
className={cn(
'flex size-8 shrink-0 items-center justify-center transition-colors',
'bg-transparent text-muted-foreground',
'group-hover:bg-transparent group-hover:text-foreground',
'group-focus:bg-transparent group-focus:text-foreground',
'text-sm text-muted-foreground',
'group-hover:bg-transparent group-hover:text-foreground/80',
'group-focus:bg-transparent group-focus:text-foreground/80',
isSubItemActive &&
'bg-transparent text-foreground'
'bg-transparent text-foreground/80'
)}
>
{subItem.icon ? subItem.icon : null}
{subItem.description}
</div>
<div className="flex-1">
<div
className={cn(
'text-sm font-medium text-muted-foreground',
'group-hover:bg-transparent group-hover:text-foreground',
'group-focus:bg-transparent group-focus:text-foreground',
isSubItemActive &&
'bg-transparent text-foreground'
)}
>
{subItem.title}
</div>
{subItem.description && (
<div
className={cn(
'text-sm text-muted-foreground',
'group-hover:bg-transparent group-hover:text-foreground/80',
'group-focus:bg-transparent group-focus:text-foreground/80',
isSubItemActive &&
'bg-transparent text-foreground/80'
)}
>
{subItem.description}
</div>
)}
</div>
{subItem.external && (
<ArrowUpRightIcon
className={cn(
'size-4 shrink-0 text-muted-foreground',
'group-hover:bg-transparent group-hover:text-foreground',
'group-focus:bg-transparent group-focus:text-foreground',
isSubItemActive &&
'bg-transparent text-foreground'
)}
/>
)}
</div>
{subItem.external && (
<ArrowUpRightIcon
className={cn(
'size-4 shrink-0 text-muted-foreground',
'group-hover:bg-transparent group-hover:text-foreground',
'group-focus:bg-transparent group-focus:text-foreground',
isSubItemActive &&
'bg-transparent text-foreground'
)}
</LocaleLink>
</NavigationMenuLink>
</li>
);
})}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
) : (
<NavigationMenuItem key={index}>
<NavigationMenuLink
asChild
active={
item.href
? localePathname.startsWith(item.href)
: false
/>
)}
</LocaleLink>
</NavigationMenuLink>
</li>
);
})}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
) : (
<NavigationMenuItem key={index}>
<NavigationMenuLink
asChild
active={
item.href
? localePathname.startsWith(item.href)
: false
}
className={customNavigationMenuTriggerStyle}
>
<LocaleLink
href={item.href || '#'}
target={item.external ? '_blank' : undefined}
rel={
item.external ? 'noopener noreferrer' : undefined
}
className={customNavigationMenuTriggerStyle}
>
<LocaleLink
href={item.href || '#'}
target={item.external ? '_blank' : undefined}
rel={
item.external ? 'noopener noreferrer' : undefined
}
>
{item.title}
</LocaleLink>
</NavigationMenuLink>
</NavigationMenuItem>
)
)}
{item.title}
</LocaleLink>
</NavigationMenuLink>
</NavigationMenuItem>
)
)}
</NavigationMenuList>
</NavigationMenu>
</div>

View File

@ -85,22 +85,21 @@ export function UserButtonMobile({ user }: UserButtonProps) {
</div>
<ul className="mb-14 mt-1 w-full text-muted-foreground">
{avatarLinks &&
avatarLinks.map((item) => (
<li
key={item.title}
className="rounded-lg text-foreground hover:bg-muted"
{avatarLinks?.map((item) => (
<li
key={item.title}
className="rounded-lg text-foreground hover:bg-muted"
>
<LocaleLink
href={item.href || '#'}
onClick={closeDrawer}
className="flex w-full items-center gap-3 px-2.5 py-2"
>
<LocaleLink
href={item.href || '#'}
onClick={closeDrawer}
className="flex w-full items-center gap-3 px-2.5 py-2"
>
{item.icon ? item.icon : null}
<p className="text-sm">{item.title}</p>
</LocaleLink>
</li>
))}
{item.icon ? item.icon : null}
<p className="text-sm">{item.title}</p>
</LocaleLink>
</li>
))}
<li
key="logout"

View File

@ -59,7 +59,7 @@ export function CheckoutButton({
});
// Redirect to checkout page
if (result && result.data?.success && result.data.data?.url) {
if (result?.data?.success && result.data.data?.url) {
window.location.href = result.data.data?.url;
} else {
console.error('Create checkout session error, result:', result);

View File

@ -54,7 +54,7 @@ export function CustomerPortalButton({
});
// Redirect to customer portal
if (result && result.data?.success && result.data.data?.url) {
if (result?.data?.success && result.data.data?.url) {
window.location.href = result.data.data?.url;
} else {
console.error('Create customer portal error, result:', result);

View File

@ -71,7 +71,7 @@ export function NewsletterFormCard({ className }: NewsletterFormCardProps) {
email: currentUser.email,
});
if (statusResult && statusResult.data?.success) {
if (statusResult?.data?.success) {
const isCurrentlySubscribed = statusResult.data.subscribed;
setIsSubscriptionChecked(isCurrentlySubscribed);
form.setValue('subscribed', isCurrentlySubscribed);
@ -122,7 +122,7 @@ export function NewsletterFormCard({ className }: NewsletterFormCardProps) {
email: currentUser.email,
});
if (subscribeResult && subscribeResult.data?.success) {
if (subscribeResult?.data?.success) {
toast.success(t('newsletter.subscribeSuccess'));
setIsSubscriptionChecked(true);
form.setValue('subscribed', true);
@ -140,7 +140,7 @@ export function NewsletterFormCard({ className }: NewsletterFormCardProps) {
email: currentUser.email,
});
if (unsubscribeResult && unsubscribeResult.data?.success) {
if (unsubscribeResult?.data?.success) {
toast.success(t('newsletter.unsubscribeSuccess'));
setIsSubscriptionChecked(false);
form.setValue('subscribed', false);

View File

@ -60,11 +60,10 @@ export async function sendEmail(
// This is a template email
const result = await provider.sendTemplate(params);
return result.success;
} else {
// This is a raw email
const result = await provider.sendRawEmail(params);
return result.success;
}
// This is a raw email
const result = await provider.sendRawEmail(params);
return result.success;
}
/**

View File

@ -71,10 +71,9 @@ export class ResendNewsletterProvider implements NewsletterProvider {
if (createResult.error) {
console.error('Error creating contact', createResult.error);
return false;
} else {
console.log('Created new contact', email);
return true;
}
console.log('Created new contact', email);
return true;
}
// If the contact already exists, update it

View File

@ -1,4 +1,4 @@
import { randomUUID } from 'crypto';
import { randomUUID } from 'node:crypto';
import db from '@/db';
import { payment, session, user } from '@/db/schema';
import {
@ -148,9 +148,8 @@ export class StripeProvider implements PaymentProvider {
if (result.length > 0) {
return result[0].id;
} else {
console.warn(`No user found with customerId ${customerId}`);
}
console.warn(`No user found with customerId ${customerId}`);
return undefined;
} catch (error) {
@ -611,11 +610,10 @@ export class StripeProvider implements PaymentProvider {
`<< Failed to create one-time payment record for user ${userId}`
);
return;
} else {
console.log(
`<< Created one-time payment record for user ${userId}, price: ${priceId}`
);
}
console.log(
`<< Created one-time payment record for user ${userId}, price: ${priceId}`
);
}
/**

View File

@ -132,56 +132,55 @@ export const uploadFileFromBrowser = async (
return await response.json();
}
// For larger files, use pre-signed URL
else {
// First, get a pre-signed URL
const presignedUrlResponse = await fetch(API_STORAGE_PRESIGNED_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
filename: file.name,
contentType: file.type,
folder: folder || '',
}),
});
if (!presignedUrlResponse.ok) {
const error = await presignedUrlResponse.json();
throw new Error(error.message || 'Failed to get pre-signed URL');
}
// First, get a pre-signed URL
const presignedUrlResponse = await fetch(API_STORAGE_PRESIGNED_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
filename: file.name,
contentType: file.type,
folder: folder || '',
}),
});
const { url, key } = await presignedUrlResponse.json();
// Then upload directly to the storage provider
const uploadResponse = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': file.type,
},
body: file,
});
if (!uploadResponse.ok) {
throw new Error('Failed to upload file using pre-signed URL');
}
// Get the public URL
const fileUrlResponse = await fetch(API_STORAGE_FILE_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ key }),
});
if (!fileUrlResponse.ok) {
const error = await fileUrlResponse.json();
throw new Error(error.message || 'Failed to get file URL');
}
return await fileUrlResponse.json();
if (!presignedUrlResponse.ok) {
const error = await presignedUrlResponse.json();
throw new Error(error.message || 'Failed to get pre-signed URL');
}
const { url, key } = await presignedUrlResponse.json();
// Then upload directly to the storage provider
const uploadResponse = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': file.type,
},
body: file,
});
if (!uploadResponse.ok) {
throw new Error('Failed to upload file using pre-signed URL');
}
// Get the public URL
const fileUrlResponse = await fetch(API_STORAGE_FILE_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ key }),
});
if (!fileUrlResponse.ok) {
const error = await fileUrlResponse.json();
throw new Error(error.message || 'Failed to get file URL');
}
return await fileUrlResponse.json();
} catch (error) {
const message =
error instanceof Error

View File

@ -1,4 +1,4 @@
import { randomUUID } from 'crypto';
import { randomUUID } from 'node:crypto';
import {
GetObjectCommand,
PutObjectCommand,