Merge remote-tracking branch 'origin/main' into cloudflare
This commit is contained in:
commit
002d2090c2
20
README.md
20
README.md
@ -1,12 +1,12 @@
|
||||
# MkSaaS
|
||||
|
||||
Make AI SaaS in days, simply and effortlessly.
|
||||
Make AI SaaS in a weekend.
|
||||
|
||||
MkSaaS is a complete Next.js boilerplate for building AI SaaS websites. Make money from day one with our battle-tested stack and built-in monetization features.
|
||||
The complete Next.js boilerplate for building profitable SaaS, with auth, payments, i18n, newsletter, dashboard, blog, docs, blocks, themes, SEO and more.
|
||||
|
||||
## Author
|
||||
|
||||
This project is created by [Fox](https://x.com/indie_maker_fox), the founder of [MkSaaS](https://mksaas.com) and [Mkdirs](https://mkdirs.com).
|
||||
This project is created by [Fox](https://x.com/indie_maker_fox), the founder of [MkSaaS](https://mksaas.com) and [Mkdirs](https://mkdirs.com). The official X account for [MkSaaS](https://mksaas.com) is [@mksaascom](https://x.com/mksaascom), you can follow this account for the updates about MkSaaS.
|
||||
|
||||
## Documentation
|
||||
|
||||
@ -16,19 +16,19 @@ If you found anything that could be improved, please let me know.
|
||||
|
||||
## Links
|
||||
|
||||
- website: [mksaas.com](https://mksaas.com)
|
||||
- demo: [demo.mksaas.com](https://demo.mksaas.com)
|
||||
- discord: [discord.gg/mtjx6W6mNY](https://discord.gg/mtjx6W6mNY)
|
||||
- documentation: [mksaas.com/docs](https://mksaas.com/docs)
|
||||
- roadmap: [mksaas project](https://github.com/orgs/MkSaaSHQ/projects/1)
|
||||
- video (WIP): [youtube.com/@MkSaaSHQ](https://www.youtube.com/@MkSaaSHQ)
|
||||
- 🔥 website: [mksaas.com](https://mksaas.com)
|
||||
- 🌐 demo: [demo.mksaas.com](https://demo.mksaas.com)
|
||||
- 📚 documentation: [mksaas.com/docs](https://mksaas.com/docs)
|
||||
- 🗓️ roadmap: [mksaas project](https://mksaas.link/roadmap)
|
||||
- 👨💻 discord: [mksaas.link/discord](https://mksaas.link/discord)
|
||||
- 📹 video (WIP): [mksaas.link/youtube](https://mksaas.link/youtube)
|
||||
|
||||
## Repositories
|
||||
|
||||
By default, you should have access to all four repositories. If you find that you’re unable to access any of them, please don’t hesitate to reach out to me, and I’ll assist you in resolving the issue.
|
||||
|
||||
- [MkSaaSHQ/mksaas-template](https://github.com/MkSaaSHQ/mksaas-template): https://demo.mksaas.com (ready)
|
||||
- [MkSaaSHQ/mksaas-blog](https://github.com/MkSaaSHQ/mksaas-blog): https://javayhu.com (ready)
|
||||
- [MkSaaSHQ/mksaas-blog](https://github.com/MkSaaSHQ/mksaas-blog): https://mksaas.me (ready)
|
||||
- [MkSaaSHQ/mksaas-app](https://github.com/MkSaaSHQ/mksaas-app): https://mksaas.app (WIP)
|
||||
- [MkSaaSHQ/mksaas-haitang](https://github.com/MkSaaSHQ/mksaas-haitang): https://haitang.app (WIP)
|
||||
|
||||
|
||||
13
env.example
13
env.example
@ -5,8 +5,6 @@
|
||||
# For development, set to http://localhost:3000 or any other port
|
||||
# -----------------------------------------------------------------------------
|
||||
NEXT_PUBLIC_BASE_URL="http://localhost:3000"
|
||||
# If your development port is not 3000, please set PORT to your port, e.g. 3005
|
||||
PORT=3000
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Database
|
||||
@ -79,9 +77,9 @@ NEXT_PUBLIC_STRIPE_PRICE_LIFETIME=""
|
||||
# Configurations
|
||||
# -----------------------------------------------------------------------------
|
||||
# Disable image optimization, check out next.config.ts for more details
|
||||
DISABLE_IMAGE_OPTIMIZATION="false"
|
||||
DISABLE_IMAGE_OPTIMIZATION=false
|
||||
# Run this website as demo website, in most cases, you should set this to false
|
||||
NEXT_PUBLIC_DEMO_WEBSITE="false"
|
||||
NEXT_PUBLIC_DEMO_WEBSITE=false
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Analytics
|
||||
@ -124,3 +122,10 @@ NEXT_PUBLIC_SELINE_TOKEN=""
|
||||
# -----------------------------------------------------------------------------
|
||||
NEXT_PUBLIC_DATAFAST_ANALYTICS_ID=""
|
||||
NEXT_PUBLIC_DATAFAST_ANALYTICS_DOMAIN=""
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Discord
|
||||
# -----------------------------------------------------------------------------
|
||||
NEXT_PUBLIC_DISCORD_WIDGET_SERVER_ID=""
|
||||
NEXT_PUBLIC_DISCORD_WIDGET_CHANNEL_ID=""
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:push": "drizzle-kit push",
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"list-contacts": "tsx scripts/list-contacts.ts",
|
||||
"docs": "content-collections build",
|
||||
"email": "email dev --dir src/mail/templates --port 3333",
|
||||
"preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview",
|
||||
@ -75,6 +76,7 @@
|
||||
"@types/canvas-confetti": "^1.9.0",
|
||||
"@vercel/analytics": "^1.5.0",
|
||||
"@vercel/speed-insights": "^1.2.0",
|
||||
"@widgetbot/react-embed": "^1.9.0",
|
||||
"ai": "^4.1.45",
|
||||
"better-auth": "^1.1.19",
|
||||
"canvas-confetti": "^1.9.3",
|
||||
|
||||
48
pnpm-lock.yaml
generated
48
pnpm-lock.yaml
generated
@ -170,6 +170,9 @@ importers:
|
||||
'@vercel/speed-insights':
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
|
||||
'@widgetbot/react-embed':
|
||||
specifier: ^1.9.0
|
||||
version: 1.9.0(react@19.0.0)
|
||||
ai:
|
||||
specifier: ^4.1.45
|
||||
version: 4.1.45(react@19.0.0)(zod@3.24.2)
|
||||
@ -4237,6 +4240,14 @@ packages:
|
||||
vue-router:
|
||||
optional: true
|
||||
|
||||
'@widgetbot/embed-api@1.2.17':
|
||||
resolution: {integrity: sha512-qoiFLMak+mBG64pgKN5xFv3amPHcG2qcurPefAbof4DI/eip5OU59pbM+ak4d9d9OIkwA1QhoDzo9KWD/cOn0w==}
|
||||
|
||||
'@widgetbot/react-embed@1.9.0':
|
||||
resolution: {integrity: sha512-+Qgqy7lwLy++lIiHmSsgxUjwcX80iFIHR0QJpKq4W82ePUmq4bTuxvUbxcE+VQH5IjNrWaydGNR8zROV5vUQsA==}
|
||||
peerDependencies:
|
||||
react: '>= 15'
|
||||
|
||||
accepts@1.3.8:
|
||||
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@ -4516,6 +4527,12 @@ packages:
|
||||
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
cross-domain-safe-weakmap@1.0.29:
|
||||
resolution: {integrity: sha512-VLoUgf2SXnf3+na8NfeUFV59TRZkIJqCIATaMdbhccgtnTlSnHXkyTRwokngEGYdQXx8JbHT9GDYitgR2sdjuA==}
|
||||
|
||||
cross-domain-utils@2.0.38:
|
||||
resolution: {integrity: sha512-zZfi3+2EIR9l4chrEiXI2xFleyacsJf8YMLR1eJ0Veb5FTMXeJ3DpxDjZkto2FhL/g717WSELqbptNSo85UJDw==}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||
engines: {node: '>= 8'}
|
||||
@ -6078,6 +6095,9 @@ packages:
|
||||
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
post-robot@8.0.32:
|
||||
resolution: {integrity: sha512-PMOdDAt3pyuKUxZcTzdcXXFxLqkdeLpRlcCQl7QAJpI+e7J1YHH+PfC7KAbcL8hRVQ1LknQYGoirbA1/eO/a1g==}
|
||||
|
||||
postcss-selector-parser@7.1.0:
|
||||
resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==}
|
||||
engines: {node: '>=4'}
|
||||
@ -6987,6 +7007,9 @@ packages:
|
||||
youch@3.3.4:
|
||||
resolution: {integrity: sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==}
|
||||
|
||||
zalgo-promise@1.0.48:
|
||||
resolution: {integrity: sha512-LLHANmdm53+MucY9aOFIggzYtUdkSBFxUsy4glTTQYNyK6B3uCPWTbfiGvSrEvLojw0mSzyFJ1/RRLv+QMNdzQ==}
|
||||
|
||||
zod-to-json-schema@3.24.2:
|
||||
resolution: {integrity: sha512-pNUqrcSxuuB3/+jBbU8qKUbTbDqYUaG1vf5cXFjbhGgoUuA1amO/y4Q8lzfOhHU8HNPK6VFJ18lBDKj3OHyDsg==}
|
||||
peerDependencies:
|
||||
@ -11549,6 +11572,15 @@ snapshots:
|
||||
next: 15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
react: 19.0.0
|
||||
|
||||
'@widgetbot/embed-api@1.2.17':
|
||||
dependencies:
|
||||
post-robot: 8.0.32
|
||||
|
||||
'@widgetbot/react-embed@1.9.0(react@19.0.0)':
|
||||
dependencies:
|
||||
'@widgetbot/embed-api': 1.2.17
|
||||
react: 19.0.0
|
||||
|
||||
accepts@1.3.8:
|
||||
dependencies:
|
||||
mime-types: 2.1.35
|
||||
@ -11830,6 +11862,14 @@ snapshots:
|
||||
object-assign: 4.1.1
|
||||
vary: 1.1.2
|
||||
|
||||
cross-domain-safe-weakmap@1.0.29:
|
||||
dependencies:
|
||||
cross-domain-utils: 2.0.38
|
||||
|
||||
cross-domain-utils@2.0.38:
|
||||
dependencies:
|
||||
zalgo-promise: 1.0.48
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
@ -13820,6 +13860,12 @@ snapshots:
|
||||
|
||||
pluralize@8.0.0: {}
|
||||
|
||||
post-robot@8.0.32:
|
||||
dependencies:
|
||||
cross-domain-safe-weakmap: 1.0.29
|
||||
cross-domain-utils: 2.0.38
|
||||
zalgo-promise: 1.0.48
|
||||
|
||||
postcss-selector-parser@7.1.0:
|
||||
dependencies:
|
||||
cssesc: 3.0.0
|
||||
@ -14909,6 +14955,8 @@ snapshots:
|
||||
mustache: 4.2.0
|
||||
stacktracey: 2.1.8
|
||||
|
||||
zalgo-promise@1.0.48: {}
|
||||
|
||||
zod-to-json-schema@3.24.2(zod@3.24.2):
|
||||
dependencies:
|
||||
zod: 3.24.2
|
||||
|
||||
25
scripts/list-contacts.ts
Normal file
25
scripts/list-contacts.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import dotenv from 'dotenv';
|
||||
import { Resend } from 'resend';
|
||||
dotenv.config();
|
||||
|
||||
const resend = new Resend(process.env.RESEND_API_KEY);
|
||||
|
||||
export default async function listContacts() {
|
||||
const contacts = await resend.contacts.list({
|
||||
audienceId: process.env.RESEND_AUDIENCE_ID!,
|
||||
});
|
||||
|
||||
// print all emails
|
||||
const emails: string[] = [];
|
||||
if (Array.isArray(contacts.data?.data)) {
|
||||
for (const contact of contacts.data.data) {
|
||||
emails.push(contact.email);
|
||||
}
|
||||
} else {
|
||||
console.error('contacts is not iterable');
|
||||
}
|
||||
|
||||
console.log(emails.join(', '));
|
||||
}
|
||||
|
||||
listContacts();
|
||||
@ -57,7 +57,7 @@ export const getUsersAction = actionClient
|
||||
: user.createdAt;
|
||||
const sortDirection = sortConfig?.desc ? desc : asc;
|
||||
|
||||
const [items, [{ count }]] = await Promise.all([
|
||||
let [items, [{ count }]] = await Promise.all([
|
||||
db
|
||||
.select()
|
||||
.from(user)
|
||||
@ -68,6 +68,16 @@ export const getUsersAction = actionClient
|
||||
db.select({ count: sql`count(*)` }).from(user).where(where),
|
||||
]);
|
||||
|
||||
// hide user data in demo website
|
||||
if (process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true') {
|
||||
items = items.map((item) => ({
|
||||
...item,
|
||||
name: 'Demo User',
|
||||
email: 'example@mksaas.com',
|
||||
customerId: 'cus_abcdef123456',
|
||||
}));
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
|
||||
@ -15,7 +15,7 @@ import { Providers } from './providers';
|
||||
import '@/styles/globals.css';
|
||||
import { Analytics } from '@/analytics/analytics';
|
||||
import { TailwindIndicator } from '@/components/layout/tailwind-indicator';
|
||||
|
||||
import DiscordWidget from '@/components/shared/discord-widget';
|
||||
interface LocaleLayoutProps {
|
||||
children: ReactNode;
|
||||
params: Promise<{ locale: Locale }>;
|
||||
@ -56,6 +56,7 @@ export default async function LocaleLayout({
|
||||
{children}
|
||||
|
||||
<Toaster richColors position="top-right" offset={64} />
|
||||
<DiscordWidget />
|
||||
<TailwindIndicator />
|
||||
<Analytics />
|
||||
</Providers>
|
||||
|
||||
@ -134,7 +134,7 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) {
|
||||
className="size-8 border"
|
||||
/>
|
||||
<span className="hover:underline hover:underline-offset-4">
|
||||
{isDemo ? 'MkSaaS User' : user.name}
|
||||
{user.name}
|
||||
</span>
|
||||
</div>
|
||||
</Button>
|
||||
@ -148,10 +148,8 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) {
|
||||
className="size-12 border"
|
||||
/>
|
||||
<div>
|
||||
<DrawerTitle>{isDemo ? 'MkSaaS User' : user.name}</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
{isDemo ? 'example@mksaas.com' : user.email}
|
||||
</DrawerDescription>
|
||||
<DrawerTitle>{user.name}</DrawerTitle>
|
||||
<DrawerDescription>{user.email}</DrawerDescription>
|
||||
</div>
|
||||
</div>
|
||||
</DrawerHeader>
|
||||
|
||||
@ -168,7 +168,7 @@ export function UsersTable({
|
||||
) : (
|
||||
<MailQuestionIcon className="stroke-red-500 dark:stroke-red-400" />
|
||||
)}
|
||||
{isDemo ? 'example@mksaas.com' : user.email}
|
||||
{user.email}
|
||||
</Badge>
|
||||
</div>
|
||||
);
|
||||
@ -227,7 +227,7 @@ export function UsersTable({
|
||||
rel="noopener noreferrer"
|
||||
className="hover:underline hover:underline-offset-4"
|
||||
>
|
||||
{!isDemo ? user.customerId : 'cus_abcdef123456'}
|
||||
{user.customerId}
|
||||
</a>
|
||||
) : (
|
||||
'-'
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Tailwind Indicator, shows the current tailwind breakpoint
|
||||
*/
|
||||
export function TailwindIndicator() {
|
||||
if (process.env.NODE_ENV === 'production') return null;
|
||||
|
||||
|
||||
80
src/components/shared/discord-widget.tsx
Normal file
80
src/components/shared/discord-widget.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
'use client';
|
||||
|
||||
import { DiscordIcon } from '@/components/icons/discord';
|
||||
import { useMediaQuery } from '@/hooks/use-media-query';
|
||||
import WidgetBot from '@widgetbot/react-embed';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
/**
|
||||
* Discord Widget, shows the channels and messages in the discord server
|
||||
*
|
||||
* https://docs.widgetbot.io/embed/react-embed/
|
||||
*/
|
||||
export default function DiscordWidget() {
|
||||
const serverId = process.env.NEXT_PUBLIC_DISCORD_WIDGET_SERVER_ID as string;
|
||||
const channelId = process.env.NEXT_PUBLIC_DISCORD_WIDGET_CHANNEL_ID as string;
|
||||
if (!serverId || !channelId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const widgetRef = useRef<HTMLDivElement>(null);
|
||||
const { device, width: windowWidth, height: windowHeight } = useMediaQuery();
|
||||
|
||||
let widgetWidth = 800;
|
||||
let widgetHeight = 600;
|
||||
if (device === 'mobile') {
|
||||
widgetWidth = windowWidth ? Math.floor(windowWidth * 0.9) : 320;
|
||||
widgetHeight = windowHeight ? Math.floor(windowHeight * 0.8) : 400;
|
||||
} else if (device === 'tablet' || device === 'sm') {
|
||||
widgetWidth = windowWidth ? Math.floor(windowWidth * 0.9) : 600;
|
||||
widgetHeight = windowHeight ? Math.floor(windowHeight * 0.8) : 480;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
function handleClick(e: MouseEvent) {
|
||||
if (widgetRef.current && !widgetRef.current.contains(e.target as Node)) {
|
||||
setOpen(false);
|
||||
}
|
||||
}
|
||||
document.addEventListener('mousedown', handleClick);
|
||||
return () => document.removeEventListener('mousedown', handleClick);
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* discord icon button, show in bottom right corner */}
|
||||
{!open && (
|
||||
<button
|
||||
aria-label="Open Discord Widget"
|
||||
className="fixed bottom-[84px] right-10 z-50 cursor-pointer flex items-center justify-center rounded-full bg-[#5865F2] shadow-lg
|
||||
hover:scale-110 transition-transform duration-150"
|
||||
style={{ width: 48, height: 48 }}
|
||||
onClick={() => setOpen(true)}
|
||||
type="button"
|
||||
>
|
||||
<DiscordIcon width={32} height={32} className="text-white" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* discord widget expand layer */}
|
||||
{open && (
|
||||
<div
|
||||
ref={widgetRef}
|
||||
className="fixed bottom-[84px] right-10 z-50 flex flex-col items-end"
|
||||
style={{ width: widgetWidth, height: widgetHeight }}
|
||||
>
|
||||
<div className="rounded-lg overflow-hidden shadow-2xl border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900">
|
||||
<WidgetBot
|
||||
server={serverId}
|
||||
channel={channelId}
|
||||
width={widgetWidth}
|
||||
height={widgetHeight}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -24,10 +24,12 @@ export const websiteConfig: WebsiteConfig = {
|
||||
},
|
||||
social: {
|
||||
github: 'https://github.com/MkSaaSHQ',
|
||||
twitter: 'https://x.com/mksaascom',
|
||||
blueSky: 'https://bsky.app/profile/mksaas.com',
|
||||
discord: 'https://discord.gg/yVwpEtTT',
|
||||
youtube: 'https://www.youtube.com/@MkSaaS',
|
||||
twitter: 'https://mksaas.link/twitter',
|
||||
blueSky: 'https://mksaas.link/bsky',
|
||||
discord: 'https://mksaas.link/discord',
|
||||
mastodon: 'https://mksaas.link/mastodon',
|
||||
linkedin: 'https://mksaas.link/linkedin',
|
||||
youtube: 'https://mksaas.link/youtube',
|
||||
},
|
||||
},
|
||||
routes: {
|
||||
|
||||
@ -10,7 +10,7 @@ import { drizzleAdapter } from 'better-auth/adapters/drizzle';
|
||||
import { admin } from 'better-auth/plugins';
|
||||
import { parse as parseCookies } from 'cookie';
|
||||
import type { Locale } from 'next-intl';
|
||||
import { getUrlWithLocaleInCallbackUrl } from './urls/urls';
|
||||
import { getBaseUrl, getUrlWithLocaleInCallbackUrl } from './urls/urls';
|
||||
|
||||
/**
|
||||
* Better Auth configuration
|
||||
@ -20,6 +20,7 @@ import { getUrlWithLocaleInCallbackUrl } from './urls/urls';
|
||||
* https://www.better-auth.com/docs/reference/options
|
||||
*/
|
||||
export const auth = betterAuth({
|
||||
baseURL: getBaseUrl(),
|
||||
appName: defaultMessages.Metadata.name,
|
||||
database: drizzleAdapter(db, {
|
||||
provider: 'pg', // or "mysql", "sqlite"
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
import db from '@/db';
|
||||
import { payment, session, user } from '@/db/schema';
|
||||
import { sendMessageToDiscord } from '@/lib/discord';
|
||||
import {
|
||||
findPlanByPlanId,
|
||||
findPlanByPriceId,
|
||||
@ -617,6 +618,10 @@ export class StripeProvider implements PaymentProvider {
|
||||
console.log(
|
||||
`<< Created one-time payment record for user ${userId}, price: ${priceId}`
|
||||
);
|
||||
|
||||
// Send message to Discord channel
|
||||
const amount = session.amount_total ? session.amount_total / 100 : 0;
|
||||
await sendMessageToDiscord(session.id, customerId, userId, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -16,7 +16,7 @@ export enum Routes {
|
||||
Contact = '/contact',
|
||||
Waitlist = '/waitlist',
|
||||
Changelog = '/changelog',
|
||||
Roadmap = 'https://github.com/orgs/MkSaaSHQ/projects/1',
|
||||
Roadmap = 'https://mksaas.link/roadmap',
|
||||
CookiePolicy = '/cookie',
|
||||
PrivacyPolicy = '/privacy',
|
||||
TermsOfService = '/terms',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user