refator: biome lint part 2
This commit is contained in:
parent
23cd59bbac
commit
27bc59354f
@ -22,6 +22,8 @@
|
|||||||
"src/components/magicui/*.tsx",
|
"src/components/magicui/*.tsx",
|
||||||
"src/app/[[]locale]/preview/**",
|
"src/app/[[]locale]/preview/**",
|
||||||
"src/db/schema.ts",
|
"src/db/schema.ts",
|
||||||
|
"src/payment/types.ts",
|
||||||
|
"src/types/index.d.ts",
|
||||||
"public/sw.js"
|
"public/sw.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -78,4 +80,4 @@
|
|||||||
"semicolons": "always"
|
"semicolons": "always"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import path from 'path';
|
import path from 'node:path';
|
||||||
import { DEFAULT_LOCALE, LOCALES } from '@/i18n/routing';
|
import { DEFAULT_LOCALE, LOCALES } from '@/i18n/routing';
|
||||||
import { defineCollection, defineConfig } from '@content-collections/core';
|
import { defineCollection, defineConfig } from '@content-collections/core';
|
||||||
import {
|
import {
|
||||||
@ -313,14 +313,14 @@ function extractLocaleAndBase(fileName: string): {
|
|||||||
if (parts.length === 1) {
|
if (parts.length === 1) {
|
||||||
// Simple filename without locale: xxx
|
// Simple filename without locale: xxx
|
||||||
return { locale: DEFAULT_LOCALE, base: parts[0] };
|
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
|
// Filename with locale: xxx.zh
|
||||||
return { locale: parts[1], base: parts[0] };
|
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({
|
export default defineConfig({
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
"build": "content-collections build && next build",
|
"build": "content-collections build && next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "biome check --write .",
|
"lint": "biome check --write .",
|
||||||
|
"lint:fix": "biome check --fix --unsafe .",
|
||||||
"format": "biome format --write .",
|
"format": "biome format --write .",
|
||||||
"db:generate": "drizzle-kit generate",
|
"db:generate": "drizzle-kit generate",
|
||||||
"db:migrate": "drizzle-kit migrate",
|
"db:migrate": "drizzle-kit migrate",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import { getPresignedUploadUrl } from '@/storage';
|
import { getPresignedUploadUrl } from '@/storage';
|
||||||
import { StorageError } from '@/storage/types';
|
import { StorageError } from '@/storage/types';
|
||||||
import { type NextRequest, NextResponse } from 'next/server';
|
import { type NextRequest, NextResponse } from 'next/server';
|
||||||
|
@ -48,7 +48,7 @@ export default function Features2Section() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="py-16">
|
<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="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">
|
<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">
|
<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="mx-auto max-w-5xl px-6">
|
||||||
<div className="grid items-center sm:grid-cols-2">
|
<div className="grid items-center sm:grid-cols-2">
|
||||||
<div className="dark:bg-muted/50 relative mx-auto w-fit">
|
<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">
|
<div className="mx-auto mb-2 flex w-fit justify-center gap-2">
|
||||||
<IntegrationCard>
|
<IntegrationCard>
|
||||||
<Gemini />
|
<Gemini />
|
||||||
|
@ -127,7 +127,7 @@ export default function TestimonialsSection() {
|
|||||||
width="120"
|
width="120"
|
||||||
height="120"
|
height="120"
|
||||||
/>
|
/>
|
||||||
<AvatarFallback></AvatarFallback>
|
<AvatarFallback />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -69,7 +69,7 @@ export function ContactFormCard() {
|
|||||||
// Submit form data using the contact server action
|
// Submit form data using the contact server action
|
||||||
const result = await sendMessageAction(values);
|
const result = await sendMessageAction(values);
|
||||||
|
|
||||||
if (result && result.data?.success) {
|
if (result?.data?.success) {
|
||||||
toast.success(t('success'));
|
toast.success(t('success'));
|
||||||
form.reset();
|
form.reset();
|
||||||
} else {
|
} else {
|
||||||
|
@ -607,16 +607,16 @@ export function DataTable({
|
|||||||
value="past-performance"
|
value="past-performance"
|
||||||
className="flex flex-col px-4 lg:px-6"
|
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>
|
||||||
<TabsContent value="key-personnel" className="flex flex-col px-4 lg:px-6">
|
<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>
|
||||||
<TabsContent
|
<TabsContent
|
||||||
value="focus-documents"
|
value="focus-documents"
|
||||||
className="flex flex-col px-4 lg:px-6"
|
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>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
);
|
);
|
||||||
|
@ -16,7 +16,7 @@ export function DiscordIcon(props: SVGProps<SVGSVGElement>) {
|
|||||||
<path
|
<path
|
||||||
fill="currentColor"
|
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"
|
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>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ export function FacebookIcon(props: SVGProps<SVGSVGElement>) {
|
|||||||
<path
|
<path
|
||||||
fill="currentColor"
|
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"
|
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>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ export function InstagramIcon(props: SVGProps<SVGSVGElement>) {
|
|||||||
<path
|
<path
|
||||||
fill="currentColor"
|
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"
|
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>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ export function LinkedInIcon(props: SVGProps<SVGSVGElement>) {
|
|||||||
<path
|
<path
|
||||||
fill="currentColor"
|
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"
|
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>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ export function TikTokIcon(props: SVGProps<SVGSVGElement>) {
|
|||||||
<path
|
<path
|
||||||
fill="currentColor"
|
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"
|
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>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ export function TwitterIcon(props: SVGProps<SVGSVGElement>) {
|
|||||||
<path
|
<path
|
||||||
fill="currentColor"
|
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"
|
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>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -39,21 +39,20 @@ export function Footer({ className }: React.HTMLAttributes<HTMLElement>) {
|
|||||||
{/* social links */}
|
{/* social links */}
|
||||||
<div className="flex items-center gap-4 py-2">
|
<div className="flex items-center gap-4 py-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{socialLinks &&
|
{socialLinks?.map((link) => (
|
||||||
socialLinks.map((link) => (
|
<a
|
||||||
<a
|
key={link.title}
|
||||||
key={link.title}
|
href={link.href || '#'}
|
||||||
href={link.href || '#'}
|
target="_blank"
|
||||||
target="_blank"
|
rel="noreferrer"
|
||||||
rel="noreferrer"
|
aria-label={link.title}
|
||||||
aria-label={link.title}
|
className="border border-border inline-flex h-8 w-8 items-center
|
||||||
className="border border-border inline-flex h-8 w-8 items-center
|
|
||||||
justify-center rounded-full hover:bg-accent hover:text-accent-foreground"
|
justify-center rounded-full hover:bg-accent hover:text-accent-foreground"
|
||||||
>
|
>
|
||||||
<span className="sr-only">{link.title}</span>
|
<span className="sr-only">{link.title}</span>
|
||||||
{link.icon ? link.icon : null}
|
{link.icon ? link.icon : null}
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -63,33 +62,32 @@ export function Footer({ className }: React.HTMLAttributes<HTMLElement>) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* footer links */}
|
{/* footer links */}
|
||||||
{footerLinks &&
|
{footerLinks?.map((section) => (
|
||||||
footerLinks.map((section) => (
|
<div
|
||||||
<div
|
key={section.title}
|
||||||
key={section.title}
|
className="col-span-1 md:col-span-1 items-start"
|
||||||
className="col-span-1 md:col-span-1 items-start"
|
>
|
||||||
>
|
<span className="text-sm font-semibold uppercase">
|
||||||
<span className="text-sm font-semibold uppercase">
|
{section.title}
|
||||||
{section.title}
|
</span>
|
||||||
</span>
|
<ul className="mt-4 list-inside space-y-3">
|
||||||
<ul className="mt-4 list-inside space-y-3">
|
{section.items?.map(
|
||||||
{section.items?.map(
|
(item) =>
|
||||||
(item) =>
|
item.href && (
|
||||||
item.href && (
|
<li key={item.title}>
|
||||||
<li key={item.title}>
|
<LocaleLink
|
||||||
<LocaleLink
|
href={item.href || '#'}
|
||||||
href={item.href || '#'}
|
target={item.external ? '_blank' : undefined}
|
||||||
target={item.external ? '_blank' : undefined}
|
className="text-sm text-muted-foreground hover:text-primary"
|
||||||
className="text-sm text-muted-foreground hover:text-primary"
|
>
|
||||||
>
|
{item.title}
|
||||||
{item.title}
|
</LocaleLink>
|
||||||
</LocaleLink>
|
</li>
|
||||||
</li>
|
)
|
||||||
)
|
)}
|
||||||
)}
|
</ul>
|
||||||
</ul>
|
</div>
|
||||||
</div>
|
))}
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
|
@ -177,104 +177,101 @@ function MainMobileMenu({ userLoggedIn, onLinkClicked }: MainMobileMenuProps) {
|
|||||||
|
|
||||||
{/* main menu */}
|
{/* main menu */}
|
||||||
<ul className="w-full px-4">
|
<ul className="w-full px-4">
|
||||||
{menuLinks &&
|
{menuLinks?.map((item) => {
|
||||||
menuLinks.map((item) => {
|
const isActive = item.href
|
||||||
const isActive = item.href
|
? localePathname.startsWith(item.href)
|
||||||
? localePathname.startsWith(item.href)
|
: item.items?.some(
|
||||||
: item.items?.some(
|
(subItem) =>
|
||||||
(subItem) =>
|
subItem.href && localePathname.startsWith(subItem.href)
|
||||||
subItem.href && localePathname.startsWith(subItem.href)
|
);
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={item.title} className="py-1">
|
<li key={item.title} className="py-1">
|
||||||
{item.items ? (
|
{item.items ? (
|
||||||
<Collapsible
|
<Collapsible
|
||||||
open={expanded[item.title.toLowerCase()]}
|
open={expanded[item.title.toLowerCase()]}
|
||||||
onOpenChange={(isOpen) =>
|
onOpenChange={(isOpen) =>
|
||||||
setExpanded((prev) => ({
|
setExpanded((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[item.title.toLowerCase()]: isOpen,
|
[item.title.toLowerCase()]: isOpen,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex w-full !pl-2 items-center justify-between text-left',
|
'flex w-full !pl-2 items-center justify-between text-left',
|
||||||
'bg-transparent text-muted-foreground cursor-pointer',
|
'bg-transparent text-muted-foreground cursor-pointer',
|
||||||
'hover:bg-transparent hover:text-foreground',
|
'hover:bg-transparent hover:text-foreground',
|
||||||
'focus:bg-transparent focus:text-foreground',
|
'focus:bg-transparent focus:text-foreground',
|
||||||
isActive &&
|
isActive &&
|
||||||
'font-semibold bg-transparent text-foreground'
|
'font-semibold bg-transparent text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className="text-base">{item.title}</span>
|
<span className="text-base">{item.title}</span>
|
||||||
{expanded[item.title.toLowerCase()] ? (
|
{expanded[item.title.toLowerCase()] ? (
|
||||||
<ChevronDownIcon className="size-4" />
|
<ChevronDownIcon className="size-4" />
|
||||||
) : (
|
) : (
|
||||||
<ChevronRightIcon className="size-4" />
|
<ChevronRightIcon className="size-4" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent className="pl-2">
|
<CollapsibleContent className="pl-2">
|
||||||
<ul className="mt-2 space-y-2 pl-0">
|
<ul className="mt-2 space-y-2 pl-0">
|
||||||
{item.items.map((subItem) => {
|
{item.items.map((subItem) => {
|
||||||
const isSubItemActive =
|
const isSubItemActive =
|
||||||
subItem.href &&
|
subItem.href &&
|
||||||
localePathname.startsWith(subItem.href);
|
localePathname.startsWith(subItem.href);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={subItem.title}>
|
<li key={subItem.title}>
|
||||||
<LocaleLink
|
<LocaleLink
|
||||||
href={subItem.href || '#'}
|
href={subItem.href || '#'}
|
||||||
target={
|
target={subItem.external ? '_blank' : undefined}
|
||||||
subItem.external ? '_blank' : undefined
|
rel={
|
||||||
}
|
subItem.external
|
||||||
rel={
|
? 'noopener noreferrer'
|
||||||
subItem.external
|
: undefined
|
||||||
? '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(
|
className={cn(
|
||||||
buttonVariants({ variant: 'ghost' }),
|
'flex size-8 shrink-0 items-center justify-center transition-colors ml-0',
|
||||||
'group h-auto w-full justify-start gap-4 p-1 !pl-0 !pr-3',
|
'bg-transparent text-muted-foreground',
|
||||||
'bg-transparent text-muted-foreground cursor-pointer',
|
'group-hover:bg-transparent group-hover:text-foreground',
|
||||||
'hover:bg-transparent hover:text-foreground',
|
'group-focus:bg-transparent group-focus:text-foreground',
|
||||||
'focus:bg-transparent focus:text-foreground',
|
|
||||||
isSubItemActive &&
|
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(
|
className={cn(
|
||||||
'flex size-8 shrink-0 items-center justify-center transition-colors ml-0',
|
'text-sm text-muted-foreground',
|
||||||
'bg-transparent text-muted-foreground',
|
|
||||||
'group-hover:bg-transparent group-hover:text-foreground',
|
'group-hover:bg-transparent group-hover:text-foreground',
|
||||||
'group-focus:bg-transparent group-focus:text-foreground',
|
'group-focus:bg-transparent group-focus:text-foreground',
|
||||||
isSubItemActive &&
|
isSubItemActive &&
|
||||||
'bg-transparent text-foreground'
|
'font-semibold bg-transparent text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{subItem.icon ? subItem.icon : null}
|
{subItem.title}
|
||||||
</div>
|
</span>
|
||||||
<div className="flex-1">
|
{/* hide description for now */}
|
||||||
<span
|
{/* {subItem.description && (
|
||||||
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 && (
|
|
||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-xs text-muted-foreground',
|
'text-xs text-muted-foreground',
|
||||||
@ -287,49 +284,48 @@ function MainMobileMenu({ userLoggedIn, onLinkClicked }: MainMobileMenuProps) {
|
|||||||
{subItem.description}
|
{subItem.description}
|
||||||
</p>
|
</p>
|
||||||
)} */}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
{subItem.external && (
|
{subItem.external && (
|
||||||
<ArrowUpRightIcon
|
<ArrowUpRightIcon
|
||||||
className={cn(
|
className={cn(
|
||||||
'size-4 shrink-0 text-muted-foreground items-center',
|
'size-4 shrink-0 text-muted-foreground items-center',
|
||||||
'group-hover:bg-transparent group-hover:text-foreground',
|
'group-hover:bg-transparent group-hover:text-foreground',
|
||||||
'group-focus:bg-transparent group-focus:text-foreground',
|
'group-focus:bg-transparent group-focus:text-foreground',
|
||||||
isSubItemActive &&
|
isSubItemActive &&
|
||||||
'bg-transparent text-foreground'
|
'bg-transparent text-foreground'
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</LocaleLink>
|
</LocaleLink>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
) : (
|
) : (
|
||||||
<LocaleLink
|
<LocaleLink
|
||||||
href={item.href || '#'}
|
href={item.href || '#'}
|
||||||
target={item.external ? '_blank' : undefined}
|
target={item.external ? '_blank' : undefined}
|
||||||
rel={item.external ? 'noopener noreferrer' : undefined}
|
rel={item.external ? 'noopener noreferrer' : undefined}
|
||||||
className={cn(
|
className={cn(
|
||||||
buttonVariants({ variant: 'ghost' }),
|
buttonVariants({ variant: 'ghost' }),
|
||||||
'w-full !pl-2 justify-start cursor-pointer group',
|
'w-full !pl-2 justify-start cursor-pointer group',
|
||||||
'bg-transparent text-muted-foreground',
|
'bg-transparent text-muted-foreground',
|
||||||
'hover:bg-transparent hover:text-foreground',
|
'hover:bg-transparent hover:text-foreground',
|
||||||
'focus:bg-transparent focus:text-foreground',
|
'focus:bg-transparent focus:text-foreground',
|
||||||
isActive &&
|
isActive && 'font-semibold bg-transparent text-foreground'
|
||||||
'font-semibold bg-transparent text-foreground'
|
)}
|
||||||
)}
|
onClick={onLinkClicked}
|
||||||
onClick={onLinkClicked}
|
>
|
||||||
>
|
<div className="flex items-center w-full pl-0">
|
||||||
<div className="flex items-center w-full pl-0">
|
<span className="text-base">{item.title}</span>
|
||||||
<span className="text-base">{item.title}</span>
|
</div>
|
||||||
</div>
|
</LocaleLink>
|
||||||
</LocaleLink>
|
)}
|
||||||
)}
|
</li>
|
||||||
</li>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{/* bottom buttons */}
|
{/* bottom buttons */}
|
||||||
|
@ -78,136 +78,132 @@ export function Navbar({ scroll }: NavBarProps) {
|
|||||||
<div className="flex-1 flex items-center justify-center space-x-2">
|
<div className="flex-1 flex items-center justify-center space-x-2">
|
||||||
<NavigationMenu className="relative">
|
<NavigationMenu className="relative">
|
||||||
<NavigationMenuList className="flex items-center">
|
<NavigationMenuList className="flex items-center">
|
||||||
{menuLinks &&
|
{menuLinks?.map((item, index) =>
|
||||||
menuLinks.map((item, index) =>
|
item.items ? (
|
||||||
item.items ? (
|
<NavigationMenuItem key={index} className="relative">
|
||||||
<NavigationMenuItem key={index} className="relative">
|
<NavigationMenuTrigger
|
||||||
<NavigationMenuTrigger
|
data-active={
|
||||||
data-active={
|
item.items.some((subItem) =>
|
||||||
item.items.some((subItem) =>
|
subItem.href
|
||||||
subItem.href
|
? localePathname.startsWith(subItem.href)
|
||||||
? localePathname.startsWith(subItem.href)
|
: false
|
||||||
: false
|
)
|
||||||
)
|
? 'true'
|
||||||
? 'true'
|
: undefined
|
||||||
: undefined
|
}
|
||||||
}
|
className={customNavigationMenuTriggerStyle}
|
||||||
className={customNavigationMenuTriggerStyle}
|
>
|
||||||
>
|
{item.title}
|
||||||
{item.title}
|
</NavigationMenuTrigger>
|
||||||
</NavigationMenuTrigger>
|
<NavigationMenuContent>
|
||||||
<NavigationMenuContent>
|
<ul className="grid w-[400px] gap-4 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px]">
|
||||||
<ul className="grid w-[400px] gap-4 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px]">
|
{item.items?.map((subItem, subIndex) => {
|
||||||
{item.items &&
|
const isSubItemActive =
|
||||||
item.items.map((subItem, subIndex) => {
|
subItem.href &&
|
||||||
const isSubItemActive =
|
localePathname.startsWith(subItem.href);
|
||||||
subItem.href &&
|
return (
|
||||||
localePathname.startsWith(subItem.href);
|
<li key={subIndex}>
|
||||||
return (
|
<NavigationMenuLink asChild>
|
||||||
<li key={subIndex}>
|
<LocaleLink
|
||||||
<NavigationMenuLink asChild>
|
href={subItem.href || '#'}
|
||||||
<LocaleLink
|
target={
|
||||||
href={subItem.href || '#'}
|
subItem.external ? '_blank' : undefined
|
||||||
target={
|
}
|
||||||
subItem.external
|
rel={
|
||||||
? '_blank'
|
subItem.external
|
||||||
: undefined
|
? 'noopener noreferrer'
|
||||||
}
|
: undefined
|
||||||
rel={
|
}
|
||||||
subItem.external
|
className={cn(
|
||||||
? 'noopener noreferrer'
|
'group flex select-none flex-row items-center gap-4 rounded-md',
|
||||||
: undefined
|
'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(
|
className={cn(
|
||||||
'group flex select-none flex-row items-center gap-4 rounded-md',
|
'text-sm font-medium text-muted-foreground',
|
||||||
'p-2 leading-none no-underline outline-hidden transition-colors',
|
'group-hover:bg-transparent group-hover:text-foreground',
|
||||||
'hover:bg-accent hover:text-accent-foreground',
|
'group-focus:bg-transparent group-focus:text-foreground',
|
||||||
'focus:bg-accent focus:text-accent-foreground',
|
|
||||||
isSubItemActive &&
|
isSubItemActive &&
|
||||||
'bg-accent text-accent-foreground'
|
'bg-transparent text-foreground'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
{subItem.title}
|
||||||
|
</div>
|
||||||
|
{subItem.description && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex size-8 shrink-0 items-center justify-center transition-colors',
|
'text-sm text-muted-foreground',
|
||||||
'bg-transparent text-muted-foreground',
|
'group-hover:bg-transparent group-hover:text-foreground/80',
|
||||||
'group-hover:bg-transparent group-hover:text-foreground',
|
'group-focus:bg-transparent group-focus:text-foreground/80',
|
||||||
'group-focus:bg-transparent group-focus:text-foreground',
|
|
||||||
isSubItemActive &&
|
isSubItemActive &&
|
||||||
'bg-transparent text-foreground'
|
'bg-transparent text-foreground/80'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{subItem.icon ? subItem.icon : null}
|
{subItem.description}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
)}
|
||||||
<div
|
</div>
|
||||||
className={cn(
|
{subItem.external && (
|
||||||
'text-sm font-medium text-muted-foreground',
|
<ArrowUpRightIcon
|
||||||
'group-hover:bg-transparent group-hover:text-foreground',
|
className={cn(
|
||||||
'group-focus:bg-transparent group-focus:text-foreground',
|
'size-4 shrink-0 text-muted-foreground',
|
||||||
isSubItemActive &&
|
'group-hover:bg-transparent group-hover:text-foreground',
|
||||||
'bg-transparent 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'
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</LocaleLink>
|
/>
|
||||||
</NavigationMenuLink>
|
)}
|
||||||
</li>
|
</LocaleLink>
|
||||||
);
|
</NavigationMenuLink>
|
||||||
})}
|
</li>
|
||||||
</ul>
|
);
|
||||||
</NavigationMenuContent>
|
})}
|
||||||
</NavigationMenuItem>
|
</ul>
|
||||||
) : (
|
</NavigationMenuContent>
|
||||||
<NavigationMenuItem key={index}>
|
</NavigationMenuItem>
|
||||||
<NavigationMenuLink
|
) : (
|
||||||
asChild
|
<NavigationMenuItem key={index}>
|
||||||
active={
|
<NavigationMenuLink
|
||||||
item.href
|
asChild
|
||||||
? localePathname.startsWith(item.href)
|
active={
|
||||||
: false
|
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
|
{item.title}
|
||||||
href={item.href || '#'}
|
</LocaleLink>
|
||||||
target={item.external ? '_blank' : undefined}
|
</NavigationMenuLink>
|
||||||
rel={
|
</NavigationMenuItem>
|
||||||
item.external ? 'noopener noreferrer' : undefined
|
)
|
||||||
}
|
)}
|
||||||
>
|
|
||||||
{item.title}
|
|
||||||
</LocaleLink>
|
|
||||||
</NavigationMenuLink>
|
|
||||||
</NavigationMenuItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</NavigationMenuList>
|
</NavigationMenuList>
|
||||||
</NavigationMenu>
|
</NavigationMenu>
|
||||||
</div>
|
</div>
|
||||||
|
@ -85,22 +85,21 @@ export function UserButtonMobile({ user }: UserButtonProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul className="mb-14 mt-1 w-full text-muted-foreground">
|
<ul className="mb-14 mt-1 w-full text-muted-foreground">
|
||||||
{avatarLinks &&
|
{avatarLinks?.map((item) => (
|
||||||
avatarLinks.map((item) => (
|
<li
|
||||||
<li
|
key={item.title}
|
||||||
key={item.title}
|
className="rounded-lg text-foreground hover:bg-muted"
|
||||||
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
|
{item.icon ? item.icon : null}
|
||||||
href={item.href || '#'}
|
<p className="text-sm">{item.title}</p>
|
||||||
onClick={closeDrawer}
|
</LocaleLink>
|
||||||
className="flex w-full items-center gap-3 px-2.5 py-2"
|
</li>
|
||||||
>
|
))}
|
||||||
{item.icon ? item.icon : null}
|
|
||||||
<p className="text-sm">{item.title}</p>
|
|
||||||
</LocaleLink>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<li
|
<li
|
||||||
key="logout"
|
key="logout"
|
||||||
|
@ -59,7 +59,7 @@ export function CheckoutButton({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Redirect to checkout page
|
// 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;
|
window.location.href = result.data.data?.url;
|
||||||
} else {
|
} else {
|
||||||
console.error('Create checkout session error, result:', result);
|
console.error('Create checkout session error, result:', result);
|
||||||
|
@ -54,7 +54,7 @@ export function CustomerPortalButton({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Redirect to customer portal
|
// 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;
|
window.location.href = result.data.data?.url;
|
||||||
} else {
|
} else {
|
||||||
console.error('Create customer portal error, result:', result);
|
console.error('Create customer portal error, result:', result);
|
||||||
|
@ -71,7 +71,7 @@ export function NewsletterFormCard({ className }: NewsletterFormCardProps) {
|
|||||||
email: currentUser.email,
|
email: currentUser.email,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (statusResult && statusResult.data?.success) {
|
if (statusResult?.data?.success) {
|
||||||
const isCurrentlySubscribed = statusResult.data.subscribed;
|
const isCurrentlySubscribed = statusResult.data.subscribed;
|
||||||
setIsSubscriptionChecked(isCurrentlySubscribed);
|
setIsSubscriptionChecked(isCurrentlySubscribed);
|
||||||
form.setValue('subscribed', isCurrentlySubscribed);
|
form.setValue('subscribed', isCurrentlySubscribed);
|
||||||
@ -122,7 +122,7 @@ export function NewsletterFormCard({ className }: NewsletterFormCardProps) {
|
|||||||
email: currentUser.email,
|
email: currentUser.email,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (subscribeResult && subscribeResult.data?.success) {
|
if (subscribeResult?.data?.success) {
|
||||||
toast.success(t('newsletter.subscribeSuccess'));
|
toast.success(t('newsletter.subscribeSuccess'));
|
||||||
setIsSubscriptionChecked(true);
|
setIsSubscriptionChecked(true);
|
||||||
form.setValue('subscribed', true);
|
form.setValue('subscribed', true);
|
||||||
@ -140,7 +140,7 @@ export function NewsletterFormCard({ className }: NewsletterFormCardProps) {
|
|||||||
email: currentUser.email,
|
email: currentUser.email,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (unsubscribeResult && unsubscribeResult.data?.success) {
|
if (unsubscribeResult?.data?.success) {
|
||||||
toast.success(t('newsletter.unsubscribeSuccess'));
|
toast.success(t('newsletter.unsubscribeSuccess'));
|
||||||
setIsSubscriptionChecked(false);
|
setIsSubscriptionChecked(false);
|
||||||
form.setValue('subscribed', false);
|
form.setValue('subscribed', false);
|
||||||
|
@ -60,11 +60,10 @@ export async function sendEmail(
|
|||||||
// This is a template email
|
// This is a template email
|
||||||
const result = await provider.sendTemplate(params);
|
const result = await provider.sendTemplate(params);
|
||||||
return result.success;
|
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) {
|
if (createResult.error) {
|
||||||
console.error('Error creating contact', createResult.error);
|
console.error('Error creating contact', createResult.error);
|
||||||
return false;
|
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
|
// 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 db from '@/db';
|
||||||
import { payment, session, user } from '@/db/schema';
|
import { payment, session, user } from '@/db/schema';
|
||||||
import {
|
import {
|
||||||
@ -148,9 +148,8 @@ export class StripeProvider implements PaymentProvider {
|
|||||||
|
|
||||||
if (result.length > 0) {
|
if (result.length > 0) {
|
||||||
return result[0].id;
|
return result[0].id;
|
||||||
} else {
|
|
||||||
console.warn(`No user found with customerId ${customerId}`);
|
|
||||||
}
|
}
|
||||||
|
console.warn(`No user found with customerId ${customerId}`);
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -611,11 +610,10 @@ export class StripeProvider implements PaymentProvider {
|
|||||||
`<< Failed to create one-time payment record for user ${userId}`
|
`<< Failed to create one-time payment record for user ${userId}`
|
||||||
);
|
);
|
||||||
return;
|
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();
|
return await response.json();
|
||||||
}
|
}
|
||||||
// For larger files, use pre-signed URL
|
// 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) {
|
// First, get a pre-signed URL
|
||||||
const error = await presignedUrlResponse.json();
|
const presignedUrlResponse = await fetch(API_STORAGE_PRESIGNED_URL, {
|
||||||
throw new Error(error.message || 'Failed to get pre-signed 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();
|
if (!presignedUrlResponse.ok) {
|
||||||
|
const error = await presignedUrlResponse.json();
|
||||||
// Then upload directly to the storage provider
|
throw new Error(error.message || 'Failed to get pre-signed URL');
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
} catch (error) {
|
||||||
const message =
|
const message =
|
||||||
error instanceof Error
|
error instanceof Error
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import {
|
import {
|
||||||
GetObjectCommand,
|
GetObjectCommand,
|
||||||
PutObjectCommand,
|
PutObjectCommand,
|
||||||
|
Loading…
Reference in New Issue
Block a user