refator: biome lint part 2
This commit is contained in:
parent
23cd59bbac
commit
27bc59354f
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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({
|
||||
|
@ -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",
|
||||
|
@ -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';
|
||||
|
@ -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">
|
||||
|
@ -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 />
|
||||
|
@ -127,7 +127,7 @@ export default function TestimonialsSection() {
|
||||
width="120"
|
||||
height="120"
|
||||
/>
|
||||
<AvatarFallback></AvatarFallback>
|
||||
<AvatarFallback />
|
||||
</Avatar>
|
||||
|
||||
<div>
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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 */}
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import {
|
||||
GetObjectCommand,
|
||||
PutObjectCommand,
|
||||
|
Loading…
Reference in New Issue
Block a user