Merge remote-tracking branch 'origin/main' into cloudflare
This commit is contained in:
commit
63dd4e52fb
@ -2,6 +2,7 @@
|
||||
|
||||
import { getDb } from '@/db';
|
||||
import { user } from '@/db/schema';
|
||||
import { isDemoWebsite } from '@/lib/demo';
|
||||
import { asc, desc, ilike, or, sql } from 'drizzle-orm';
|
||||
import { createSafeActionClient } from 'next-safe-action';
|
||||
import { z } from 'zod';
|
||||
@ -75,7 +76,8 @@ export const getUsersAction = actionClient
|
||||
]);
|
||||
|
||||
// hide user data in demo website
|
||||
if (process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true') {
|
||||
const isDemo = isDemoWebsite();
|
||||
if (isDemo) {
|
||||
items = items.map((item) => ({
|
||||
...item,
|
||||
name: 'Demo User',
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { DashboardHeader } from '@/components/dashboard/dashboard-header';
|
||||
import { isDemoWebsite } from '@/lib/demo';
|
||||
import { getSession } from '@/lib/server';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { notFound } from 'next/navigation';
|
||||
@ -9,7 +10,7 @@ interface UsersLayoutProps {
|
||||
|
||||
export default async function UsersLayout({ children }: UsersLayoutProps) {
|
||||
// if is demo website, allow user to access admin and user pages, but data is fake
|
||||
const isDemo = process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true';
|
||||
const isDemo = isDemoWebsite();
|
||||
// Check if user is admin
|
||||
const session = await getSession();
|
||||
if (!session || (session.user.role !== 'admin' && !isDemo)) {
|
||||
|
@ -1,20 +1,15 @@
|
||||
import { UpdateAvatarCard } from '@/components/settings/profile/update-avatar-card';
|
||||
import { UpdateNameCard } from '@/components/settings/profile/update-name-card';
|
||||
import { websiteConfig } from '@/config/website';
|
||||
|
||||
export default function ProfilePage() {
|
||||
const enableUpdateAvatar = websiteConfig.features.enableUpdateAvatar;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8">
|
||||
{enableUpdateAvatar && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<UpdateAvatarCard />
|
||||
</div>
|
||||
)}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<UpdateNameCard />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<UpdateAvatarCard />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -14,8 +14,6 @@ type Href = Parameters<typeof getLocalePathname>[0]['href'];
|
||||
const staticRoutes = [
|
||||
'/',
|
||||
'/pricing',
|
||||
'/blog',
|
||||
'/docs',
|
||||
'/about',
|
||||
'/contact',
|
||||
'/waitlist',
|
||||
@ -25,6 +23,8 @@ const staticRoutes = [
|
||||
'/cookie',
|
||||
'/auth/login',
|
||||
'/auth/register',
|
||||
...(websiteConfig.blog.enable ? ['/blog'] : []),
|
||||
...(websiteConfig.docs.enable ? ['/docs'] : []),
|
||||
];
|
||||
|
||||
/**
|
||||
@ -48,101 +48,106 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||
})
|
||||
);
|
||||
|
||||
// add categories
|
||||
sitemapList.push(
|
||||
...categorySource.getPages().flatMap((category) =>
|
||||
routing.locales.map((locale) => ({
|
||||
url: getUrl(`/blog/category/${category.slugs[0]}`, locale),
|
||||
lastModified: new Date(),
|
||||
priority: 0.8,
|
||||
changeFrequency: 'weekly' as const,
|
||||
}))
|
||||
)
|
||||
);
|
||||
|
||||
// add paginated blog list pages
|
||||
routing.locales.forEach((locale) => {
|
||||
const posts = blogSource
|
||||
.getPages(locale)
|
||||
.filter((post) => post.data.published);
|
||||
const totalPages = Math.max(
|
||||
1,
|
||||
Math.ceil(posts.length / websiteConfig.blog.paginationSize)
|
||||
// add blog related routes if enabled
|
||||
if (websiteConfig.blog.enable) {
|
||||
// add categories
|
||||
sitemapList.push(
|
||||
...categorySource.getPages().flatMap((category) =>
|
||||
routing.locales.map((locale) => ({
|
||||
url: getUrl(`/blog/category/${category.slugs[0]}`, locale),
|
||||
lastModified: new Date(),
|
||||
priority: 0.8,
|
||||
changeFrequency: 'weekly' as const,
|
||||
}))
|
||||
)
|
||||
);
|
||||
// /blog/page/[page] (from 2)
|
||||
for (let page = 2; page <= totalPages; page++) {
|
||||
sitemapList.push({
|
||||
url: getUrl(`/blog/page/${page}`, locale),
|
||||
lastModified: new Date(),
|
||||
priority: 0.8,
|
||||
changeFrequency: 'weekly' as const,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// add paginated category pages
|
||||
routing.locales.forEach((locale) => {
|
||||
const localeCategories = categorySource.getPages(locale);
|
||||
localeCategories.forEach((category) => {
|
||||
// posts in this category and locale
|
||||
const postsInCategory = blogSource
|
||||
// add paginated blog list pages
|
||||
routing.locales.forEach((locale) => {
|
||||
const posts = blogSource
|
||||
.getPages(locale)
|
||||
.filter((post) => post.data.published)
|
||||
.filter((post) =>
|
||||
post.data.categories.some((cat) => cat === category.slugs[0])
|
||||
);
|
||||
.filter((post) => post.data.published);
|
||||
const totalPages = Math.max(
|
||||
1,
|
||||
Math.ceil(postsInCategory.length / websiteConfig.blog.paginationSize)
|
||||
Math.ceil(posts.length / websiteConfig.blog.paginationSize)
|
||||
);
|
||||
// /blog/category/[slug] (first page)
|
||||
sitemapList.push({
|
||||
url: getUrl(`/blog/category/${category.slugs[0]}`, locale),
|
||||
lastModified: new Date(),
|
||||
priority: 0.8,
|
||||
changeFrequency: 'weekly' as const,
|
||||
});
|
||||
// /blog/category/[slug]/page/[page] (from 2)
|
||||
// /blog/page/[page] (from 2)
|
||||
for (let page = 2; page <= totalPages; page++) {
|
||||
sitemapList.push({
|
||||
url: getUrl(
|
||||
`/blog/category/${category.slugs[0]}/page/${page}`,
|
||||
locale
|
||||
),
|
||||
url: getUrl(`/blog/page/${page}`, locale),
|
||||
lastModified: new Date(),
|
||||
priority: 0.8,
|
||||
changeFrequency: 'weekly' as const,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// add posts (single post pages)
|
||||
sitemapList.push(
|
||||
...blogSource.getPages().flatMap((post) =>
|
||||
routing.locales
|
||||
.filter((locale) => post.locale === locale)
|
||||
.map((locale) => ({
|
||||
url: getUrl(`/blog/${post.slugs.join('/')}`, locale),
|
||||
// add paginated category pages
|
||||
routing.locales.forEach((locale) => {
|
||||
const localeCategories = categorySource.getPages(locale);
|
||||
localeCategories.forEach((category) => {
|
||||
// posts in this category and locale
|
||||
const postsInCategory = blogSource
|
||||
.getPages(locale)
|
||||
.filter((post) => post.data.published)
|
||||
.filter((post) =>
|
||||
post.data.categories.some((cat) => cat === category.slugs[0])
|
||||
);
|
||||
const totalPages = Math.max(
|
||||
1,
|
||||
Math.ceil(postsInCategory.length / websiteConfig.blog.paginationSize)
|
||||
);
|
||||
// /blog/category/[slug] (first page)
|
||||
sitemapList.push({
|
||||
url: getUrl(`/blog/category/${category.slugs[0]}`, locale),
|
||||
lastModified: new Date(),
|
||||
priority: 0.8,
|
||||
changeFrequency: 'weekly' as const,
|
||||
});
|
||||
// /blog/category/[slug]/page/[page] (from 2)
|
||||
for (let page = 2; page <= totalPages; page++) {
|
||||
sitemapList.push({
|
||||
url: getUrl(
|
||||
`/blog/category/${category.slugs[0]}/page/${page}`,
|
||||
locale
|
||||
),
|
||||
lastModified: new Date(),
|
||||
priority: 0.8,
|
||||
changeFrequency: 'weekly' as const,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// add posts (single post pages)
|
||||
sitemapList.push(
|
||||
...blogSource.getPages().flatMap((post) =>
|
||||
routing.locales
|
||||
.filter((locale) => post.locale === locale)
|
||||
.map((locale) => ({
|
||||
url: getUrl(`/blog/${post.slugs.join('/')}`, locale),
|
||||
lastModified: new Date(),
|
||||
priority: 0.8,
|
||||
changeFrequency: 'weekly' as const,
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// add docs related routes if enabled
|
||||
if (websiteConfig.docs.enable) {
|
||||
const docsParams = source.generateParams();
|
||||
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,
|
||||
}))
|
||||
)
|
||||
);
|
||||
|
||||
// add docs
|
||||
const docsParams = source.generateParams();
|
||||
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,
|
||||
}))
|
||||
)
|
||||
);
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return sitemapList;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import { Textarea } from '@/components/ui/textarea';
|
||||
import { useIsMobile } from '@/hooks/use-mobile';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import type { User } from '@/lib/auth-types';
|
||||
import { isDemoWebsite } from '@/lib/demo';
|
||||
import { formatDate } from '@/lib/formatter';
|
||||
import { getStripeDashboardCustomerUrl } from '@/lib/urls/urls';
|
||||
import { cn } from '@/lib/utils';
|
||||
@ -53,7 +54,7 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) {
|
||||
const triggerRefresh = useUsersStore((state) => state.triggerRefresh);
|
||||
|
||||
// show fake data in demo website
|
||||
const isDemo = process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true';
|
||||
const isDemo = isDemoWebsite();
|
||||
|
||||
const handleBan = async () => {
|
||||
if (!banReason) {
|
||||
|
@ -27,6 +27,7 @@ import {
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import type { User } from '@/lib/auth-types';
|
||||
import { isDemoWebsite } from '@/lib/demo';
|
||||
import { formatDate } from '@/lib/formatter';
|
||||
import { getStripeDashboardCustomerUrl } from '@/lib/urls/urls';
|
||||
import { IconCaretDownFilled, IconCaretUpFilled } from '@tabler/icons-react';
|
||||
@ -152,7 +153,7 @@ export function UsersTable({
|
||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
||||
|
||||
// show fake data in demo website
|
||||
const isDemo = process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true';
|
||||
const isDemo = isDemoWebsite();
|
||||
|
||||
// Map column IDs to translation keys
|
||||
const columnIdToTranslationKey = {
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
} from '@/components/ui/breadcrumb';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { SidebarTrigger } from '@/components/ui/sidebar';
|
||||
import { isDemoWebsite } from '@/lib/demo';
|
||||
import React, { type ReactNode } from 'react';
|
||||
import { CreditsBalanceButton } from '../layout/credits-balance-button';
|
||||
import LocaleSwitcher from '../layout/locale-switcher';
|
||||
@ -30,8 +31,7 @@ export function DashboardHeader({
|
||||
breadcrumbs,
|
||||
actions,
|
||||
}: DashboardHeaderProps) {
|
||||
// if is demo website, allow user to access admin and user pages, but data is fake
|
||||
const isDemo = process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true';
|
||||
const isDemo = isDemoWebsite();
|
||||
|
||||
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)">
|
||||
|
@ -19,7 +19,7 @@ interface YoutubeVideoProps {
|
||||
export const YoutubeVideo = ({
|
||||
url,
|
||||
width = 560,
|
||||
height = 315,
|
||||
height = 460,
|
||||
}: YoutubeVideoProps) => {
|
||||
return (
|
||||
<div className="my-4">
|
||||
|
@ -10,7 +10,6 @@ import { LocaleLink } from '@/i18n/navigation';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import type React from 'react';
|
||||
import { ThemeSelector } from './theme-selector';
|
||||
|
||||
export function Footer({ className }: React.HTMLAttributes<HTMLElement>) {
|
||||
const t = useTranslations();
|
||||
@ -99,7 +98,6 @@ export function Footer({ className }: React.HTMLAttributes<HTMLElement>) {
|
||||
</span>
|
||||
|
||||
<div className="flex items-center gap-x-4">
|
||||
{/* <ThemeSelector /> */}
|
||||
<ModeSwitcherHorizontal />
|
||||
</div>
|
||||
</Container>
|
||||
|
@ -1,10 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { NewsletterForm } from '@/components/newsletter/newsletter-form';
|
||||
import { websiteConfig } from '@/config/website';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { HeaderSection } from '../layout/header-section';
|
||||
|
||||
export function NewsletterCard() {
|
||||
// show nothing if newsletter is disabled
|
||||
if (!websiteConfig.newsletter.enable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const t = useTranslations('Newsletter');
|
||||
|
||||
return (
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
FormLabel,
|
||||
} from '@/components/ui/form';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { websiteConfig } from '@/config/website';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
@ -40,6 +41,11 @@ interface NewsletterFormCardProps {
|
||||
* Allows users to toggle their newsletter subscription status
|
||||
*/
|
||||
export function NewsletterFormCard({ className }: NewsletterFormCardProps) {
|
||||
// show nothing if newsletter is disabled
|
||||
if (!websiteConfig.newsletter.enable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const t = useTranslations('Dashboard.settings.notification');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | undefined>('');
|
||||
|
@ -11,6 +11,7 @@ import {
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { websiteConfig } from '@/config/website';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { uploadFileFromBrowser } from '@/storage/client';
|
||||
@ -27,6 +28,14 @@ interface UpdateAvatarCardProps {
|
||||
* Update the user's avatar
|
||||
*/
|
||||
export function UpdateAvatarCard({ className }: UpdateAvatarCardProps) {
|
||||
// show nothing if storage is disabled or update avatar is disabled
|
||||
if (
|
||||
!websiteConfig.storage.enable ||
|
||||
!websiteConfig.features.enableUpdateAvatar
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const t = useTranslations('Dashboard.settings.profile');
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const [error, setError] = useState<string | undefined>('');
|
||||
|
@ -3,6 +3,7 @@
|
||||
import { Routes } from '@/routes';
|
||||
import type { NestedMenuItem } from '@/types';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { websiteConfig } from './website';
|
||||
|
||||
/**
|
||||
* Get footer config with translations
|
||||
@ -41,16 +42,24 @@ export function getFooterLinks(): NestedMenuItem[] {
|
||||
{
|
||||
title: t('resources.title'),
|
||||
items: [
|
||||
{
|
||||
title: t('resources.items.blog'),
|
||||
href: Routes.Blog,
|
||||
external: false,
|
||||
},
|
||||
{
|
||||
title: t('resources.items.docs'),
|
||||
href: Routes.Docs,
|
||||
external: false,
|
||||
},
|
||||
...(websiteConfig.blog.enable
|
||||
? [
|
||||
{
|
||||
title: t('resources.items.blog'),
|
||||
href: Routes.Blog,
|
||||
external: false,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(websiteConfig.docs.enable
|
||||
? [
|
||||
{
|
||||
title: t('resources.items.docs'),
|
||||
href: Routes.Docs,
|
||||
external: false,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
title: t('resources.items.changelog'),
|
||||
href: Routes.Changelog,
|
||||
|
@ -34,6 +34,7 @@ import {
|
||||
WandSparklesIcon,
|
||||
} from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { websiteConfig } from './website';
|
||||
|
||||
/**
|
||||
* Get navbar config with translations
|
||||
@ -59,16 +60,24 @@ export function getNavbarLinks(): NestedMenuItem[] {
|
||||
href: Routes.Pricing,
|
||||
external: false,
|
||||
},
|
||||
{
|
||||
title: t('blog.title'),
|
||||
href: Routes.Blog,
|
||||
external: false,
|
||||
},
|
||||
{
|
||||
title: t('docs.title'),
|
||||
href: Routes.Docs,
|
||||
external: false,
|
||||
},
|
||||
...(websiteConfig.blog.enable
|
||||
? [
|
||||
{
|
||||
title: t('blog.title'),
|
||||
href: Routes.Blog,
|
||||
external: false,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(websiteConfig.docs.enable
|
||||
? [
|
||||
{
|
||||
title: t('docs.title'),
|
||||
href: Routes.Docs,
|
||||
external: false,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
title: t('ai.title'),
|
||||
items: [
|
||||
|
@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { isDemoWebsite } from '@/lib/demo';
|
||||
import { Routes } from '@/routes';
|
||||
import type { NestedMenuItem } from '@/types';
|
||||
import {
|
||||
@ -30,7 +31,7 @@ export function getSidebarLinks(): NestedMenuItem[] {
|
||||
const t = useTranslations('Dashboard');
|
||||
|
||||
// if is demo website, allow user to access admin and user pages, but data is fake
|
||||
const isDemo = process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true';
|
||||
const isDemo = isDemoWebsite();
|
||||
|
||||
return [
|
||||
{
|
||||
|
@ -34,12 +34,12 @@ export const websiteConfig: WebsiteConfig = {
|
||||
},
|
||||
features: {
|
||||
enableDiscordWidget: false,
|
||||
enableCrispChat: process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true',
|
||||
enableUpgradeCard: true,
|
||||
enableUpdateAvatar: true,
|
||||
enableAffonsoAffiliate: false,
|
||||
enablePromotekitAffiliate: false,
|
||||
enableDatafastRevenueTrack: false,
|
||||
enableCrispChat: process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true',
|
||||
enableTurnstileCaptcha: process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true',
|
||||
},
|
||||
routes: {
|
||||
@ -68,19 +68,25 @@ export const websiteConfig: WebsiteConfig = {
|
||||
},
|
||||
},
|
||||
blog: {
|
||||
enable: true,
|
||||
paginationSize: 6,
|
||||
relatedPostsSize: 3,
|
||||
},
|
||||
docs: {
|
||||
enable: true,
|
||||
},
|
||||
mail: {
|
||||
provider: 'resend',
|
||||
fromEmail: 'MkSaaS <support@mksaas.com>',
|
||||
supportEmail: 'MkSaaS <support@mksaas.com>',
|
||||
},
|
||||
newsletter: {
|
||||
enable: true,
|
||||
provider: 'resend',
|
||||
autoSubscribeAfterSignUp: true,
|
||||
},
|
||||
storage: {
|
||||
enable: true,
|
||||
provider: 's3',
|
||||
},
|
||||
payment: {
|
||||
|
@ -164,7 +164,11 @@ export function getLocaleFromRequest(request?: Request): Locale {
|
||||
async function onCreateUser(user: User) {
|
||||
// Auto subscribe user to newsletter after sign up if enabled in website config
|
||||
// Add a delay to avoid hitting Resend's 1 email per second limit
|
||||
if (user.email && websiteConfig.newsletter.autoSubscribeAfterSignUp) {
|
||||
if (
|
||||
user.email &&
|
||||
websiteConfig.newsletter.enable &&
|
||||
websiteConfig.newsletter.autoSubscribeAfterSignUp
|
||||
) {
|
||||
// Delay newsletter subscription by 2 seconds to avoid rate limiting
|
||||
// This ensures the email verification email is sent first
|
||||
// Using 2 seconds instead of 1 to provide extra buffer for network delays
|
||||
@ -184,6 +188,7 @@ async function onCreateUser(user: User) {
|
||||
|
||||
// Add register gift credits to the user if enabled in website config
|
||||
if (
|
||||
websiteConfig.credits.enableCredits &&
|
||||
websiteConfig.credits.registerGiftCredits.enable &&
|
||||
websiteConfig.credits.registerGiftCredits.credits > 0
|
||||
) {
|
||||
@ -199,21 +204,26 @@ async function onCreateUser(user: User) {
|
||||
}
|
||||
|
||||
// Add free monthly credits to the user if enabled in website config
|
||||
const pricePlans = await getAllPricePlans();
|
||||
const freePlan = pricePlans.find((plan) => plan.isFree);
|
||||
if (
|
||||
freePlan?.credits?.enable &&
|
||||
freePlan?.credits?.amount &&
|
||||
freePlan?.credits?.amount > 0
|
||||
websiteConfig.credits.enableCredits &&
|
||||
websiteConfig.credits.enableForFreePlan
|
||||
) {
|
||||
try {
|
||||
await addMonthlyFreeCredits(user.id);
|
||||
const credits = freePlan.credits.amount;
|
||||
console.log(
|
||||
`added free monthly credits for user ${user.id}, credits: ${credits}`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Free monthly credits error:', error);
|
||||
const pricePlans = await getAllPricePlans();
|
||||
const freePlan = pricePlans.find((plan) => plan.isFree);
|
||||
if (
|
||||
freePlan?.credits?.enable &&
|
||||
freePlan?.credits?.amount &&
|
||||
freePlan?.credits?.amount > 0
|
||||
) {
|
||||
try {
|
||||
await addMonthlyFreeCredits(user.id);
|
||||
const credits = freePlan.credits.amount;
|
||||
console.log(
|
||||
`added free monthly credits for user ${user.id}, credits: ${credits}`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Free monthly credits error:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
6
src/lib/demo.ts
Normal file
6
src/lib/demo.ts
Normal file
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* check if the website is a demo website
|
||||
*/
|
||||
export function isDemoWebsite() {
|
||||
return process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true';
|
||||
}
|
11
src/types/index.d.ts
vendored
11
src/types/index.d.ts
vendored
@ -13,6 +13,7 @@ export type WebsiteConfig = {
|
||||
auth: AuthConfig;
|
||||
i18n: I18nConfig;
|
||||
blog: BlogConfig;
|
||||
docs: DocsConfig;
|
||||
mail: MailConfig;
|
||||
newsletter: NewsletterConfig;
|
||||
storage: StorageConfig;
|
||||
@ -111,10 +112,18 @@ export interface I18nConfig {
|
||||
* Blog configuration
|
||||
*/
|
||||
export interface BlogConfig {
|
||||
enable: boolean; // Whether to enable the blog
|
||||
paginationSize: number; // Number of posts per page
|
||||
relatedPostsSize: number; // Number of related posts to show
|
||||
}
|
||||
|
||||
/**
|
||||
* Docs configuration
|
||||
*/
|
||||
export interface DocsConfig {
|
||||
enable: boolean; // Whether to enable the docs
|
||||
}
|
||||
|
||||
/**
|
||||
* Mail configuration
|
||||
*/
|
||||
@ -128,6 +137,7 @@ export interface MailConfig {
|
||||
* Newsletter configuration
|
||||
*/
|
||||
export interface NewsletterConfig {
|
||||
enable: boolean; // Whether to enable the newsletter
|
||||
provider: 'resend'; // The newsletter provider, only resend is supported for now
|
||||
autoSubscribeAfterSignUp?: boolean; // Whether to automatically subscribe users to the newsletter after sign up
|
||||
}
|
||||
@ -136,6 +146,7 @@ export interface NewsletterConfig {
|
||||
* Storage configuration
|
||||
*/
|
||||
export interface StorageConfig {
|
||||
enable: boolean; // Whether to enable the storage
|
||||
provider: 's3'; // The storage provider, only s3 is supported for now
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user