refactor: biome lint part 1
This commit is contained in:
parent
5b02b0379f
commit
23cd59bbac
@ -66,6 +66,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"
|
||||
]
|
||||
},
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { DEFAULT_LOCALE, LOCALES } from "@/i18n/routing";
|
||||
import { defineCollection, defineConfig } from "@content-collections/core";
|
||||
import path from 'path';
|
||||
import { DEFAULT_LOCALE, LOCALES } from '@/i18n/routing';
|
||||
import { defineCollection, defineConfig } from '@content-collections/core';
|
||||
import {
|
||||
createDocSchema,
|
||||
createMetaSchema,
|
||||
transformMDX,
|
||||
} from '@fumadocs/content-collections/configuration';
|
||||
import path from "path";
|
||||
|
||||
/**
|
||||
* 1. Content Collections documentation
|
||||
@ -53,7 +53,7 @@ export const authors = defineCollection({
|
||||
slug: z.string(),
|
||||
name: z.string(),
|
||||
avatar: z.string(),
|
||||
locale: z.string().optional().default(DEFAULT_LOCALE)
|
||||
locale: z.string().optional().default(DEFAULT_LOCALE),
|
||||
}),
|
||||
transform: async (data, context) => {
|
||||
// Get the filename from the path
|
||||
@ -68,7 +68,7 @@ export const authors = defineCollection({
|
||||
...data,
|
||||
locale,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
@ -88,7 +88,7 @@ export const categories = defineCollection({
|
||||
slug: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
locale: z.string().optional().default(DEFAULT_LOCALE)
|
||||
locale: z.string().optional().default(DEFAULT_LOCALE),
|
||||
}),
|
||||
transform: async (data, context) => {
|
||||
// Get the filename from the path
|
||||
@ -101,9 +101,9 @@ export const categories = defineCollection({
|
||||
|
||||
return {
|
||||
...data,
|
||||
locale
|
||||
locale,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
@ -136,7 +136,7 @@ export const posts = defineCollection({
|
||||
published: z.boolean().default(true),
|
||||
categories: z.array(z.string()),
|
||||
author: z.string(),
|
||||
estimatedTime: z.number().optional() // Reading time in minutes
|
||||
estimatedTime: z.number().optional(), // Reading time in minutes
|
||||
}),
|
||||
transform: async (data, context) => {
|
||||
// Use Fumadocs transformMDX for consistent MDX processing
|
||||
@ -156,13 +156,15 @@ export const posts = defineCollection({
|
||||
.find((a) => a.slug === data.author && a.locale === locale);
|
||||
|
||||
// Find categories by matching slug and locale
|
||||
const blogCategories = data.categories.map(categorySlug => {
|
||||
const blogCategories = data.categories
|
||||
.map((categorySlug) => {
|
||||
const category = context
|
||||
.documents(categories)
|
||||
.find(c => c.slug === categorySlug && c.locale === locale);
|
||||
.find((c) => c.slug === categorySlug && c.locale === locale);
|
||||
|
||||
return category;
|
||||
}).filter(Boolean); // Remove null values
|
||||
})
|
||||
.filter(Boolean); // Remove null values
|
||||
|
||||
// Create the slug and slugAsParams
|
||||
const slug = `/blog/${base}`;
|
||||
@ -182,9 +184,9 @@ export const posts = defineCollection({
|
||||
slugAsParams,
|
||||
estimatedTime,
|
||||
body: transformedData.body,
|
||||
toc: transformedData.toc
|
||||
toc: transformedData.toc,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
@ -210,7 +212,7 @@ export const pages = defineCollection({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
date: z.string().datetime(),
|
||||
published: z.boolean().default(true)
|
||||
published: z.boolean().default(true),
|
||||
}),
|
||||
transform: async (data, context) => {
|
||||
// Use Fumadocs transformMDX for consistent MDX processing
|
||||
@ -234,9 +236,9 @@ export const pages = defineCollection({
|
||||
slug,
|
||||
slugAsParams,
|
||||
body: transformedData.body,
|
||||
toc: transformedData.toc
|
||||
toc: transformedData.toc,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
@ -263,7 +265,7 @@ export const releases = defineCollection({
|
||||
description: z.string(),
|
||||
date: z.string().datetime(),
|
||||
version: z.string(),
|
||||
published: z.boolean().default(true)
|
||||
published: z.boolean().default(true),
|
||||
}),
|
||||
transform: async (data, context) => {
|
||||
// Use Fumadocs transformMDX for consistent MDX processing
|
||||
@ -287,9 +289,9 @@ export const releases = defineCollection({
|
||||
slug,
|
||||
slugAsParams,
|
||||
body: transformedData.body,
|
||||
toc: transformedData.toc
|
||||
toc: transformedData.toc,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
@ -301,7 +303,10 @@ export const releases = defineCollection({
|
||||
* @param fileName Filename without extension (already has .mdx removed)
|
||||
* @returns Object with locale and base name
|
||||
*/
|
||||
function extractLocaleAndBase(fileName: string): { locale: string; base: string } {
|
||||
function extractLocaleAndBase(fileName: string): {
|
||||
locale: string;
|
||||
base: string;
|
||||
} {
|
||||
// Split filename into parts
|
||||
const parts = fileName.split('.');
|
||||
|
||||
@ -319,5 +324,5 @@ function extractLocaleAndBase(fileName: string): { locale: string; base: string
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
collections: [docs, metas, authors, categories, posts, pages, releases]
|
||||
collections: [docs, metas, authors, categories, posts, pages, releases],
|
||||
});
|
4
global.d.ts
vendored
4
global.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
import { routing } from '@/i18n/routing';
|
||||
import messages from './messages/en.json';
|
||||
import type { routing } from '@/i18n/routing';
|
||||
import type messages from './messages/en.json';
|
||||
|
||||
/**
|
||||
* next-intl 4.0.0
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { NextConfig } from "next";
|
||||
import { withContentCollections } from '@content-collections/next';
|
||||
import type { NextConfig } from 'next';
|
||||
import createNextIntlPlugin from 'next-intl/plugin';
|
||||
import { withContentCollections } from "@content-collections/next";
|
||||
|
||||
/**
|
||||
* https://nextjs.org/docs/app/api-reference/config/next-config-js
|
||||
@ -12,22 +12,22 @@ const nextConfig: NextConfig = {
|
||||
// https://nextjs.org/docs/architecture/nextjs-compiler#remove-console
|
||||
// Remove all console.* calls in production only
|
||||
compiler: {
|
||||
removeConsole: process.env.NODE_ENV === "production",
|
||||
removeConsole: process.env.NODE_ENV === 'production',
|
||||
},
|
||||
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "avatars.githubusercontent.com",
|
||||
protocol: 'https',
|
||||
hostname: 'avatars.githubusercontent.com',
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "lh3.googleusercontent.com",
|
||||
protocol: 'https',
|
||||
hostname: 'lh3.googleusercontent.com',
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "randomuser.me",
|
||||
protocol: 'https',
|
||||
hostname: 'randomuser.me',
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
|
@ -1,14 +1,14 @@
|
||||
'use server';
|
||||
|
||||
import { getSession } from "@/lib/server";
|
||||
import { findPlanByPlanId } from "@/lib/price-plan";
|
||||
import { getUrlWithLocale } from "@/lib/urls/urls";
|
||||
import { createCheckout } from "@/payment";
|
||||
import { CreateCheckoutParams } from "@/payment/types";
|
||||
import { getLocale } from "next-intl/server";
|
||||
import { findPlanByPlanId } from '@/lib/price-plan';
|
||||
import { getSession } from '@/lib/server';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { createCheckout } from '@/payment';
|
||||
import type { CreateCheckoutParams } from '@/payment/types';
|
||||
import { Routes } from '@/routes';
|
||||
import { getLocale } from 'next-intl/server';
|
||||
import { createSafeActionClient } from 'next-safe-action';
|
||||
import { z } from 'zod';
|
||||
import { Routes } from "@/routes";
|
||||
|
||||
// Create a safe action client
|
||||
const actionClient = createSafeActionClient();
|
||||
@ -33,7 +33,9 @@ export const createCheckoutAction = actionClient
|
||||
// Get the current user session for authorization
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
console.warn(`unauthorized request to create checkout session for user ${userId}`);
|
||||
console.warn(
|
||||
`unauthorized request to create checkout session for user ${userId}`
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Unauthorized',
|
||||
@ -42,7 +44,9 @@ export const createCheckoutAction = actionClient
|
||||
|
||||
// Only allow users to create their own checkout session
|
||||
if (session.user.id !== userId) {
|
||||
console.warn(`current user ${session.user.id} is not authorized to create checkout session for user ${userId}`);
|
||||
console.warn(
|
||||
`current user ${session.user.id} is not authorized to create checkout session for user ${userId}`
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Not authorized to do this action',
|
||||
@ -70,7 +74,10 @@ export const createCheckoutAction = actionClient
|
||||
};
|
||||
|
||||
// Create the checkout session with localized URLs
|
||||
const successUrl = getUrlWithLocale('/settings/billing?session_id={CHECKOUT_SESSION_ID}', locale);
|
||||
const successUrl = getUrlWithLocale(
|
||||
'/settings/billing?session_id={CHECKOUT_SESSION_ID}',
|
||||
locale
|
||||
);
|
||||
const cancelUrl = getUrlWithLocale(Routes.Pricing, locale);
|
||||
const params: CreateCheckoutParams = {
|
||||
planId,
|
||||
@ -89,7 +96,7 @@ export const createCheckoutAction = actionClient
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("create checkout session error:", error);
|
||||
console.error('create checkout session error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Something went wrong',
|
||||
|
@ -1,13 +1,13 @@
|
||||
'use server';
|
||||
|
||||
import db from "@/db";
|
||||
import { user } from "@/db/schema";
|
||||
import { getSession } from "@/lib/server";
|
||||
import { getUrlWithLocale } from "@/lib/urls/urls";
|
||||
import { createCustomerPortal } from "@/payment";
|
||||
import { CreatePortalParams } from "@/payment/types";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { getLocale } from "next-intl/server";
|
||||
import db from '@/db';
|
||||
import { user } from '@/db/schema';
|
||||
import { getSession } from '@/lib/server';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { createCustomerPortal } from '@/payment';
|
||||
import type { CreatePortalParams } from '@/payment/types';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { getLocale } from 'next-intl/server';
|
||||
import { createSafeActionClient } from 'next-safe-action';
|
||||
import { z } from 'zod';
|
||||
|
||||
@ -17,7 +17,10 @@ const actionClient = createSafeActionClient();
|
||||
// Portal schema for validation
|
||||
const portalSchema = z.object({
|
||||
userId: z.string().min(1, { message: 'User ID is required' }),
|
||||
returnUrl: z.string().url({ message: 'Return URL must be a valid URL' }).optional(),
|
||||
returnUrl: z
|
||||
.string()
|
||||
.url({ message: 'Return URL must be a valid URL' })
|
||||
.optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
@ -31,7 +34,9 @@ export const createPortalAction = actionClient
|
||||
// Get the current user session for authorization
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
console.warn(`unauthorized request to create portal session for user ${userId}`);
|
||||
console.warn(
|
||||
`unauthorized request to create portal session for user ${userId}`
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Unauthorized',
|
||||
@ -40,7 +45,9 @@ export const createPortalAction = actionClient
|
||||
|
||||
// Only allow users to create their own portal session
|
||||
if (session.user.id !== userId) {
|
||||
console.warn(`current user ${session.user.id} is not authorized to create portal session for user ${userId}`);
|
||||
console.warn(
|
||||
`current user ${session.user.id} is not authorized to create portal session for user ${userId}`
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Not authorized to do this action',
|
||||
@ -67,11 +74,12 @@ export const createPortalAction = actionClient
|
||||
const locale = await getLocale();
|
||||
|
||||
// Create the portal session with localized URL if no custom return URL is provided
|
||||
const returnUrlWithLocale = returnUrl || getUrlWithLocale('/settings/billing', locale);
|
||||
const returnUrlWithLocale =
|
||||
returnUrl || getUrlWithLocale('/settings/billing', locale);
|
||||
const params: CreatePortalParams = {
|
||||
customerId: customerResult[0].customerId,
|
||||
returnUrl: returnUrlWithLocale,
|
||||
locale
|
||||
locale,
|
||||
};
|
||||
|
||||
const result = await createCustomerPortal(params);
|
||||
@ -81,7 +89,7 @@ export const createPortalAction = actionClient
|
||||
data: result,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("create customer portal error:", error);
|
||||
console.error('create customer portal error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Something went wrong',
|
||||
|
@ -1,9 +1,9 @@
|
||||
'use server';
|
||||
|
||||
import { getSession } from "@/lib/server";
|
||||
import { getSubscriptions } from "@/payment";
|
||||
import { getSession } from '@/lib/server';
|
||||
import { getSubscriptions } from '@/payment';
|
||||
import { createSafeActionClient } from 'next-safe-action';
|
||||
import { z } from "zod";
|
||||
import { z } from 'zod';
|
||||
|
||||
// Create a safe action client
|
||||
const actionClient = createSafeActionClient();
|
||||
@ -27,7 +27,9 @@ export const getActiveSubscriptionAction = actionClient
|
||||
// Get the current user session for authorization
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
console.warn(`unauthorized request to get active subscription for user ${userId}`);
|
||||
console.warn(
|
||||
`unauthorized request to get active subscription for user ${userId}`
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Unauthorized',
|
||||
@ -36,7 +38,9 @@ export const getActiveSubscriptionAction = actionClient
|
||||
|
||||
// Only allow users to check their own status unless they're admins
|
||||
if (session.user.id !== userId && session.user.role !== 'admin') {
|
||||
console.warn(`current user ${session.user.id} is not authorized to get active subscription for user ${userId}`);
|
||||
console.warn(
|
||||
`current user ${session.user.id} is not authorized to get active subscription for user ${userId}`
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Not authorized to do this action',
|
||||
@ -46,7 +50,7 @@ export const getActiveSubscriptionAction = actionClient
|
||||
try {
|
||||
// Find the user's most recent active subscription
|
||||
const subscriptions = await getSubscriptions({
|
||||
userId: session.user.id
|
||||
userId: session.user.id,
|
||||
});
|
||||
// console.log('get user subscriptions:', subscriptions);
|
||||
|
||||
@ -54,8 +58,8 @@ export const getActiveSubscriptionAction = actionClient
|
||||
// Find the most recent active subscription (if any)
|
||||
if (subscriptions && subscriptions.length > 0) {
|
||||
// First try to find an active subscription
|
||||
const activeSubscription = subscriptions.find(sub =>
|
||||
sub.status === 'active' || sub.status === 'trialing'
|
||||
const activeSubscription = subscriptions.find(
|
||||
(sub) => sub.status === 'active' || sub.status === 'trialing'
|
||||
);
|
||||
|
||||
// If found, use it
|
||||
@ -63,7 +67,10 @@ export const getActiveSubscriptionAction = actionClient
|
||||
console.log('find active subscription for userId:', session.user.id);
|
||||
subscriptionData = activeSubscription;
|
||||
} else {
|
||||
console.log('no active subscription found for userId:', session.user.id);
|
||||
console.log(
|
||||
'no active subscription found for userId:',
|
||||
session.user.id
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.log('no subscriptions found for userId:', session.user.id);
|
||||
@ -74,7 +81,7 @@ export const getActiveSubscriptionAction = actionClient
|
||||
data: subscriptionData,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("get user subscription data error:", error);
|
||||
console.error('get user subscription data error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Something went wrong',
|
||||
|
@ -1,13 +1,13 @@
|
||||
'use server';
|
||||
|
||||
import db from "@/db";
|
||||
import { payment } from "@/db/schema";
|
||||
import { getSession } from "@/lib/server";
|
||||
import { getAllPricePlans, findPlanByPriceId } from "@/lib/price-plan";
|
||||
import { PaymentTypes } from "@/payment/types";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import db from '@/db';
|
||||
import { payment } from '@/db/schema';
|
||||
import { findPlanByPriceId, getAllPricePlans } from '@/lib/price-plan';
|
||||
import { getSession } from '@/lib/server';
|
||||
import { PaymentTypes } from '@/payment/types';
|
||||
import { and, eq } from 'drizzle-orm';
|
||||
import { createSafeActionClient } from 'next-safe-action';
|
||||
import { z } from "zod";
|
||||
import { z } from 'zod';
|
||||
|
||||
// Create a safe action client
|
||||
const actionClient = createSafeActionClient();
|
||||
@ -33,7 +33,9 @@ export const getLifetimeStatusAction = actionClient
|
||||
// Get the current user session for authorization
|
||||
const session = await getSession();
|
||||
if (!session) {
|
||||
console.warn(`unauthorized request to get lifetime status for user ${userId}`);
|
||||
console.warn(
|
||||
`unauthorized request to get lifetime status for user ${userId}`
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Unauthorized',
|
||||
@ -42,7 +44,9 @@ export const getLifetimeStatusAction = actionClient
|
||||
|
||||
// Only allow users to check their own status unless they're admins
|
||||
if (session.user.id !== userId && session.user.role !== 'admin') {
|
||||
console.warn(`current user ${session.user.id} is not authorized to get lifetime status for user ${userId}`);
|
||||
console.warn(
|
||||
`current user ${session.user.id} is not authorized to get lifetime status for user ${userId}`
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Not authorized to do this action',
|
||||
@ -53,8 +57,8 @@ export const getLifetimeStatusAction = actionClient
|
||||
// Get lifetime plans
|
||||
const plans = getAllPricePlans();
|
||||
const lifetimePlanIds = plans
|
||||
.filter(plan => plan.isLifetime)
|
||||
.map(plan => plan.id);
|
||||
.filter((plan) => plan.isLifetime)
|
||||
.map((plan) => plan.id);
|
||||
|
||||
// Check if there are any lifetime plans defined in the system
|
||||
if (lifetimePlanIds.length === 0) {
|
||||
@ -66,7 +70,11 @@ export const getLifetimeStatusAction = actionClient
|
||||
|
||||
// Query the database for one-time payments with lifetime plans
|
||||
const result = await db
|
||||
.select({ id: payment.id, priceId: payment.priceId, type: payment.type })
|
||||
.select({
|
||||
id: payment.id,
|
||||
priceId: payment.priceId,
|
||||
type: payment.type,
|
||||
})
|
||||
.from(payment)
|
||||
.where(
|
||||
and(
|
||||
@ -77,7 +85,7 @@ export const getLifetimeStatusAction = actionClient
|
||||
);
|
||||
|
||||
// Check if any payment has a lifetime plan
|
||||
const hasLifetimePayment = result.some(paymentRecord => {
|
||||
const hasLifetimePayment = result.some((paymentRecord) => {
|
||||
const plan = findPlanByPriceId(paymentRecord.priceId);
|
||||
return plan && lifetimePlanIds.includes(plan.id);
|
||||
});
|
||||
@ -87,7 +95,7 @@ export const getLifetimeStatusAction = actionClient
|
||||
isLifetimeMember: hasLifetimePayment,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("get user lifetime status error:", error);
|
||||
console.error('get user lifetime status error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Something went wrong',
|
||||
|
@ -15,12 +15,13 @@ const actionClient = createSafeActionClient();
|
||||
*/
|
||||
// Contact form schema for validation
|
||||
const contactFormSchema = z.object({
|
||||
name: z.string()
|
||||
name: z
|
||||
.string()
|
||||
.min(3, { message: 'Name must be at least 3 characters' })
|
||||
.max(30, { message: 'Name must not exceed 30 characters' }),
|
||||
email: z.string()
|
||||
.email({ message: 'Please enter a valid email address' }),
|
||||
message: z.string()
|
||||
email: z.string().email({ message: 'Please enter a valid email address' }),
|
||||
message: z
|
||||
.string()
|
||||
.min(10, { message: 'Message must be at least 10 characters' })
|
||||
.max(500, { message: 'Message must not exceed 500 characters' }),
|
||||
});
|
||||
|
@ -1,9 +1,9 @@
|
||||
import GoogleAnalytics from "./google-analytics";
|
||||
import { UmamiAnalytics } from "./umami-analytics";
|
||||
import { PlausibleAnalytics } from "./plausible-analytics";
|
||||
import DataFastAnalytics from "./data-fast-analytics";
|
||||
import OpenPanelAnalytics from "./open-panel-analytics";
|
||||
import { SelineAnalytics } from "./seline-analytics";
|
||||
import DataFastAnalytics from './data-fast-analytics';
|
||||
import GoogleAnalytics from './google-analytics';
|
||||
import OpenPanelAnalytics from './open-panel-analytics';
|
||||
import { PlausibleAnalytics } from './plausible-analytics';
|
||||
import { SelineAnalytics } from './seline-analytics';
|
||||
import { UmamiAnalytics } from './umami-analytics';
|
||||
|
||||
/**
|
||||
* Analytics Components all in one
|
||||
@ -12,7 +12,7 @@ import { SelineAnalytics } from "./seline-analytics";
|
||||
* 2. only work if the environment variable for the analytics is set
|
||||
*/
|
||||
export function Analytics() {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import Script from "next/script";
|
||||
import Script from 'next/script';
|
||||
|
||||
/**
|
||||
* DataFast Analytics
|
||||
@ -8,7 +8,7 @@ import Script from "next/script";
|
||||
* https://datafa.st
|
||||
*/
|
||||
export default function DataFastAnalytics() {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import { GoogleAnalytics as NextGoogleAnalytics } from "@next/third-parties/google";
|
||||
import { GoogleAnalytics as NextGoogleAnalytics } from '@next/third-parties/google';
|
||||
|
||||
/**
|
||||
* Google Analytics
|
||||
@ -9,7 +9,7 @@ import { GoogleAnalytics as NextGoogleAnalytics } from "@next/third-parties/goog
|
||||
* https://nextjs.org/docs/app/building-your-application/optimizing/third-party-libraries#google-analytics
|
||||
*/
|
||||
export default function GoogleAnalytics() {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { OpenPanelComponent } from "@openpanel/nextjs";
|
||||
import { OpenPanelComponent } from '@openpanel/nextjs';
|
||||
|
||||
/**
|
||||
* OpenPanel Analytics (https://openpanel.dev)
|
||||
@ -6,7 +6,7 @@ import { OpenPanelComponent } from "@openpanel/nextjs";
|
||||
* https://docs.openpanel.dev/docs/sdks/nextjs#options
|
||||
*/
|
||||
export default function OpenPanelAnalytics() {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import Script from "next/script";
|
||||
import Script from 'next/script';
|
||||
|
||||
/**
|
||||
* Plausible Analytics
|
||||
@ -8,7 +8,7 @@ import Script from "next/script";
|
||||
* https://plausible.io
|
||||
*/
|
||||
export function PlausibleAnalytics() {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -23,11 +23,6 @@ export function PlausibleAnalytics() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Script
|
||||
defer
|
||||
type="text/javascript"
|
||||
data-domain={domain}
|
||||
src={script}
|
||||
/>
|
||||
<Script defer type="text/javascript" data-domain={domain} src={script} />
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import Script from "next/script";
|
||||
import Script from 'next/script';
|
||||
|
||||
/**
|
||||
* Seline Analytics
|
||||
@ -10,7 +10,7 @@ import Script from "next/script";
|
||||
* https://seline.com/docs/stripe
|
||||
*/
|
||||
export function SelineAnalytics() {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import Script from "next/script";
|
||||
import Script from 'next/script';
|
||||
|
||||
/**
|
||||
* Umami Analytics
|
||||
@ -8,7 +8,7 @@ import Script from "next/script";
|
||||
* https://umami.is
|
||||
*/
|
||||
export function UmamiAnalytics() {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -14,8 +14,8 @@ import StatsSection from '@/components/blocks/stats/stats';
|
||||
import TestimonialsSection from '@/components/blocks/testimonials/testimonials';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Metadata } from 'next';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
/**
|
||||
|
@ -4,7 +4,7 @@ import { getPage } from '@/lib/page/get-page';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import type { NextPageProps } from '@/types/next-page-props';
|
||||
import type { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
@ -28,7 +28,7 @@ export async function generateMetadata({
|
||||
return constructMetadata({
|
||||
title: page.title + ' | ' + t('title'),
|
||||
description: page.description,
|
||||
canonicalUrl: getUrlWithLocale("/cookie", locale),
|
||||
canonicalUrl: getUrlWithLocale('/cookie', locale),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Container from '@/components/layout/container';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
import '@/styles/mdx.css';
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { getPage } from '@/lib/page/get-page';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import type { NextPageProps } from '@/types/next-page-props';
|
||||
import type { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
@ -28,7 +28,7 @@ export async function generateMetadata({
|
||||
return constructMetadata({
|
||||
title: page.title + ' | ' + t('title'),
|
||||
description: page.description,
|
||||
canonicalUrl: getUrlWithLocale("/privacy", locale),
|
||||
canonicalUrl: getUrlWithLocale('/privacy', locale),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { getPage } from '@/lib/page/get-page';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import type { NextPageProps } from '@/types/next-page-props';
|
||||
import type { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
@ -28,7 +28,7 @@ export async function generateMetadata({
|
||||
return constructMetadata({
|
||||
title: page.title + ' | ' + t('title'),
|
||||
description: page.description,
|
||||
canonicalUrl: getUrlWithLocale("/terms", locale),
|
||||
canonicalUrl: getUrlWithLocale('/terms', locale),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -4,8 +4,8 @@ import { websiteConfig } from '@/config/website';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { MailIcon } from 'lucide-react';
|
||||
import { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Metadata } from 'next';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export async function generateMetadata({
|
||||
@ -20,7 +20,7 @@ export async function generateMetadata({
|
||||
return constructMetadata({
|
||||
title: pt('title') + ' | ' + t('title'),
|
||||
description: pt('description'),
|
||||
canonicalUrl: getUrlWithLocale("/about", locale),
|
||||
canonicalUrl: getUrlWithLocale('/about', locale),
|
||||
});
|
||||
}
|
||||
|
||||
@ -49,9 +49,7 @@ export default async function AboutPage() {
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<h1 className="text-4xl text-foreground">
|
||||
{t('authorName')}
|
||||
</h1>
|
||||
<h1 className="text-4xl text-foreground">{t('authorName')}</h1>
|
||||
<p className="text-base text-muted-foreground mt-2">
|
||||
{t('authorBio')}
|
||||
</p>
|
||||
@ -67,7 +65,9 @@ export default async function AboutPage() {
|
||||
<div className="flex items-center gap-4">
|
||||
<Button className="rounded-lg cursor-pointer">
|
||||
<MailIcon className="mr-1 size-4" />
|
||||
<a href={`mailto:${websiteConfig.mail.from}`}>{t('talkWithMe')}</a>
|
||||
<a href={`mailto:${websiteConfig.mail.from}`}>
|
||||
{t('talkWithMe')}
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -4,7 +4,7 @@ import { getReleases } from '@/lib/release/get-releases';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import type { NextPageProps } from '@/types/next-page-props';
|
||||
import type { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
@ -22,7 +22,7 @@ export async function generateMetadata({
|
||||
return constructMetadata({
|
||||
title: pt('title') + ' | ' + t('title'),
|
||||
description: pt('description'),
|
||||
canonicalUrl: getUrlWithLocale("/changelog", locale),
|
||||
canonicalUrl: getUrlWithLocale('/changelog', locale),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ContactFormCard } from '@/components/contact/contact-form-card';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Metadata } from 'next';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export async function generateMetadata({
|
||||
@ -17,7 +17,7 @@ export async function generateMetadata({
|
||||
return constructMetadata({
|
||||
title: pt('title') + ' | ' + t('title'),
|
||||
description: pt('description'),
|
||||
canonicalUrl: getUrlWithLocale("/contact", locale),
|
||||
canonicalUrl: getUrlWithLocale('/contact', locale),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Container from '@/components/layout/container';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
export default function PageLayout({ children }: PropsWithChildren) {
|
||||
return (
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { WaitlistFormCard } from '@/components/waitlist/waitlist-form-card';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Metadata } from 'next';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export async function generateMetadata({
|
||||
@ -16,7 +16,7 @@ export async function generateMetadata({
|
||||
return constructMetadata({
|
||||
title: pt('title') + ' | ' + t('title'),
|
||||
description: pt('description'),
|
||||
canonicalUrl: getUrlWithLocale("/waitlist", locale),
|
||||
canonicalUrl: getUrlWithLocale('/waitlist', locale),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Metadata } from 'next';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export async function generateMetadata({
|
||||
@ -17,7 +17,7 @@ export async function generateMetadata({
|
||||
return constructMetadata({
|
||||
title: pt('title') + ' | ' + t('title'),
|
||||
description: pt('description'),
|
||||
canonicalUrl: getUrlWithLocale("/ai/audio", locale),
|
||||
canonicalUrl: getUrlWithLocale('/ai/audio', locale),
|
||||
});
|
||||
}
|
||||
|
||||
@ -44,9 +44,7 @@ export default async function AIAudioPage() {
|
||||
</Avatar>
|
||||
|
||||
<div>
|
||||
<h1 className="text-4xl text-foreground">
|
||||
{t('content')}
|
||||
</h1>
|
||||
<h1 className="text-4xl text-foreground">{t('content')}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Metadata } from 'next';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export async function generateMetadata({
|
||||
@ -17,7 +17,7 @@ export async function generateMetadata({
|
||||
return constructMetadata({
|
||||
title: pt('title') + ' | ' + t('title'),
|
||||
description: pt('description'),
|
||||
canonicalUrl: getUrlWithLocale("/ai/image", locale),
|
||||
canonicalUrl: getUrlWithLocale('/ai/image', locale),
|
||||
});
|
||||
}
|
||||
|
||||
@ -44,9 +44,7 @@ export default async function AIImagePage() {
|
||||
</Avatar>
|
||||
|
||||
<div>
|
||||
<h1 className="text-4xl text-foreground">
|
||||
{t('content')}
|
||||
</h1>
|
||||
<h1 className="text-4xl text-foreground">{t('content')}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Container from '@/components/layout/container';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
export default function PageLayout({ children }: PropsWithChildren) {
|
||||
return (
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Metadata } from 'next';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export async function generateMetadata({
|
||||
@ -17,7 +17,7 @@ export async function generateMetadata({
|
||||
return constructMetadata({
|
||||
title: pt('title') + ' | ' + t('title'),
|
||||
description: pt('description'),
|
||||
canonicalUrl: getUrlWithLocale("/ai/text", locale),
|
||||
canonicalUrl: getUrlWithLocale('/ai/text', locale),
|
||||
});
|
||||
}
|
||||
|
||||
@ -44,9 +44,7 @@ export default async function AITextPage() {
|
||||
</Avatar>
|
||||
|
||||
<div>
|
||||
<h1 className="text-4xl text-foreground">
|
||||
{t('content')}
|
||||
</h1>
|
||||
<h1 className="text-4xl text-foreground">{t('content')}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Metadata } from 'next';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export async function generateMetadata({
|
||||
@ -17,7 +17,7 @@ export async function generateMetadata({
|
||||
return constructMetadata({
|
||||
title: pt('title') + ' | ' + t('title'),
|
||||
description: pt('description'),
|
||||
canonicalUrl: getUrlWithLocale("/ai/video", locale),
|
||||
canonicalUrl: getUrlWithLocale('/ai/video', locale),
|
||||
});
|
||||
}
|
||||
|
||||
@ -44,9 +44,7 @@ export default async function AIVideoPage() {
|
||||
</Avatar>
|
||||
|
||||
<div>
|
||||
<h1 className="text-4xl text-foreground">
|
||||
{t('content')}
|
||||
</h1>
|
||||
<h1 className="text-4xl text-foreground">{t('content')}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { categories } from '@/components/nsui/blocks';
|
||||
import BlocksNav from '@/components/nsui/blocks-nav';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
/**
|
||||
* The locale inconsistency issue has been fixed in the BlocksNav component
|
||||
@ -10,9 +10,7 @@ export default function BlockCategoryLayout({ children }: PropsWithChildren) {
|
||||
<>
|
||||
<BlocksNav categories={categories} />
|
||||
|
||||
<main>
|
||||
{children}
|
||||
</main>
|
||||
<main>{children}</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ import BlockPreview from '@/components/nsui/block-preview';
|
||||
import { blocks, categories } from '@/components/nsui/blocks';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Metadata } from 'next';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
@ -26,7 +26,7 @@ export async function generateMetadata({
|
||||
return constructMetadata({
|
||||
title: category + ' | ' + t('title'),
|
||||
description: t('description'),
|
||||
canonicalUrl: getUrlWithLocale("/blocks/${category}", locale),
|
||||
canonicalUrl: getUrlWithLocale('/blocks/${category}', locale),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -4,10 +4,10 @@ import CustomPagination from '@/components/shared/pagination';
|
||||
import { websiteConfig } from '@/config/website';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { NextPageProps } from '@/types/next-page-props';
|
||||
import type { NextPageProps } from '@/types/next-page-props';
|
||||
import { allCategories, allPosts } from 'content-collections';
|
||||
import type { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export async function generateMetadata({
|
||||
@ -34,7 +34,7 @@ export async function generateMetadata({
|
||||
return constructMetadata({
|
||||
title: `${category.name} | ${t('title')}`,
|
||||
description: category.description,
|
||||
canonicalUrl: getUrlWithLocale("/blog/category/${slug}", locale),
|
||||
canonicalUrl: getUrlWithLocale('/blog/category/${slug}', locale),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { BlogCategoryFilter } from '@/components/blog/blog-category-filter';
|
||||
import Container from '@/components/layout/container';
|
||||
import { NextPageProps } from '@/types/next-page-props';
|
||||
import type { NextPageProps } from '@/types/next-page-props';
|
||||
import { allCategories } from 'content-collections';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
interface BlogListLayoutProps extends PropsWithChildren, NextPageProps {}
|
||||
|
||||
|
@ -4,10 +4,10 @@ import CustomPagination from '@/components/shared/pagination';
|
||||
import { websiteConfig } from '@/config/website';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { NextPageProps } from '@/types/next-page-props';
|
||||
import type { NextPageProps } from '@/types/next-page-props';
|
||||
import { allPosts } from 'content-collections';
|
||||
import { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Metadata } from 'next';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export async function generateMetadata({
|
||||
@ -21,7 +21,7 @@ export async function generateMetadata({
|
||||
return constructMetadata({
|
||||
title: pt('title') + ' | ' + t('title'),
|
||||
description: pt('description'),
|
||||
canonicalUrl: getUrlWithLocale("/blog", locale),
|
||||
canonicalUrl: getUrlWithLocale('/blog', locale),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Container from '@/components/layout/container';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
export default function BlogPostLayout({ children }: PropsWithChildren) {
|
||||
return (
|
||||
|
@ -6,17 +6,17 @@ import { CustomMDXContent } from '@/components/shared/custom-mdx-content';
|
||||
import { websiteConfig } from '@/config/website';
|
||||
import { LocaleLink } from '@/i18n/navigation';
|
||||
import { getTableOfContents } from '@/lib/blog/toc';
|
||||
import { formatDate } from '@/lib/formatter';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import type { NextPageProps } from '@/types/next-page-props';
|
||||
import { allPosts, Post } from 'content-collections';
|
||||
import { type Post, allPosts } from 'content-collections';
|
||||
import { CalendarIcon, ClockIcon, FileTextIcon } from 'lucide-react';
|
||||
import type { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import Image from 'next/image';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { formatDate } from '@/lib/formatter';
|
||||
|
||||
import '@/styles/mdx.css';
|
||||
|
||||
@ -85,7 +85,7 @@ export async function generateMetadata({
|
||||
|
||||
const post = await getBlogPostFromParams({
|
||||
params: Promise.resolve({ slug, locale }),
|
||||
searchParams: Promise.resolve({})
|
||||
searchParams: Promise.resolve({}),
|
||||
});
|
||||
if (!post) {
|
||||
console.warn(
|
||||
@ -194,9 +194,7 @@ export default async function BlogPostPage(props: NextPageProps) {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<span className="line-clamp-1">
|
||||
{post.author?.name}
|
||||
</span>
|
||||
<span className="line-clamp-1">{post.author?.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Footer } from '@/components/layout/footer';
|
||||
import { Navbar } from '@/components/layout/navbar';
|
||||
import { ReactNode } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export default function MarketingLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Metadata } from 'next';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export async function generateMetadata({
|
||||
|
@ -4,7 +4,7 @@ import { DataTable } from '@/components/dashboard/data-table';
|
||||
import { SectionCards } from '@/components/dashboard/section-cards';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
import data from "./data.json";
|
||||
import data from './data.json';
|
||||
|
||||
/**
|
||||
* Admin users page
|
||||
|
@ -4,7 +4,7 @@ import { DataTable } from '@/components/dashboard/data-table';
|
||||
import { SectionCards } from '@/components/dashboard/section-cards';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
import data from "./data.json";
|
||||
import data from './data.json';
|
||||
|
||||
/**
|
||||
* Dashboard page
|
||||
|
@ -1,9 +1,6 @@
|
||||
import { DashboardSidebar } from '@/components/dashboard/dashboard-sidebar';
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider
|
||||
} from '@/components/ui/sidebar';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
/**
|
||||
* inspired by dashboard-01
|
||||
@ -14,16 +11,14 @@ export default function DashboardLayout({ children }: PropsWithChildren) {
|
||||
<SidebarProvider
|
||||
style={
|
||||
{
|
||||
"--sidebar-width": "calc(var(--spacing) * 72)",
|
||||
"--header-height": "calc(var(--spacing) * 12)",
|
||||
'--sidebar-width': 'calc(var(--spacing) * 72)',
|
||||
'--header-height': 'calc(var(--spacing) * 12)',
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<DashboardSidebar variant="inset" />
|
||||
|
||||
<SidebarInset>
|
||||
{children}
|
||||
</SidebarInset>
|
||||
<SidebarInset>{children}</SidebarInset>
|
||||
</SidebarProvider>
|
||||
);
|
||||
}
|
||||
|
@ -5,9 +5,7 @@ interface BillingLayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default async function BillingLayout({
|
||||
children,
|
||||
}: BillingLayoutProps) {
|
||||
export default async function BillingLayout({ children }: BillingLayoutProps) {
|
||||
const t = await getTranslations('Dashboard.settings');
|
||||
|
||||
const breadcrumbs = [
|
||||
|
@ -1,7 +1,5 @@
|
||||
import BillingCard from '@/components/settings/billing/billing-card';
|
||||
|
||||
export default function BillingPage() {
|
||||
return (
|
||||
<BillingCard />
|
||||
);
|
||||
return <BillingCard />;
|
||||
}
|
||||
|
@ -5,9 +5,7 @@ interface ProfileLayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default async function ProfileLayout({
|
||||
children,
|
||||
}: ProfileLayoutProps) {
|
||||
export default async function ProfileLayout({ children }: ProfileLayoutProps) {
|
||||
const t = await getTranslations('Dashboard.settings');
|
||||
|
||||
const breadcrumbs = [
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ErrorCard } from '@/components/auth/error-card';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Metadata } from 'next';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export async function generateMetadata({
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ForgotPasswordForm } from '@/components/auth/forgot-password-form';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Metadata } from 'next';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export async function generateMetadata({
|
||||
@ -12,12 +12,15 @@ export async function generateMetadata({
|
||||
}): Promise<Metadata | undefined> {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: 'Metadata' });
|
||||
const pt = await getTranslations({locale, namespace: 'AuthPage.forgotPassword'});
|
||||
const pt = await getTranslations({
|
||||
locale,
|
||||
namespace: 'AuthPage.forgotPassword',
|
||||
});
|
||||
|
||||
return constructMetadata({
|
||||
title: pt('title') + ' | ' + t('title'),
|
||||
description: t('description'),
|
||||
canonicalUrl: getUrlWithLocale("/auth/forgot-password", locale),
|
||||
canonicalUrl: getUrlWithLocale('/auth/forgot-password', locale),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,8 @@ import { LocaleLink } from '@/i18n/navigation';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { Routes } from '@/routes';
|
||||
import { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Metadata } from 'next';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export async function generateMetadata({
|
||||
@ -19,7 +19,7 @@ export async function generateMetadata({
|
||||
return constructMetadata({
|
||||
title: pt('title') + ' | ' + t('title'),
|
||||
description: t('description'),
|
||||
canonicalUrl: getUrlWithLocale("/auth/login", locale),
|
||||
canonicalUrl: getUrlWithLocale('/auth/login', locale),
|
||||
});
|
||||
}
|
||||
|
||||
@ -47,4 +47,4 @@ export default async function LoginPage() {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
@ -3,8 +3,8 @@ import { LocaleLink } from '@/i18n/navigation';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { Routes } from '@/routes';
|
||||
import { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Metadata } from 'next';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export async function generateMetadata({
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ResetPasswordForm } from '@/components/auth/reset-password-form';
|
||||
import { constructMetadata } from '@/lib/metadata';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Metadata } from 'next';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export async function generateMetadata({
|
||||
@ -12,12 +12,15 @@ export async function generateMetadata({
|
||||
}): Promise<Metadata | undefined> {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: 'Metadata' });
|
||||
const pt = await getTranslations({locale, namespace: 'AuthPage.resetPassword'});
|
||||
const pt = await getTranslations({
|
||||
locale,
|
||||
namespace: 'AuthPage.resetPassword',
|
||||
});
|
||||
|
||||
return constructMetadata({
|
||||
title: pt('title') + ' | ' + t('title'),
|
||||
description: t('description'),
|
||||
canonicalUrl: getUrlWithLocale("/auth/reset-password", locale),
|
||||
canonicalUrl: getUrlWithLocale('/auth/reset-password', locale),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,31 +1,38 @@
|
||||
import * as Preview from '@/components/docs';
|
||||
import { CustomMDXContent } from '@/components/shared/custom-mdx-content';
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger, } from '@/components/ui/hover-card';
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
HoverCardTrigger,
|
||||
} from '@/components/ui/hover-card';
|
||||
import { LOCALES } from '@/i18n/routing';
|
||||
import { source } from '@/lib/docs/source';
|
||||
import Link from 'fumadocs-core/link';
|
||||
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/page';
|
||||
import {
|
||||
DocsBody,
|
||||
DocsDescription,
|
||||
DocsPage,
|
||||
DocsTitle,
|
||||
} from 'fumadocs-ui/page';
|
||||
import type { Metadata } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { ReactNode } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export function generateStaticParams() {
|
||||
const locales = LOCALES;
|
||||
const slugParams = source.generateParams();
|
||||
const params = locales.flatMap(locale =>
|
||||
slugParams.map(param => ({
|
||||
const params = locales.flatMap((locale) =>
|
||||
slugParams.map((param) => ({
|
||||
locale,
|
||||
slug: param.slug
|
||||
slug: param.slug,
|
||||
}))
|
||||
);
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: DocPageProps) {
|
||||
export async function generateMetadata({ params }: DocPageProps) {
|
||||
const { slug, locale } = await params;
|
||||
const language = locale as string;
|
||||
const page = source.getPage(slug, language);
|
||||
@ -54,7 +61,7 @@ export const revalidate = false;
|
||||
interface DocPageProps {
|
||||
params: Promise<{
|
||||
slug?: string[];
|
||||
locale: Locale
|
||||
locale: Locale;
|
||||
}>;
|
||||
}
|
||||
|
||||
@ -64,9 +71,7 @@ interface DocPageProps {
|
||||
* ref:
|
||||
* https://github.com/fuma-nama/fumadocs/blob/dev/apps/docs/app/docs/%5B...slug%5D/page.tsx
|
||||
*/
|
||||
export default async function DocPage({
|
||||
params,
|
||||
}: DocPageProps) {
|
||||
export default async function DocPage({ params }: DocPageProps) {
|
||||
const { slug, locale } = await params;
|
||||
const language = locale as string;
|
||||
const page = source.getPage(slug, language);
|
||||
@ -79,18 +84,15 @@ export default async function DocPage({
|
||||
const preview = page.data.preview;
|
||||
|
||||
return (
|
||||
<DocsPage toc={page.data.toc}
|
||||
<DocsPage
|
||||
toc={page.data.toc}
|
||||
full={page.data.full}
|
||||
tableOfContent={{
|
||||
style: "clerk",
|
||||
style: 'clerk',
|
||||
}}
|
||||
>
|
||||
<DocsTitle>
|
||||
{page.data.title}
|
||||
</DocsTitle>
|
||||
<DocsDescription>
|
||||
{page.data.description}
|
||||
</DocsDescription>
|
||||
<DocsTitle>{page.data.title}</DocsTitle>
|
||||
<DocsDescription>{page.data.description}</DocsDescription>
|
||||
<DocsBody>
|
||||
{/* Preview Rendered Component */}
|
||||
{preview ? <PreviewRenderer preview={preview} /> : null}
|
||||
|
@ -5,11 +5,11 @@ import { websiteConfig } from '@/config/website';
|
||||
import { docsI18nConfig } from '@/lib/docs/i18n';
|
||||
import { source } from '@/lib/docs/source';
|
||||
import { getUrlWithLocale } from '@/lib/urls/urls';
|
||||
import { I18nProvider, Translations } from 'fumadocs-ui/i18n';
|
||||
import { I18nProvider, type Translations } from 'fumadocs-ui/i18n';
|
||||
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
||||
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
|
||||
import { BookIcon, HomeIcon } from 'lucide-react';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
@ -17,10 +17,12 @@ import '@/styles/mdx.css';
|
||||
|
||||
// available languages that will be displayed on UI
|
||||
// make sure `locale` is consistent with your i18n config
|
||||
const locales = Object.entries(websiteConfig.i18n.locales).map(([locale, data]) => ({
|
||||
const locales = Object.entries(websiteConfig.i18n.locales).map(
|
||||
([locale, data]) => ({
|
||||
name: data.name,
|
||||
locale,
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
interface DocsLayoutProps {
|
||||
children: ReactNode;
|
||||
@ -41,7 +43,10 @@ interface DocsLayoutProps {
|
||||
* ref:
|
||||
* https://github.com/fuma-nama/fumadocs/blob/dev/apps/docs/content/docs/ui/meta.json
|
||||
*/
|
||||
export default async function DocsRootLayout({ children, params }: DocsLayoutProps) {
|
||||
export default async function DocsRootLayout({
|
||||
children,
|
||||
params,
|
||||
}: DocsLayoutProps) {
|
||||
const { locale } = await params;
|
||||
const t = await getTranslations({ locale, namespace: 'DocsPage' });
|
||||
|
||||
@ -81,28 +86,24 @@ export default async function DocsRootLayout({ children, params }: DocsLayoutPro
|
||||
...(websiteConfig.metadata.social?.twitter
|
||||
? [
|
||||
{
|
||||
type: "icon" as const,
|
||||
type: 'icon' as const,
|
||||
icon: <XTwitterIcon />,
|
||||
text: "X",
|
||||
text: 'X',
|
||||
url: websiteConfig.metadata.social.twitter,
|
||||
secondary: true,
|
||||
}
|
||||
},
|
||||
]
|
||||
: [])
|
||||
: []),
|
||||
],
|
||||
themeSwitch: {
|
||||
enabled: true,
|
||||
mode: 'light-dark-system',
|
||||
component: <ModeSwitcher />
|
||||
component: <ModeSwitcher />,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<I18nProvider
|
||||
locales={locales}
|
||||
locale={locale}
|
||||
translations={translations}
|
||||
>
|
||||
<I18nProvider locales={locales} locale={locale} translations={translations}>
|
||||
<DocsLayout tree={source.pageTree[locale]} {...docsOptions}>
|
||||
{children}
|
||||
</DocsLayout>
|
||||
|
@ -1,15 +1,20 @@
|
||||
import { fontBricolageGrotesque, fontNotoSans, fontNotoSansMono, fontNotoSerif } from '@/assets/fonts';
|
||||
import {
|
||||
fontBricolageGrotesque,
|
||||
fontNotoSans,
|
||||
fontNotoSansMono,
|
||||
fontNotoSerif,
|
||||
} from '@/assets/fonts';
|
||||
import { routing } from '@/i18n/routing';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { hasLocale, Locale, NextIntlClientProvider } from 'next-intl';
|
||||
import { type Locale, NextIntlClientProvider, hasLocale } from 'next-intl';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { ReactNode } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Toaster } from 'sonner';
|
||||
import { Providers } from './providers';
|
||||
|
||||
import '@/styles/globals.css';
|
||||
import { TailwindIndicator } from '@/components/layout/tailwind-indicator';
|
||||
import { Analytics } from '@/analytics/analytics';
|
||||
import { TailwindIndicator } from '@/components/layout/tailwind-indicator';
|
||||
|
||||
interface LocaleLayoutProps {
|
||||
children: ReactNode;
|
||||
|
@ -6,7 +6,7 @@ import { TooltipProvider } from '@/components/ui/tooltip';
|
||||
import { websiteConfig } from '@/config/website';
|
||||
import { RootProvider } from 'fumadocs-ui/provider';
|
||||
import { ThemeProvider, useTheme } from 'next-themes';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
/**
|
||||
* Providers
|
||||
@ -21,7 +21,7 @@ import { PropsWithChildren } from 'react';
|
||||
*/
|
||||
export function Providers({ children }: PropsWithChildren) {
|
||||
const theme = useTheme();
|
||||
const defaultMode = websiteConfig.metadata.mode?.defaultMode ?? "system";
|
||||
const defaultMode = websiteConfig.metadata.mode?.defaultMode ?? 'system';
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
@ -33,9 +33,7 @@ export function Providers({ children }: PropsWithChildren) {
|
||||
<ActiveThemeProvider>
|
||||
<RootProvider theme={theme}>
|
||||
<TooltipProvider>
|
||||
<PaymentProvider>
|
||||
{children}
|
||||
</PaymentProvider>
|
||||
<PaymentProvider>{children}</PaymentProvider>
|
||||
</TooltipProvider>
|
||||
</RootProvider>
|
||||
</ActiveThemeProvider>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { docsI18nConfig } from '@/lib/docs/i18n';
|
||||
import { source } from '@/lib/docs/source';
|
||||
import { createTokenizer } from '@orama/tokenizers/mandarin';
|
||||
import { createI18nSearchAPI } from 'fumadocs-core/search/server';
|
||||
import { docsI18nConfig } from '@/lib/docs/i18n';
|
||||
|
||||
/**
|
||||
* Fumadocs i18n search configuration
|
||||
@ -26,7 +26,7 @@ const searchAPI = createI18nSearchAPI('advanced', {
|
||||
id: page.url,
|
||||
url: page.url,
|
||||
locale: language,
|
||||
})),
|
||||
}))
|
||||
),
|
||||
|
||||
// Configure special language tokenizers and search options
|
||||
@ -73,7 +73,10 @@ export const GET = async (request: Request) => {
|
||||
console.log('search, referer pathname:', refererUrl.pathname);
|
||||
const refererPathParts = refererUrl.pathname.split('/').filter(Boolean);
|
||||
console.log('search, referer path parts:', refererPathParts);
|
||||
if (refererPathParts.length > 0 && docsI18nConfig.languages.includes(refererPathParts[0])) {
|
||||
if (
|
||||
refererPathParts.length > 0 &&
|
||||
docsI18nConfig.languages.includes(refererPathParts[0])
|
||||
) {
|
||||
locale = refererPathParts[0];
|
||||
console.log(`search, detected locale from referer: ${locale}`);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
|
||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||
import { StorageError } from '@/storage/types';
|
||||
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@ -68,10 +68,7 @@ export async function POST(request: NextRequest) {
|
||||
console.error('Error getting file URL:', error);
|
||||
|
||||
if (error instanceof StorageError) {
|
||||
return NextResponse.json(
|
||||
{ error: error.message },
|
||||
{ status: 500 }
|
||||
);
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getPresignedUploadUrl } from '@/storage';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { getPresignedUploadUrl } from '@/storage';
|
||||
import { StorageError } from '@/storage/types';
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@ -47,10 +47,7 @@ export async function POST(request: NextRequest) {
|
||||
console.error('Error generating pre-signed URL:', error);
|
||||
|
||||
if (error instanceof StorageError) {
|
||||
return NextResponse.json(
|
||||
{ error: error.message },
|
||||
{ status: 500 }
|
||||
);
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { uploadFile } from '@/storage';
|
||||
import { StorageError } from '@/storage/types';
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@ -9,10 +9,7 @@ export async function POST(request: NextRequest) {
|
||||
const folder = formData.get('folder') as string | null;
|
||||
|
||||
if (!file) {
|
||||
return NextResponse.json(
|
||||
{ error: 'No file provided' },
|
||||
{ status: 400 }
|
||||
);
|
||||
return NextResponse.json({ error: 'No file provided' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Validate file size (max 10MB)
|
||||
@ -49,10 +46,7 @@ export async function POST(request: NextRequest) {
|
||||
console.error('Error uploading file:', error);
|
||||
|
||||
if (error instanceof StorageError) {
|
||||
return NextResponse.json(
|
||||
{ error: error.message },
|
||||
{ status: 500 }
|
||||
);
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { handleWebhookEvent } from '@/payment';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
/**
|
||||
* Stripe webhook handler
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ReactNode } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { defaultMessages } from '@/i18n/messages';
|
||||
import { type MetadataRoute } from 'next';
|
||||
import type { MetadataRoute } from 'next';
|
||||
|
||||
/**
|
||||
* Generates the Web App Manifest for the application
|
||||
|
@ -15,7 +15,7 @@ export default function GlobalNotFound() {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<Error statusCode={404} />;
|
||||
<Error statusCode={404} />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { MetadataRoute } from 'next';
|
||||
import type { MetadataRoute } from 'next';
|
||||
import { getBaseUrl } from '../lib/urls/urls';
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
|
@ -2,8 +2,8 @@ import { getLocalePathname } from '@/i18n/navigation';
|
||||
import { routing } from '@/i18n/routing';
|
||||
import { source } from '@/lib/docs/source';
|
||||
import { allCategories, allPosts } from 'content-collections';
|
||||
import { MetadataRoute } from 'next';
|
||||
import { Locale } from 'next-intl';
|
||||
import type { MetadataRoute } from 'next';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getBaseUrl } from '../lib/urls/urls';
|
||||
|
||||
type Href = Parameters<typeof getLocalePathname>[0]['href'];
|
||||
@ -37,45 +37,53 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||
const sitemapList: MetadataRoute.Sitemap = []; // final result
|
||||
|
||||
// add static routes
|
||||
sitemapList.push(...staticRoutes.flatMap((route) => {
|
||||
sitemapList.push(
|
||||
...staticRoutes.flatMap((route) => {
|
||||
return routing.locales.map((locale) => ({
|
||||
url: getUrl(route, locale),
|
||||
lastModified: new Date(),
|
||||
priority: 1,
|
||||
changeFrequency: 'weekly' as const
|
||||
}));
|
||||
changeFrequency: 'weekly' as const,
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
// add categories
|
||||
sitemapList.push(...allCategories.flatMap((category: { slug: string }) =>
|
||||
sitemapList.push(
|
||||
...allCategories.flatMap((category: { slug: string }) =>
|
||||
routing.locales.map((locale) => ({
|
||||
url: getUrl(`/blog/category/${category.slug}`, locale),
|
||||
lastModified: new Date(),
|
||||
priority: 0.8,
|
||||
changeFrequency: 'weekly' as const
|
||||
changeFrequency: 'weekly' as const,
|
||||
}))
|
||||
));
|
||||
)
|
||||
);
|
||||
|
||||
// add posts
|
||||
sitemapList.push(...allPosts.flatMap((post: { slugAsParams: string }) =>
|
||||
sitemapList.push(
|
||||
...allPosts.flatMap((post: { slugAsParams: string }) =>
|
||||
routing.locales.map((locale) => ({
|
||||
url: getUrl(`/blog/${post.slugAsParams}`, locale),
|
||||
lastModified: new Date(),
|
||||
priority: 0.8,
|
||||
changeFrequency: 'weekly' as const
|
||||
changeFrequency: 'weekly' as const,
|
||||
}))
|
||||
));
|
||||
)
|
||||
);
|
||||
|
||||
// add docs
|
||||
const docsParams = source.generateParams();
|
||||
sitemapList.push(...docsParams.flatMap(param =>
|
||||
sitemapList.push(
|
||||
...docsParams.flatMap((param) =>
|
||||
routing.locales.map((locale) => ({
|
||||
url: getUrl(`/docs/${param.slug.join('/')}`, locale),
|
||||
lastModified: new Date(),
|
||||
priority: 0.8,
|
||||
changeFrequency: 'weekly' as const
|
||||
changeFrequency: 'weekly' as const,
|
||||
}))
|
||||
));
|
||||
)
|
||||
);
|
||||
|
||||
return sitemapList;
|
||||
}
|
||||
|
@ -1,4 +1,9 @@
|
||||
import { Bricolage_Grotesque, Noto_Sans, Noto_Sans_Mono, Noto_Serif } from 'next/font/google';
|
||||
import {
|
||||
Bricolage_Grotesque,
|
||||
Noto_Sans,
|
||||
Noto_Sans_Mono,
|
||||
Noto_Serif,
|
||||
} from 'next/font/google';
|
||||
|
||||
/**
|
||||
* This file shows how to customize the font by using local font or google font
|
||||
|
@ -35,9 +35,7 @@ export const AuthCard = ({
|
||||
</LocaleLink>
|
||||
<CardDescription>{headerLabel}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{children}
|
||||
</CardContent>
|
||||
<CardContent>{children}</CardContent>
|
||||
<CardFooter>
|
||||
<BottomLink label={bottomButtonLabel} href={bottomButtonHref} />
|
||||
</CardFooter>
|
||||
|
@ -8,17 +8,12 @@ interface DividerWithTextProps {
|
||||
/**
|
||||
* A horizontal divider with text in the middle
|
||||
*/
|
||||
export const DividerWithText = ({
|
||||
text,
|
||||
className,
|
||||
}: DividerWithTextProps) => {
|
||||
export const DividerWithText = ({ text, className }: DividerWithTextProps) => {
|
||||
return (
|
||||
<div className={cn('relative flex items-center', className)}>
|
||||
<div className="grow border-t border-border"></div>
|
||||
<span className="shrink mx-4 text-sm text-muted-foreground">
|
||||
{text}
|
||||
</span>
|
||||
<div className="grow border-t border-border"></div>
|
||||
<div className="grow border-t border-border" />
|
||||
<span className="shrink mx-4 text-sm text-muted-foreground">{text}</span>
|
||||
<div className="grow border-t border-border" />
|
||||
</div>
|
||||
);
|
||||
};
|
@ -118,9 +118,7 @@ export const ForgotPasswordForm = ({ className }: { className?: string }) => {
|
||||
type="submit"
|
||||
className="w-full cursor-pointer"
|
||||
>
|
||||
{isPending && (
|
||||
<Loader2Icon className="mr-2 size-4 animate-spin" />
|
||||
)}
|
||||
{isPending && <Loader2Icon className="mr-2 size-4 animate-spin" />}
|
||||
<span>{t('send')}</span>
|
||||
</Button>
|
||||
</form>
|
||||
|
@ -32,14 +32,20 @@ export interface LoginFormProps {
|
||||
callbackUrl?: string;
|
||||
}
|
||||
|
||||
export const LoginForm = ({ className, callbackUrl: propCallbackUrl }: LoginFormProps) => {
|
||||
export const LoginForm = ({
|
||||
className,
|
||||
callbackUrl: propCallbackUrl,
|
||||
}: LoginFormProps) => {
|
||||
const t = useTranslations('AuthPage.login');
|
||||
const searchParams = useSearchParams();
|
||||
const urlError = searchParams.get('error');
|
||||
const paramCallbackUrl = searchParams.get('callbackUrl');
|
||||
// Use prop callback URL or param callback URL if provided, otherwise use the default login redirect
|
||||
const locale = useLocale();
|
||||
const defaultCallbackUrl = getUrlWithLocaleInCallbackUrl(DEFAULT_LOGIN_REDIRECT, locale);
|
||||
const defaultCallbackUrl = getUrlWithLocaleInCallbackUrl(
|
||||
DEFAULT_LOGIN_REDIRECT,
|
||||
locale
|
||||
);
|
||||
const callbackUrl = propCallbackUrl || paramCallbackUrl || defaultCallbackUrl;
|
||||
console.log('login form, callbackUrl', callbackUrl);
|
||||
|
||||
@ -158,7 +164,7 @@ export const LoginForm = ({ className, callbackUrl: propCallbackUrl }: LoginForm
|
||||
{...field}
|
||||
disabled={isPending}
|
||||
placeholder="******"
|
||||
type={showPassword ? "text" : "password"}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
className="pr-10"
|
||||
/>
|
||||
<Button
|
||||
@ -193,9 +199,7 @@ export const LoginForm = ({ className, callbackUrl: propCallbackUrl }: LoginForm
|
||||
type="submit"
|
||||
className="w-full flex items-center justify-center gap-2 cursor-pointer"
|
||||
>
|
||||
{isPending && (
|
||||
<Loader2Icon className="mr-2 size-4 animate-spin" />
|
||||
)}
|
||||
{isPending && <Loader2Icon className="mr-2 size-4 animate-spin" />}
|
||||
<span>{t('signIn')}</span>
|
||||
</Button>
|
||||
</form>
|
||||
|
@ -27,8 +27,6 @@ export const LoginWrapper = ({
|
||||
callbackUrl,
|
||||
}: LoginWrapperProps) => {
|
||||
const router = useLocaleRouter();
|
||||
const pathname = useLocalePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
const handleLogin = () => {
|
||||
@ -40,11 +38,6 @@ export const LoginWrapper = ({
|
||||
router.push(loginPath);
|
||||
};
|
||||
|
||||
// Close the modal on route change
|
||||
useEffect(() => {
|
||||
setIsModalOpen(false);
|
||||
}, [pathname, searchParams]);
|
||||
|
||||
if (mode === 'modal') {
|
||||
return (
|
||||
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
|
||||
|
@ -29,13 +29,18 @@ interface RegisterFormProps {
|
||||
callbackUrl?: string;
|
||||
}
|
||||
|
||||
export const RegisterForm = ({ callbackUrl: propCallbackUrl }: RegisterFormProps) => {
|
||||
export const RegisterForm = ({
|
||||
callbackUrl: propCallbackUrl,
|
||||
}: RegisterFormProps) => {
|
||||
const t = useTranslations('AuthPage.register');
|
||||
const searchParams = useSearchParams();
|
||||
const paramCallbackUrl = searchParams.get('callbackUrl');
|
||||
// Use prop callback URL or param callback URL if provided, otherwise use the default login redirect
|
||||
const locale = useLocale();
|
||||
const defaultCallbackUrl = getUrlWithLocaleInCallbackUrl(DEFAULT_LOGIN_REDIRECT, locale);
|
||||
const defaultCallbackUrl = getUrlWithLocaleInCallbackUrl(
|
||||
DEFAULT_LOGIN_REDIRECT,
|
||||
locale
|
||||
);
|
||||
const callbackUrl = propCallbackUrl || paramCallbackUrl || defaultCallbackUrl;
|
||||
console.log('register form, callbackUrl', callbackUrl);
|
||||
|
||||
@ -159,7 +164,7 @@ export const RegisterForm = ({ callbackUrl: propCallbackUrl }: RegisterFormProps
|
||||
{...field}
|
||||
disabled={isPending}
|
||||
placeholder="******"
|
||||
type={showPassword ? "text" : "password"}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
className="pr-10"
|
||||
/>
|
||||
<Button
|
||||
@ -194,9 +199,7 @@ export const RegisterForm = ({ callbackUrl: propCallbackUrl }: RegisterFormProps
|
||||
type="submit"
|
||||
className="cursor-pointer w-full flex items-center justify-center gap-2"
|
||||
>
|
||||
{isPending && (
|
||||
<Loader2Icon className="mr-2 size-4 animate-spin" />
|
||||
)}
|
||||
{isPending && <Loader2Icon className="mr-2 size-4 animate-spin" />}
|
||||
<span>{t('signUp')}</span>
|
||||
</Button>
|
||||
</form>
|
||||
|
@ -117,7 +117,7 @@ export const ResetPasswordForm = () => {
|
||||
{...field}
|
||||
disabled={isPending}
|
||||
placeholder="******"
|
||||
type={showPassword ? "text" : "password"}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
className="pr-10"
|
||||
/>
|
||||
<Button
|
||||
@ -152,9 +152,7 @@ export const ResetPasswordForm = () => {
|
||||
type="submit"
|
||||
className="w-full cursor-pointer"
|
||||
>
|
||||
{isPending && (
|
||||
<Loader2Icon className="mr-2 size-4 animate-spin" />
|
||||
)}
|
||||
{isPending && <Loader2Icon className="mr-2 size-4 animate-spin" />}
|
||||
<span>{t('reset')}</span>
|
||||
</Button>
|
||||
</form>
|
||||
|
@ -19,13 +19,18 @@ interface SocialLoginButtonProps {
|
||||
/**
|
||||
* social login buttons
|
||||
*/
|
||||
export const SocialLoginButton = ({ callbackUrl: propCallbackUrl }: SocialLoginButtonProps) => {
|
||||
export const SocialLoginButton = ({
|
||||
callbackUrl: propCallbackUrl,
|
||||
}: SocialLoginButtonProps) => {
|
||||
const t = useTranslations('AuthPage.login');
|
||||
const searchParams = useSearchParams();
|
||||
const paramCallbackUrl = searchParams.get('callbackUrl');
|
||||
// Use prop callback URL or param callback URL if provided, otherwise use the default login redirect
|
||||
const locale = useLocale();
|
||||
const defaultCallbackUrl = getUrlWithLocaleInCallbackUrl(DEFAULT_LOGIN_REDIRECT, locale);
|
||||
const defaultCallbackUrl = getUrlWithLocaleInCallbackUrl(
|
||||
DEFAULT_LOGIN_REDIRECT,
|
||||
locale
|
||||
);
|
||||
const callbackUrl = propCallbackUrl || paramCallbackUrl || defaultCallbackUrl;
|
||||
const [isLoading, setIsLoading] = useState<'google' | 'github' | null>(null);
|
||||
console.log('social login button, callbackUrl', callbackUrl);
|
||||
|
@ -12,9 +12,7 @@ export default function CallToActionSection() {
|
||||
<h2 className="text-balance text-4xl font-semibold lg:text-5xl">
|
||||
{t('title')}
|
||||
</h2>
|
||||
<p className="mt-4">
|
||||
{t('description')}
|
||||
</p>
|
||||
<p className="mt-4">{t('description')}</p>
|
||||
|
||||
<div className="mt-12 flex flex-wrap justify-center gap-4">
|
||||
<Button asChild size="lg">
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from '@/components/ui/accordion';
|
||||
import { IconName } from 'lucide-react/dynamic';
|
||||
import type { IconName } from 'lucide-react/dynamic';
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
|
||||
type FAQItem = {
|
||||
|
@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { BorderBeam } from '@/components/magicui/border-beam';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
@ -12,11 +13,10 @@ import {
|
||||
Fingerprint,
|
||||
IdCard,
|
||||
} from 'lucide-react';
|
||||
import { AnimatePresence, motion } from 'motion/react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import Image from 'next/image';
|
||||
import { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import { BorderBeam } from '@/components/magicui/border-beam';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
/**
|
||||
* https://nsui.irung.me/features
|
||||
@ -48,15 +48,13 @@ 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">
|
||||
{t('title')}
|
||||
</h2>
|
||||
<p>
|
||||
{t('description')}
|
||||
</p>
|
||||
<p>{t('description')}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-12 sm:px-12 md:grid-cols-2 lg:grid-cols-12 md:gap-12 lg:gap-24 lg:px-0">
|
||||
|
@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { BorderBeam } from '@/components/magicui/border-beam';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
@ -12,11 +13,10 @@ import {
|
||||
Fingerprint,
|
||||
IdCard,
|
||||
} from 'lucide-react';
|
||||
import { AnimatePresence, motion } from 'motion/react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import Image from 'next/image';
|
||||
import { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import { BorderBeam } from '@/components/magicui/border-beam';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
/**
|
||||
* https://nsui.irung.me/features
|
||||
@ -54,13 +54,10 @@ export default function Features2Section() {
|
||||
<h2 className="text-balance text-4xl lg:text-5xl font-semibold">
|
||||
{t('title')}
|
||||
</h2>
|
||||
<p>
|
||||
{t('description')}
|
||||
</p>
|
||||
<p>{t('description')}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-12 sm:px-12 md:grid-cols-2 lg:grid-cols-12 md:gap-12 lg:gap-24 lg:px-0">
|
||||
|
||||
<div className="bg-background w-full relative flex overflow-hidden rounded-2xl border p-2 md:h-auto lg:col-span-7">
|
||||
<div className="aspect-76/59 bg-background relative w-full rounded-2xl">
|
||||
<AnimatePresence mode="wait">
|
||||
|
@ -1,4 +1,9 @@
|
||||
import { ActivityIcon, DraftingCompassIcon, MailIcon, ZapIcon } from 'lucide-react';
|
||||
import {
|
||||
ActivityIcon,
|
||||
DraftingCompassIcon,
|
||||
MailIcon,
|
||||
ZapIcon,
|
||||
} from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import Image from 'next/image';
|
||||
|
||||
@ -15,12 +20,8 @@ export default function Features3Section() {
|
||||
<div className="grid items-center gap-12 md:grid-cols-2 md:gap-12 lg:grid-cols-5 lg:gap-24">
|
||||
<div className="lg:col-span-2">
|
||||
<div className="md:pr-6 lg:pr-0">
|
||||
<h2 className="text-4xl font-semibold">
|
||||
{t('title')}
|
||||
</h2>
|
||||
<p className="mt-6">
|
||||
{t('description')}
|
||||
</p>
|
||||
<h2 className="text-4xl font-semibold">{t('title')}</h2>
|
||||
<p className="mt-6">{t('description')}</p>
|
||||
</div>
|
||||
|
||||
<ul className="mt-8 divide-y border-y *:flex *:items-center *:gap-3 *:py-3">
|
||||
|
@ -1,4 +1,9 @@
|
||||
import { ActivityIcon, DraftingCompassIcon, MailIcon, ZapIcon } from 'lucide-react';
|
||||
import {
|
||||
ActivityIcon,
|
||||
DraftingCompassIcon,
|
||||
MailIcon,
|
||||
ZapIcon,
|
||||
} from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import Image from 'next/image';
|
||||
|
||||
@ -36,12 +41,8 @@ export default function Features4Section() {
|
||||
|
||||
<div className="lg:col-span-2">
|
||||
<div className="md:pr-6 lg:pr-0">
|
||||
<h2 className="text-4xl font-semibold">
|
||||
{t('title')}
|
||||
</h2>
|
||||
<p className="mt-6">
|
||||
{t('description')}
|
||||
</p>
|
||||
<h2 className="text-4xl font-semibold">{t('title')}</h2>
|
||||
<p className="mt-6">{t('description')}</p>
|
||||
</div>
|
||||
|
||||
<ul className="mt-8 divide-y border-y *:flex *:items-center *:gap-3 *:py-3">
|
||||
|
@ -1,4 +1,11 @@
|
||||
import { CpuIcon, FingerprintIcon, PencilIcon, Settings2Icon, SparklesIcon, ZapIcon } from 'lucide-react';
|
||||
import {
|
||||
CpuIcon,
|
||||
FingerprintIcon,
|
||||
PencilIcon,
|
||||
Settings2Icon,
|
||||
SparklesIcon,
|
||||
ZapIcon,
|
||||
} from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
/**
|
||||
@ -15,16 +22,16 @@ export default function Features5Section() {
|
||||
<h2 className="text-balance text-4xl lg:text-5xl font-semibold">
|
||||
{t('title')}
|
||||
</h2>
|
||||
<p className="mt-4">
|
||||
{t('description')}
|
||||
</p>
|
||||
<p className="mt-4">{t('description')}</p>
|
||||
</div>
|
||||
|
||||
<div className="relative mx-auto grid divide-x divide-y border *:p-8 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<ZapIcon className="size-4" />
|
||||
<h3 className="text-base font-medium">{t('items.item-1.title')}</h3>
|
||||
<h3 className="text-base font-medium">
|
||||
{t('items.item-1.title')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-4">
|
||||
{t('items.item-1.description')}
|
||||
@ -33,7 +40,9 @@ export default function Features5Section() {
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<CpuIcon className="size-4" />
|
||||
<h3 className="text-base font-medium">{t('items.item-2.title')}</h3>
|
||||
<h3 className="text-base font-medium">
|
||||
{t('items.item-2.title')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-4">
|
||||
{t('items.item-2.description')}
|
||||
@ -43,7 +52,9 @@ export default function Features5Section() {
|
||||
<div className="flex items-center gap-2">
|
||||
<FingerprintIcon className="size-4" />
|
||||
|
||||
<h3 className="text-base font-medium">{t('items.item-3.title')}</h3>
|
||||
<h3 className="text-base font-medium">
|
||||
{t('items.item-3.title')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-4">
|
||||
{t('items.item-3.description')}
|
||||
@ -53,7 +64,9 @@ export default function Features5Section() {
|
||||
<div className="flex items-center gap-2">
|
||||
<PencilIcon className="size-4" />
|
||||
|
||||
<h3 className="text-base font-medium">{t('items.item-4.title')}</h3>
|
||||
<h3 className="text-base font-medium">
|
||||
{t('items.item-4.title')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-4">
|
||||
{t('items.item-4.description')}
|
||||
@ -63,7 +76,9 @@ export default function Features5Section() {
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings2Icon className="size-4" />
|
||||
|
||||
<h3 className="text-base font-medium">{t('items.item-5.title')}</h3>
|
||||
<h3 className="text-base font-medium">
|
||||
{t('items.item-5.title')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-4">
|
||||
{t('items.item-5.description')}
|
||||
@ -73,7 +88,9 @@ export default function Features5Section() {
|
||||
<div className="flex items-center gap-2">
|
||||
<SparklesIcon className="size-4" />
|
||||
|
||||
<h3 className="text-base font-medium">{t('items.item-6.title')}</h3>
|
||||
<h3 className="text-base font-medium">
|
||||
{t('items.item-6.title')}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-4">
|
||||
{t('items.item-6.description')}
|
||||
|
@ -119,9 +119,7 @@ export default function HeroSection() {
|
||||
className="rounded-xl px-5 text-base"
|
||||
>
|
||||
<LocaleLink href={linkPrimary}>
|
||||
<span className="text-nowrap">
|
||||
{t('primary')}
|
||||
</span>
|
||||
<span className="text-nowrap">{t('primary')}</span>
|
||||
</LocaleLink>
|
||||
</Button>
|
||||
</div>
|
||||
@ -133,9 +131,7 @@ export default function HeroSection() {
|
||||
className="h-10.5 rounded-xl px-5"
|
||||
>
|
||||
<LocaleLink href={linkSecondary}>
|
||||
<span className="text-nowrap">
|
||||
{t('secondary')}
|
||||
</span>
|
||||
<span className="text-nowrap">{t('secondary')}</span>
|
||||
</LocaleLink>
|
||||
</Button>
|
||||
</AnimatedGroup>
|
||||
|
@ -11,7 +11,7 @@ import { Card } from '@/components/ui/card';
|
||||
import { LocaleLink } from '@/i18n/navigation';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import * as React from 'react';
|
||||
import type * as React from 'react';
|
||||
|
||||
export default function IntegrationSection() {
|
||||
const t = useTranslations('HomePage.integration');
|
||||
@ -24,9 +24,7 @@ export default function IntegrationSection() {
|
||||
<h2 className="text-balance text-3xl font-semibold md:text-4xl">
|
||||
{t('title')}
|
||||
</h2>
|
||||
<p className="text-muted-foreground mt-6">
|
||||
{t('description')}
|
||||
</p>
|
||||
<p className="text-muted-foreground mt-6">{t('description')}</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-12 grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
|
@ -59,9 +59,7 @@ export default function Integration2Section() {
|
||||
<h2 className="text-balance text-3xl font-semibold md:text-4xl">
|
||||
{t('title')}
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
{t('description')}
|
||||
</p>
|
||||
<p className="text-muted-foreground">{t('description')}</p>
|
||||
|
||||
<div className="mt-12 flex flex-wrap justify-start gap-4">
|
||||
<Button asChild size="lg">
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export default function LogoCloudSection() {
|
||||
const t = useTranslations('HomePage.logocloud');
|
||||
@ -6,9 +6,7 @@ export default function LogoCloudSection() {
|
||||
return (
|
||||
<section className="py-16">
|
||||
<div className="mx-auto max-w-5xl px-6">
|
||||
<h2 className="text-center text-xl font-medium">
|
||||
{t('title')}
|
||||
</h2>
|
||||
<h2 className="text-center text-xl font-medium">{t('title')}</h2>
|
||||
<div className="mx-auto mt-20 flex max-w-4xl flex-wrap items-center justify-center gap-x-12 gap-y-8 sm:gap-x-16 sm:gap-y-12">
|
||||
<img
|
||||
className="h-4 w-fit dark:invert"
|
||||
@ -85,4 +83,3 @@ export default function LogoCloudSection() {
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { PricingTable } from "@/components/pricing/pricing-table";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { PricingTable } from '@/components/pricing/pricing-table';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export default function PricingSection() {
|
||||
const t = useTranslations('HomePage.pricing');
|
||||
@ -11,9 +11,7 @@ export default function PricingSection() {
|
||||
<h2 className="text-balance text-4xl lg:text-5xl font-semibold">
|
||||
{t('title')}
|
||||
</h2>
|
||||
<p className="mt-4">
|
||||
{t('description')}
|
||||
</p>
|
||||
<p className="mt-4">{t('description')}</p>
|
||||
</div>
|
||||
|
||||
<PricingTable />
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export default function StatsSection() {
|
||||
const t = useTranslations('HomePage.stats');
|
||||
@ -7,12 +7,8 @@ export default function StatsSection() {
|
||||
<section className="py-12 md:py-20 w-full bg-muted dark:bg-background">
|
||||
<div className="mx-auto max-w-5xl space-y-8 px-6 md:space-y-16">
|
||||
<div className="relative z-10 mx-auto max-w-xl space-y-6 text-center">
|
||||
<h2 className="text-4xl font-medium lg:text-5xl">
|
||||
{t('title')}
|
||||
</h2>
|
||||
<p>
|
||||
{t('description')}
|
||||
</p>
|
||||
<h2 className="text-4xl font-medium lg:text-5xl">{t('title')}</h2>
|
||||
<p>{t('description')}</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-12 divide-y-0 *:text-center md:grid-cols-3 md:gap-2 md:divide-x">
|
||||
|
@ -95,7 +95,7 @@ export default function TestimonialsSection() {
|
||||
role: t('items.item-12.role'),
|
||||
image: t('items.item-12.image'),
|
||||
quote: t('items.item-12.quote'),
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
const testimonialChunks = chunkArray(
|
||||
@ -111,9 +111,7 @@ export default function TestimonialsSection() {
|
||||
<h2 className="text-title text-4xl lg:text-5xl font-semibold">
|
||||
{t('title')}
|
||||
</h2>
|
||||
<p className="text-body mt-6">
|
||||
{t('description')}
|
||||
</p>
|
||||
<p className="text-body mt-6">{t('description')}</p>
|
||||
</div>
|
||||
<div className="mt-8 grid gap-3 sm:grid-cols-2 md:mt-12 lg:grid-cols-3">
|
||||
{testimonialChunks.map((chunk, chunkIndex) => (
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { PLACEHOLDER_IMAGE } from '@/lib/constants';
|
||||
import { LocaleLink } from '@/i18n/navigation';
|
||||
import { PLACEHOLDER_IMAGE } from '@/lib/constants';
|
||||
import { formatDate } from '@/lib/formatter';
|
||||
import { Post } from 'content-collections';
|
||||
import type { Post } from 'content-collections';
|
||||
import Image from 'next/image';
|
||||
|
||||
interface BlogCardProps {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Container from '@/components/layout/container';
|
||||
import { Category } from 'content-collections';
|
||||
import type { Category } from 'content-collections';
|
||||
import { BlogCategoryListDesktop } from './blog-category-list-desktop';
|
||||
import { BlogCategoryListMobile } from './blog-category-list-mobile';
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Category } from 'content-collections';
|
||||
import { LocaleLink } from '@/i18n/navigation';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { Category } from 'content-collections';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useParams } from 'next/navigation';
|
||||
|
||||
export type BlogCategoryListDesktopProps = {
|
||||
categoryList: Category[];
|
||||
@ -52,7 +52,10 @@ export function BlogCategoryListDesktop({
|
||||
)}
|
||||
aria-label={`Toggle blog category of ${category.name}`}
|
||||
>
|
||||
<LocaleLink href={`/blog/category/${category.slug}`} className="px-4">
|
||||
<LocaleLink
|
||||
href={`/blog/category/${category.slug}`}
|
||||
className="px-4"
|
||||
>
|
||||
<h2>{category.name}</h2>
|
||||
</LocaleLink>
|
||||
</ToggleGroupItem>
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from '@/components/ui/drawer';
|
||||
import { Category } from 'content-collections';
|
||||
import type { Category } from 'content-collections';
|
||||
import { LayoutListIcon } from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useParams } from 'next/navigation';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import BlogCard, { BlogCardSkeleton } from '@/components/blog/blog-card';
|
||||
import { websiteConfig } from '@/config/website';
|
||||
import { Post } from 'content-collections';
|
||||
import type { Post } from 'content-collections';
|
||||
|
||||
interface BlogGridProps {
|
||||
posts: Post[];
|
||||
|
@ -1,4 +1,4 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import { sendMessageAction } from '@/actions/send-message';
|
||||
import { FormError } from '@/components/shared/form-error';
|
||||
@ -9,7 +9,7 @@ import {
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import {
|
||||
Form,
|
||||
@ -17,13 +17,13 @@ import {
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useTransition, useState } from 'react';
|
||||
import { useState, useTransition } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { toast } from 'sonner';
|
||||
import { z } from 'zod';
|
||||
@ -39,13 +39,8 @@ export function ContactFormCard() {
|
||||
|
||||
// Create a schema for contact form validation
|
||||
const formSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.min(3, t('nameMinLength'))
|
||||
.max(30, t('nameMaxLength')),
|
||||
email: z
|
||||
.string()
|
||||
.email(t('emailValidation')),
|
||||
name: z.string().min(3, t('nameMinLength')).max(30, t('nameMaxLength')),
|
||||
email: z.string().email(t('emailValidation')),
|
||||
message: z
|
||||
.string()
|
||||
.min(10, t('messageMinLength'))
|
||||
@ -93,12 +88,8 @@ export function ContactFormCard() {
|
||||
return (
|
||||
<Card className="mx-auto max-w-lg overflow-hidden pt-6 pb-0">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg font-semibold">
|
||||
{t('title')}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
{t('description')}
|
||||
</CardDescription>
|
||||
<CardTitle className="text-lg font-semibold">{t('title')}</CardTitle>
|
||||
<CardDescription>{t('description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col">
|
||||
@ -110,10 +101,7 @@ export function ContactFormCard() {
|
||||
<FormItem>
|
||||
<FormLabel>{t('name')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t('name')}
|
||||
{...field}
|
||||
/>
|
||||
<Input placeholder={t('name')} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -127,11 +115,7 @@ export function ContactFormCard() {
|
||||
<FormItem>
|
||||
<FormLabel>{t('email')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="email"
|
||||
placeholder={t('email')}
|
||||
{...field}
|
||||
/>
|
||||
<Input type="email" placeholder={t('email')} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -145,11 +129,7 @@ export function ContactFormCard() {
|
||||
<FormItem>
|
||||
<FormLabel>{t('message')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={t('message')}
|
||||
rows={3}
|
||||
{...field}
|
||||
/>
|
||||
<Textarea placeholder={t('message')} rows={3} {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
@ -1,7 +1,5 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import * as React from "react";
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts";
|
||||
import {
|
||||
Card,
|
||||
CardAction,
|
||||
@ -9,159 +7,158 @@ import {
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
} from '@/components/ui/card';
|
||||
import {
|
||||
ChartConfig,
|
||||
type ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/components/ui/chart";
|
||||
} from '@/components/ui/chart';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
ToggleGroup,
|
||||
ToggleGroupItem,
|
||||
} from "@/components/ui/toggle-group";
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
} from '@/components/ui/select';
|
||||
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
||||
import { useIsMobile } from '@/hooks/use-mobile';
|
||||
import * as React from 'react';
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from 'recharts';
|
||||
|
||||
export const description = "An interactive area chart"
|
||||
export const description = 'An interactive area chart';
|
||||
|
||||
const chartData = [
|
||||
{ date: "2024-04-01", desktop: 222, mobile: 150 },
|
||||
{ date: "2024-04-02", desktop: 97, mobile: 180 },
|
||||
{ date: "2024-04-03", desktop: 167, mobile: 120 },
|
||||
{ date: "2024-04-04", desktop: 242, mobile: 260 },
|
||||
{ date: "2024-04-05", desktop: 373, mobile: 290 },
|
||||
{ date: "2024-04-06", desktop: 301, mobile: 340 },
|
||||
{ date: "2024-04-07", desktop: 245, mobile: 180 },
|
||||
{ date: "2024-04-08", desktop: 409, mobile: 320 },
|
||||
{ date: "2024-04-09", desktop: 59, mobile: 110 },
|
||||
{ date: "2024-04-10", desktop: 261, mobile: 190 },
|
||||
{ date: "2024-04-11", desktop: 327, mobile: 350 },
|
||||
{ date: "2024-04-12", desktop: 292, mobile: 210 },
|
||||
{ date: "2024-04-13", desktop: 342, mobile: 380 },
|
||||
{ date: "2024-04-14", desktop: 137, mobile: 220 },
|
||||
{ date: "2024-04-15", desktop: 120, mobile: 170 },
|
||||
{ date: "2024-04-16", desktop: 138, mobile: 190 },
|
||||
{ date: "2024-04-17", desktop: 446, mobile: 360 },
|
||||
{ date: "2024-04-18", desktop: 364, mobile: 410 },
|
||||
{ date: "2024-04-19", desktop: 243, mobile: 180 },
|
||||
{ date: "2024-04-20", desktop: 89, mobile: 150 },
|
||||
{ date: "2024-04-21", desktop: 137, mobile: 200 },
|
||||
{ date: "2024-04-22", desktop: 224, mobile: 170 },
|
||||
{ date: "2024-04-23", desktop: 138, mobile: 230 },
|
||||
{ date: "2024-04-24", desktop: 387, mobile: 290 },
|
||||
{ date: "2024-04-25", desktop: 215, mobile: 250 },
|
||||
{ date: "2024-04-26", desktop: 75, mobile: 130 },
|
||||
{ date: "2024-04-27", desktop: 383, mobile: 420 },
|
||||
{ date: "2024-04-28", desktop: 122, mobile: 180 },
|
||||
{ date: "2024-04-29", desktop: 315, mobile: 240 },
|
||||
{ date: "2024-04-30", desktop: 454, mobile: 380 },
|
||||
{ date: "2024-05-01", desktop: 165, mobile: 220 },
|
||||
{ date: "2024-05-02", desktop: 293, mobile: 310 },
|
||||
{ date: "2024-05-03", desktop: 247, mobile: 190 },
|
||||
{ date: "2024-05-04", desktop: 385, mobile: 420 },
|
||||
{ date: "2024-05-05", desktop: 481, mobile: 390 },
|
||||
{ date: "2024-05-06", desktop: 498, mobile: 520 },
|
||||
{ date: "2024-05-07", desktop: 388, mobile: 300 },
|
||||
{ date: "2024-05-08", desktop: 149, mobile: 210 },
|
||||
{ date: "2024-05-09", desktop: 227, mobile: 180 },
|
||||
{ date: "2024-05-10", desktop: 293, mobile: 330 },
|
||||
{ date: "2024-05-11", desktop: 335, mobile: 270 },
|
||||
{ date: "2024-05-12", desktop: 197, mobile: 240 },
|
||||
{ date: "2024-05-13", desktop: 197, mobile: 160 },
|
||||
{ date: "2024-05-14", desktop: 448, mobile: 490 },
|
||||
{ date: "2024-05-15", desktop: 473, mobile: 380 },
|
||||
{ date: "2024-05-16", desktop: 338, mobile: 400 },
|
||||
{ date: "2024-05-17", desktop: 499, mobile: 420 },
|
||||
{ date: "2024-05-18", desktop: 315, mobile: 350 },
|
||||
{ date: "2024-05-19", desktop: 235, mobile: 180 },
|
||||
{ date: "2024-05-20", desktop: 177, mobile: 230 },
|
||||
{ date: "2024-05-21", desktop: 82, mobile: 140 },
|
||||
{ date: "2024-05-22", desktop: 81, mobile: 120 },
|
||||
{ date: "2024-05-23", desktop: 252, mobile: 290 },
|
||||
{ date: "2024-05-24", desktop: 294, mobile: 220 },
|
||||
{ date: "2024-05-25", desktop: 201, mobile: 250 },
|
||||
{ date: "2024-05-26", desktop: 213, mobile: 170 },
|
||||
{ date: "2024-05-27", desktop: 420, mobile: 460 },
|
||||
{ date: "2024-05-28", desktop: 233, mobile: 190 },
|
||||
{ date: "2024-05-29", desktop: 78, mobile: 130 },
|
||||
{ date: "2024-05-30", desktop: 340, mobile: 280 },
|
||||
{ date: "2024-05-31", desktop: 178, mobile: 230 },
|
||||
{ date: "2024-06-01", desktop: 178, mobile: 200 },
|
||||
{ date: "2024-06-02", desktop: 470, mobile: 410 },
|
||||
{ date: "2024-06-03", desktop: 103, mobile: 160 },
|
||||
{ date: "2024-06-04", desktop: 439, mobile: 380 },
|
||||
{ date: "2024-06-05", desktop: 88, mobile: 140 },
|
||||
{ date: "2024-06-06", desktop: 294, mobile: 250 },
|
||||
{ date: "2024-06-07", desktop: 323, mobile: 370 },
|
||||
{ date: "2024-06-08", desktop: 385, mobile: 320 },
|
||||
{ date: "2024-06-09", desktop: 438, mobile: 480 },
|
||||
{ date: "2024-06-10", desktop: 155, mobile: 200 },
|
||||
{ date: "2024-06-11", desktop: 92, mobile: 150 },
|
||||
{ date: "2024-06-12", desktop: 492, mobile: 420 },
|
||||
{ date: "2024-06-13", desktop: 81, mobile: 130 },
|
||||
{ date: "2024-06-14", desktop: 426, mobile: 380 },
|
||||
{ date: "2024-06-15", desktop: 307, mobile: 350 },
|
||||
{ date: "2024-06-16", desktop: 371, mobile: 310 },
|
||||
{ date: "2024-06-17", desktop: 475, mobile: 520 },
|
||||
{ date: "2024-06-18", desktop: 107, mobile: 170 },
|
||||
{ date: "2024-06-19", desktop: 341, mobile: 290 },
|
||||
{ date: "2024-06-20", desktop: 408, mobile: 450 },
|
||||
{ date: "2024-06-21", desktop: 169, mobile: 210 },
|
||||
{ date: "2024-06-22", desktop: 317, mobile: 270 },
|
||||
{ date: "2024-06-23", desktop: 480, mobile: 530 },
|
||||
{ date: "2024-06-24", desktop: 132, mobile: 180 },
|
||||
{ date: "2024-06-25", desktop: 141, mobile: 190 },
|
||||
{ date: "2024-06-26", desktop: 434, mobile: 380 },
|
||||
{ date: "2024-06-27", desktop: 448, mobile: 490 },
|
||||
{ date: "2024-06-28", desktop: 149, mobile: 200 },
|
||||
{ date: "2024-06-29", desktop: 103, mobile: 160 },
|
||||
{ date: "2024-06-30", desktop: 446, mobile: 400 },
|
||||
]
|
||||
{ date: '2024-04-01', desktop: 222, mobile: 150 },
|
||||
{ date: '2024-04-02', desktop: 97, mobile: 180 },
|
||||
{ date: '2024-04-03', desktop: 167, mobile: 120 },
|
||||
{ date: '2024-04-04', desktop: 242, mobile: 260 },
|
||||
{ date: '2024-04-05', desktop: 373, mobile: 290 },
|
||||
{ date: '2024-04-06', desktop: 301, mobile: 340 },
|
||||
{ date: '2024-04-07', desktop: 245, mobile: 180 },
|
||||
{ date: '2024-04-08', desktop: 409, mobile: 320 },
|
||||
{ date: '2024-04-09', desktop: 59, mobile: 110 },
|
||||
{ date: '2024-04-10', desktop: 261, mobile: 190 },
|
||||
{ date: '2024-04-11', desktop: 327, mobile: 350 },
|
||||
{ date: '2024-04-12', desktop: 292, mobile: 210 },
|
||||
{ date: '2024-04-13', desktop: 342, mobile: 380 },
|
||||
{ date: '2024-04-14', desktop: 137, mobile: 220 },
|
||||
{ date: '2024-04-15', desktop: 120, mobile: 170 },
|
||||
{ date: '2024-04-16', desktop: 138, mobile: 190 },
|
||||
{ date: '2024-04-17', desktop: 446, mobile: 360 },
|
||||
{ date: '2024-04-18', desktop: 364, mobile: 410 },
|
||||
{ date: '2024-04-19', desktop: 243, mobile: 180 },
|
||||
{ date: '2024-04-20', desktop: 89, mobile: 150 },
|
||||
{ date: '2024-04-21', desktop: 137, mobile: 200 },
|
||||
{ date: '2024-04-22', desktop: 224, mobile: 170 },
|
||||
{ date: '2024-04-23', desktop: 138, mobile: 230 },
|
||||
{ date: '2024-04-24', desktop: 387, mobile: 290 },
|
||||
{ date: '2024-04-25', desktop: 215, mobile: 250 },
|
||||
{ date: '2024-04-26', desktop: 75, mobile: 130 },
|
||||
{ date: '2024-04-27', desktop: 383, mobile: 420 },
|
||||
{ date: '2024-04-28', desktop: 122, mobile: 180 },
|
||||
{ date: '2024-04-29', desktop: 315, mobile: 240 },
|
||||
{ date: '2024-04-30', desktop: 454, mobile: 380 },
|
||||
{ date: '2024-05-01', desktop: 165, mobile: 220 },
|
||||
{ date: '2024-05-02', desktop: 293, mobile: 310 },
|
||||
{ date: '2024-05-03', desktop: 247, mobile: 190 },
|
||||
{ date: '2024-05-04', desktop: 385, mobile: 420 },
|
||||
{ date: '2024-05-05', desktop: 481, mobile: 390 },
|
||||
{ date: '2024-05-06', desktop: 498, mobile: 520 },
|
||||
{ date: '2024-05-07', desktop: 388, mobile: 300 },
|
||||
{ date: '2024-05-08', desktop: 149, mobile: 210 },
|
||||
{ date: '2024-05-09', desktop: 227, mobile: 180 },
|
||||
{ date: '2024-05-10', desktop: 293, mobile: 330 },
|
||||
{ date: '2024-05-11', desktop: 335, mobile: 270 },
|
||||
{ date: '2024-05-12', desktop: 197, mobile: 240 },
|
||||
{ date: '2024-05-13', desktop: 197, mobile: 160 },
|
||||
{ date: '2024-05-14', desktop: 448, mobile: 490 },
|
||||
{ date: '2024-05-15', desktop: 473, mobile: 380 },
|
||||
{ date: '2024-05-16', desktop: 338, mobile: 400 },
|
||||
{ date: '2024-05-17', desktop: 499, mobile: 420 },
|
||||
{ date: '2024-05-18', desktop: 315, mobile: 350 },
|
||||
{ date: '2024-05-19', desktop: 235, mobile: 180 },
|
||||
{ date: '2024-05-20', desktop: 177, mobile: 230 },
|
||||
{ date: '2024-05-21', desktop: 82, mobile: 140 },
|
||||
{ date: '2024-05-22', desktop: 81, mobile: 120 },
|
||||
{ date: '2024-05-23', desktop: 252, mobile: 290 },
|
||||
{ date: '2024-05-24', desktop: 294, mobile: 220 },
|
||||
{ date: '2024-05-25', desktop: 201, mobile: 250 },
|
||||
{ date: '2024-05-26', desktop: 213, mobile: 170 },
|
||||
{ date: '2024-05-27', desktop: 420, mobile: 460 },
|
||||
{ date: '2024-05-28', desktop: 233, mobile: 190 },
|
||||
{ date: '2024-05-29', desktop: 78, mobile: 130 },
|
||||
{ date: '2024-05-30', desktop: 340, mobile: 280 },
|
||||
{ date: '2024-05-31', desktop: 178, mobile: 230 },
|
||||
{ date: '2024-06-01', desktop: 178, mobile: 200 },
|
||||
{ date: '2024-06-02', desktop: 470, mobile: 410 },
|
||||
{ date: '2024-06-03', desktop: 103, mobile: 160 },
|
||||
{ date: '2024-06-04', desktop: 439, mobile: 380 },
|
||||
{ date: '2024-06-05', desktop: 88, mobile: 140 },
|
||||
{ date: '2024-06-06', desktop: 294, mobile: 250 },
|
||||
{ date: '2024-06-07', desktop: 323, mobile: 370 },
|
||||
{ date: '2024-06-08', desktop: 385, mobile: 320 },
|
||||
{ date: '2024-06-09', desktop: 438, mobile: 480 },
|
||||
{ date: '2024-06-10', desktop: 155, mobile: 200 },
|
||||
{ date: '2024-06-11', desktop: 92, mobile: 150 },
|
||||
{ date: '2024-06-12', desktop: 492, mobile: 420 },
|
||||
{ date: '2024-06-13', desktop: 81, mobile: 130 },
|
||||
{ date: '2024-06-14', desktop: 426, mobile: 380 },
|
||||
{ date: '2024-06-15', desktop: 307, mobile: 350 },
|
||||
{ date: '2024-06-16', desktop: 371, mobile: 310 },
|
||||
{ date: '2024-06-17', desktop: 475, mobile: 520 },
|
||||
{ date: '2024-06-18', desktop: 107, mobile: 170 },
|
||||
{ date: '2024-06-19', desktop: 341, mobile: 290 },
|
||||
{ date: '2024-06-20', desktop: 408, mobile: 450 },
|
||||
{ date: '2024-06-21', desktop: 169, mobile: 210 },
|
||||
{ date: '2024-06-22', desktop: 317, mobile: 270 },
|
||||
{ date: '2024-06-23', desktop: 480, mobile: 530 },
|
||||
{ date: '2024-06-24', desktop: 132, mobile: 180 },
|
||||
{ date: '2024-06-25', desktop: 141, mobile: 190 },
|
||||
{ date: '2024-06-26', desktop: 434, mobile: 380 },
|
||||
{ date: '2024-06-27', desktop: 448, mobile: 490 },
|
||||
{ date: '2024-06-28', desktop: 149, mobile: 200 },
|
||||
{ date: '2024-06-29', desktop: 103, mobile: 160 },
|
||||
{ date: '2024-06-30', desktop: 446, mobile: 400 },
|
||||
];
|
||||
|
||||
const chartConfig = {
|
||||
visitors: {
|
||||
label: "Visitors",
|
||||
label: 'Visitors',
|
||||
},
|
||||
desktop: {
|
||||
label: "Desktop",
|
||||
color: "var(--primary)",
|
||||
label: 'Desktop',
|
||||
color: 'var(--primary)',
|
||||
},
|
||||
mobile: {
|
||||
label: "Mobile",
|
||||
color: "var(--primary)",
|
||||
label: 'Mobile',
|
||||
color: 'var(--primary)',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
} satisfies ChartConfig;
|
||||
|
||||
export function ChartAreaInteractive() {
|
||||
const isMobile = useIsMobile()
|
||||
const [timeRange, setTimeRange] = React.useState("90d")
|
||||
const isMobile = useIsMobile();
|
||||
const [timeRange, setTimeRange] = React.useState('90d');
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isMobile) {
|
||||
setTimeRange("7d")
|
||||
setTimeRange('7d');
|
||||
}
|
||||
}, [isMobile])
|
||||
}, [isMobile]);
|
||||
|
||||
const filteredData = chartData.filter((item) => {
|
||||
const date = new Date(item.date)
|
||||
const referenceDate = new Date("2024-06-30")
|
||||
let daysToSubtract = 90
|
||||
if (timeRange === "30d") {
|
||||
daysToSubtract = 30
|
||||
} else if (timeRange === "7d") {
|
||||
daysToSubtract = 7
|
||||
const date = new Date(item.date);
|
||||
const referenceDate = new Date('2024-06-30');
|
||||
let daysToSubtract = 90;
|
||||
if (timeRange === '30d') {
|
||||
daysToSubtract = 30;
|
||||
} else if (timeRange === '7d') {
|
||||
daysToSubtract = 7;
|
||||
}
|
||||
const startDate = new Date(referenceDate)
|
||||
startDate.setDate(startDate.getDate() - daysToSubtract)
|
||||
return date >= startDate
|
||||
})
|
||||
const startDate = new Date(referenceDate);
|
||||
startDate.setDate(startDate.getDate() - daysToSubtract);
|
||||
return date >= startDate;
|
||||
});
|
||||
|
||||
return (
|
||||
<Card className="@container/card">
|
||||
@ -247,11 +244,11 @@ export function ChartAreaInteractive() {
|
||||
tickMargin={8}
|
||||
minTickGap={32}
|
||||
tickFormatter={(value) => {
|
||||
const date = new Date(value)
|
||||
return date.toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
})
|
||||
const date = new Date(value);
|
||||
return date.toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<ChartTooltip
|
||||
@ -260,10 +257,10 @@ export function ChartAreaInteractive() {
|
||||
content={
|
||||
<ChartTooltipContent
|
||||
labelFormatter={(value) => {
|
||||
return new Date(value).toLocaleDateString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
})
|
||||
return new Date(value).toLocaleDateString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
});
|
||||
}}
|
||||
indicator="dot"
|
||||
/>
|
||||
@ -287,5 +284,5 @@ export function ChartAreaInteractive() {
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
} from '@/components/ui/breadcrumb';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { SidebarTrigger } from '@/components/ui/sidebar';
|
||||
import React, { ReactNode } from 'react';
|
||||
import React, { type ReactNode } from 'react';
|
||||
import LocaleSwitcher from '../layout/locale-switcher';
|
||||
import { ModeSwitcher } from '../layout/mode-switcher';
|
||||
import { ThemeSelector } from '../layout/theme-selector';
|
||||
@ -25,7 +25,10 @@ interface DashboardHeaderProps {
|
||||
/**
|
||||
* Dashboard header
|
||||
*/
|
||||
export function DashboardHeader({ breadcrumbs, actions }: DashboardHeaderProps) {
|
||||
export function DashboardHeader({
|
||||
breadcrumbs,
|
||||
actions,
|
||||
}: DashboardHeaderProps) {
|
||||
return (
|
||||
<header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
|
||||
<div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
|
||||
@ -40,11 +43,16 @@ export function DashboardHeader({ breadcrumbs, actions }: DashboardHeaderProps)
|
||||
{breadcrumbs.map((item, index) => (
|
||||
<React.Fragment key={`breadcrumb-${index}`}>
|
||||
{index > 0 && (
|
||||
<BreadcrumbSeparator key={`sep-${index}`} className="hidden md:block" />
|
||||
<BreadcrumbSeparator
|
||||
key={`sep-${index}`}
|
||||
className="hidden md:block"
|
||||
/>
|
||||
)}
|
||||
<BreadcrumbItem
|
||||
key={`item-${index}`}
|
||||
className={index < breadcrumbs.length - 1 ? "hidden md:block" : ""}
|
||||
className={
|
||||
index < breadcrumbs.length - 1 ? 'hidden md:block' : ''
|
||||
}
|
||||
>
|
||||
{item.isCurrentPage ? (
|
||||
<BreadcrumbPage>{item.label}</BreadcrumbPage>
|
||||
|
@ -9,21 +9,23 @@ import {
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem
|
||||
SidebarMenuItem,
|
||||
} from '@/components/ui/sidebar';
|
||||
import { getSidebarLinks } from '@/config/sidebar-config';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { LocaleLink } from '@/i18n/navigation';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { Routes } from '@/routes';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import * as React from 'react';
|
||||
import type * as React from 'react';
|
||||
import { Logo } from '../layout/logo';
|
||||
import { UpgradeCard } from './upgrade-card';
|
||||
|
||||
/**
|
||||
* Dashboard sidebar
|
||||
*/
|
||||
export function DashboardSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
export function DashboardSidebar({
|
||||
...props
|
||||
}: React.ComponentProps<typeof Sidebar>) {
|
||||
const t = useTranslations();
|
||||
const sidebarLinks = getSidebarLinks();
|
||||
const { data: session, isPending } = authClient.useSession();
|
||||
|
@ -1,25 +1,24 @@
|
||||
"use client"
|
||||
'use client';
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
DndContext,
|
||||
type DragEndEvent,
|
||||
KeyboardSensor,
|
||||
MouseSensor,
|
||||
TouchSensor,
|
||||
type UniqueIdentifier,
|
||||
closestCenter,
|
||||
useSensor,
|
||||
useSensors,
|
||||
type DragEndEvent,
|
||||
type UniqueIdentifier,
|
||||
} from "@dnd-kit/core"
|
||||
import { restrictToVerticalAxis } from "@dnd-kit/modifiers"
|
||||
} from '@dnd-kit/core';
|
||||
import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
|
||||
import {
|
||||
SortableContext,
|
||||
arrayMove,
|
||||
useSortable,
|
||||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable"
|
||||
import { CSS } from "@dnd-kit/utilities"
|
||||
} from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import {
|
||||
IconChevronDown,
|
||||
IconChevronLeft,
|
||||
@ -33,13 +32,13 @@ import {
|
||||
IconLoader,
|
||||
IconPlus,
|
||||
IconTrendingUp,
|
||||
} from "@tabler/icons-react"
|
||||
} from '@tabler/icons-react';
|
||||
import {
|
||||
ColumnDef,
|
||||
ColumnFiltersState,
|
||||
Row,
|
||||
SortingState,
|
||||
VisibilityState,
|
||||
type ColumnDef,
|
||||
type ColumnFiltersState,
|
||||
type Row,
|
||||
type SortingState,
|
||||
type VisibilityState,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFacetedRowModel,
|
||||
@ -48,21 +47,21 @@ import {
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table"
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
||||
import { toast } from "sonner"
|
||||
import { z } from "zod"
|
||||
} from '@tanstack/react-table';
|
||||
import * as React from 'react';
|
||||
import { Area, AreaChart, CartesianGrid, XAxis } from 'recharts';
|
||||
import { toast } from 'sonner';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useIsMobile } from "@/hooks/use-mobile"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
ChartConfig,
|
||||
type ChartConfig,
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/components/ui/chart"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
} from '@/components/ui/chart';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
@ -72,7 +71,7 @@ import {
|
||||
DrawerHeader,
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from "@/components/ui/drawer"
|
||||
} from '@/components/ui/drawer';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
@ -80,17 +79,17 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
} from '@/components/ui/select';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@ -98,13 +97,9 @@ import {
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table"
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/components/ui/tabs"
|
||||
} from '@/components/ui/table';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { useIsMobile } from '@/hooks/use-mobile';
|
||||
|
||||
export const schema = z.object({
|
||||
id: z.number(),
|
||||
@ -114,13 +109,13 @@ export const schema = z.object({
|
||||
target: z.string(),
|
||||
limit: z.string(),
|
||||
reviewer: z.string(),
|
||||
})
|
||||
});
|
||||
|
||||
// Create a separate component for the drag handle
|
||||
function DragHandle({ id }: { id: number }) {
|
||||
const { attributes, listeners } = useSortable({
|
||||
id,
|
||||
})
|
||||
});
|
||||
|
||||
return (
|
||||
<Button
|
||||
@ -133,23 +128,23 @@ function DragHandle({ id }: { id: number }) {
|
||||
<IconGripVertical className="text-muted-foreground size-3" />
|
||||
<span className="sr-only">Drag to reorder</span>
|
||||
</Button>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||
{
|
||||
id: "drag",
|
||||
id: 'drag',
|
||||
header: () => null,
|
||||
cell: ({ row }) => <DragHandle id={row.original.id} />,
|
||||
},
|
||||
{
|
||||
id: "select",
|
||||
id: 'select',
|
||||
header: ({ table }) => (
|
||||
<div className="flex items-center justify-center">
|
||||
<Checkbox
|
||||
checked={
|
||||
table.getIsAllPageRowsSelected() ||
|
||||
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||
(table.getIsSomePageRowsSelected() && 'indeterminate')
|
||||
}
|
||||
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||
aria-label="Select all"
|
||||
@ -169,16 +164,16 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: "header",
|
||||
header: "Header",
|
||||
accessorKey: 'header',
|
||||
header: 'Header',
|
||||
cell: ({ row }) => {
|
||||
return <TableCellViewer item={row.original} />
|
||||
return <TableCellViewer item={row.original} />;
|
||||
},
|
||||
enableHiding: false,
|
||||
},
|
||||
{
|
||||
accessorKey: "type",
|
||||
header: "Section Type",
|
||||
accessorKey: 'type',
|
||||
header: 'Section Type',
|
||||
cell: ({ row }) => (
|
||||
<div className="w-32">
|
||||
<Badge variant="outline" className="text-muted-foreground px-1.5">
|
||||
@ -188,11 +183,11 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "status",
|
||||
header: "Status",
|
||||
accessorKey: 'status',
|
||||
header: 'Status',
|
||||
cell: ({ row }) => (
|
||||
<Badge variant="outline" className="text-muted-foreground px-1.5">
|
||||
{row.original.status === "Done" ? (
|
||||
{row.original.status === 'Done' ? (
|
||||
<IconCircleCheckFilled className="fill-green-500 dark:fill-green-400" />
|
||||
) : (
|
||||
<IconLoader />
|
||||
@ -202,17 +197,17 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "target",
|
||||
accessorKey: 'target',
|
||||
header: () => <div className="w-full text-right">Target</div>,
|
||||
cell: ({ row }) => (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
e.preventDefault();
|
||||
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
|
||||
loading: `Saving ${row.original.header}`,
|
||||
success: "Done",
|
||||
error: "Error",
|
||||
})
|
||||
success: 'Done',
|
||||
error: 'Error',
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Label htmlFor={`${row.original.id}-target`} className="sr-only">
|
||||
@ -227,17 +222,17 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "limit",
|
||||
accessorKey: 'limit',
|
||||
header: () => <div className="w-full text-right">Limit</div>,
|
||||
cell: ({ row }) => (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
e.preventDefault();
|
||||
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
|
||||
loading: `Saving ${row.original.header}`,
|
||||
success: "Done",
|
||||
error: "Error",
|
||||
})
|
||||
success: 'Done',
|
||||
error: 'Error',
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Label htmlFor={`${row.original.id}-limit`} className="sr-only">
|
||||
@ -252,13 +247,13 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "reviewer",
|
||||
header: "Reviewer",
|
||||
accessorKey: 'reviewer',
|
||||
header: 'Reviewer',
|
||||
cell: ({ row }) => {
|
||||
const isAssigned = row.original.reviewer !== "Assign reviewer"
|
||||
const isAssigned = row.original.reviewer !== 'Assign reviewer';
|
||||
|
||||
if (isAssigned) {
|
||||
return row.original.reviewer
|
||||
return row.original.reviewer;
|
||||
}
|
||||
|
||||
return (
|
||||
@ -282,11 +277,11 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</>
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
id: 'actions',
|
||||
cell: () => (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@ -309,16 +304,16 @@ const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||
</DropdownMenu>
|
||||
),
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
function DraggableRow({ row }: { row: Row<z.infer<typeof schema>> }) {
|
||||
const { transform, transition, setNodeRef, isDragging } = useSortable({
|
||||
id: row.original.id,
|
||||
})
|
||||
});
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
data-state={row.getIsSelected() && 'selected'}
|
||||
data-dragging={isDragging}
|
||||
ref={setNodeRef}
|
||||
className="relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80"
|
||||
@ -333,37 +328,37 @@ function DraggableRow({ row }: { row: Row<z.infer<typeof schema>> }) {
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function DataTable({
|
||||
data: initialData,
|
||||
}: {
|
||||
data: z.infer<typeof schema>[]
|
||||
data: z.infer<typeof schema>[];
|
||||
}) {
|
||||
const [data, setData] = React.useState(() => initialData)
|
||||
const [rowSelection, setRowSelection] = React.useState({})
|
||||
const [data, setData] = React.useState(() => initialData);
|
||||
const [rowSelection, setRowSelection] = React.useState({});
|
||||
const [columnVisibility, setColumnVisibility] =
|
||||
React.useState<VisibilityState>({})
|
||||
React.useState<VisibilityState>({});
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
[]
|
||||
)
|
||||
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||
);
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [pagination, setPagination] = React.useState({
|
||||
pageIndex: 0,
|
||||
pageSize: 10,
|
||||
})
|
||||
const sortableId = React.useId()
|
||||
});
|
||||
const sortableId = React.useId();
|
||||
const sensors = useSensors(
|
||||
useSensor(MouseSensor, {}),
|
||||
useSensor(TouchSensor, {}),
|
||||
useSensor(KeyboardSensor, {})
|
||||
)
|
||||
);
|
||||
|
||||
const dataIds = React.useMemo<UniqueIdentifier[]>(
|
||||
() => data?.map(({ id }) => id) || [],
|
||||
[data]
|
||||
)
|
||||
);
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
@ -388,16 +383,16 @@ export function DataTable({
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFacetedRowModel: getFacetedRowModel(),
|
||||
getFacetedUniqueValues: getFacetedUniqueValues(),
|
||||
})
|
||||
});
|
||||
|
||||
function handleDragEnd(event: DragEndEvent) {
|
||||
const { active, over } = event
|
||||
const { active, over } = event;
|
||||
if (active && over && active.id !== over.id) {
|
||||
setData((data) => {
|
||||
const oldIndex = dataIds.indexOf(active.id)
|
||||
const newIndex = dataIds.indexOf(over.id)
|
||||
return arrayMove(data, oldIndex, newIndex)
|
||||
})
|
||||
const oldIndex = dataIds.indexOf(active.id);
|
||||
const newIndex = dataIds.indexOf(over.id);
|
||||
return arrayMove(data, oldIndex, newIndex);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -450,7 +445,7 @@ export function DataTable({
|
||||
.getAllColumns()
|
||||
.filter(
|
||||
(column) =>
|
||||
typeof column.accessorFn !== "undefined" &&
|
||||
typeof column.accessorFn !== 'undefined' &&
|
||||
column.getCanHide()
|
||||
)
|
||||
.map((column) => {
|
||||
@ -465,7 +460,7 @@ export function DataTable({
|
||||
>
|
||||
{column.id}
|
||||
</DropdownMenuCheckboxItem>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@ -501,7 +496,7 @@ export function DataTable({
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
@ -532,7 +527,7 @@ export function DataTable({
|
||||
</div>
|
||||
<div className="flex items-center justify-between px-4">
|
||||
<div className="text-muted-foreground hidden flex-1 text-sm lg:flex">
|
||||
{table.getFilteredSelectedRowModel().rows.length} of{" "}
|
||||
{table.getFilteredSelectedRowModel().rows.length} of{' '}
|
||||
{table.getFilteredRowModel().rows.length} row(s) selected.
|
||||
</div>
|
||||
<div className="flex w-full items-center gap-8 lg:w-fit">
|
||||
@ -543,7 +538,7 @@ export function DataTable({
|
||||
<Select
|
||||
value={`${table.getState().pagination.pageSize}`}
|
||||
onValueChange={(value) => {
|
||||
table.setPageSize(Number(value))
|
||||
table.setPageSize(Number(value));
|
||||
}}
|
||||
>
|
||||
<SelectTrigger size="sm" className="w-20" id="rows-per-page">
|
||||
@ -561,7 +556,7 @@ export function DataTable({
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex w-fit items-center justify-center text-sm font-medium">
|
||||
Page {table.getState().pagination.pageIndex + 1} of{" "}
|
||||
Page {table.getState().pagination.pageIndex + 1} of{' '}
|
||||
{table.getPageCount()}
|
||||
</div>
|
||||
<div className="ml-auto flex items-center gap-2 lg:ml-0">
|
||||
@ -624,34 +619,34 @@ export function DataTable({
|
||||
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const chartData = [
|
||||
{ month: "January", desktop: 186, mobile: 80 },
|
||||
{ month: "February", desktop: 305, mobile: 200 },
|
||||
{ month: "March", desktop: 237, mobile: 120 },
|
||||
{ month: "April", desktop: 73, mobile: 190 },
|
||||
{ month: "May", desktop: 209, mobile: 130 },
|
||||
{ month: "June", desktop: 214, mobile: 140 },
|
||||
]
|
||||
{ month: 'January', desktop: 186, mobile: 80 },
|
||||
{ month: 'February', desktop: 305, mobile: 200 },
|
||||
{ month: 'March', desktop: 237, mobile: 120 },
|
||||
{ month: 'April', desktop: 73, mobile: 190 },
|
||||
{ month: 'May', desktop: 209, mobile: 130 },
|
||||
{ month: 'June', desktop: 214, mobile: 140 },
|
||||
];
|
||||
|
||||
const chartConfig = {
|
||||
desktop: {
|
||||
label: "Desktop",
|
||||
color: "var(--primary)",
|
||||
label: 'Desktop',
|
||||
color: 'var(--primary)',
|
||||
},
|
||||
mobile: {
|
||||
label: "Mobile",
|
||||
color: "var(--primary)",
|
||||
label: 'Mobile',
|
||||
color: 'var(--primary)',
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
} satisfies ChartConfig;
|
||||
|
||||
function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {
|
||||
const isMobile = useIsMobile()
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<Drawer direction={isMobile ? "bottom" : "right"}>
|
||||
<Drawer direction={isMobile ? 'bottom' : 'right'}>
|
||||
<DrawerTrigger asChild>
|
||||
<Button variant="link" className="text-foreground w-fit px-0 text-left">
|
||||
{item.header}
|
||||
@ -710,7 +705,7 @@ function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {
|
||||
<Separator />
|
||||
<div className="grid gap-2">
|
||||
<div className="flex gap-2 leading-none font-medium">
|
||||
Trending up by 5.2% this month{" "}
|
||||
Trending up by 5.2% this month{' '}
|
||||
<IconTrendingUp className="size-4" />
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
@ -803,5 +798,5 @@ function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { IconTrendingDown, IconTrendingUp } from "@tabler/icons-react"
|
||||
import { IconTrendingDown, IconTrendingUp } from '@tabler/icons-react';
|
||||
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import {
|
||||
Card,
|
||||
CardAction,
|
||||
@ -8,7 +8,7 @@ import {
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card"
|
||||
} from '@/components/ui/card';
|
||||
|
||||
export function SectionCards() {
|
||||
return (
|
||||
@ -98,5 +98,5 @@ export function SectionCards() {
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
"use client";
|
||||
'use client';
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
@ -7,14 +7,14 @@ import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { LocaleLink, useLocalePathname } from "@/i18n/navigation";
|
||||
import { NestedMenuItem } from "@/types";
|
||||
} from '@/components/ui/sidebar';
|
||||
import { LocaleLink, useLocalePathname } from '@/i18n/navigation';
|
||||
import type { NestedMenuItem } from '@/types';
|
||||
|
||||
/**
|
||||
* Main navigation for the dashboard sidebar
|
||||
*/
|
||||
export function SidebarMain({ items, }: { items: NestedMenuItem[] }) {
|
||||
export function SidebarMain({ items }: { items: NestedMenuItem[] }) {
|
||||
const pathname = useLocalePathname();
|
||||
|
||||
// Function to check if a path is active
|
||||
@ -34,7 +34,10 @@ export function SidebarMain({ items, }: { items: NestedMenuItem[] }) {
|
||||
<SidebarMenu>
|
||||
{item.items.map((subItem) => (
|
||||
<SidebarMenuItem key={subItem.title}>
|
||||
<SidebarMenuButton asChild isActive={isActive(subItem.href)}>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
isActive={isActive(subItem.href)}
|
||||
>
|
||||
<LocaleLink href={subItem.href || ''}>
|
||||
{subItem.icon ? subItem.icon : null}
|
||||
<span className="truncate font-medium text-sm">
|
||||
@ -68,5 +71,5 @@ export function SidebarMain({ items, }: { items: NestedMenuItem[] }) {
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user