Merge branch 'main' of github.com:MkSaaSHQ/mksaas-template

This commit is contained in:
javayhu 2025-05-15 00:43:56 +08:00
commit 50d6e2b069
6 changed files with 131 additions and 1 deletions

View File

@ -124,3 +124,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=""

View File

@ -71,6 +71,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
View File

@ -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)
@ -3278,6 +3281,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'}
@ -3509,6 +3520,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'}
@ -4822,6 +4839,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'}
@ -5601,6 +5621,9 @@ packages:
resolution: {integrity: sha512-qJNAmSF77lWjfRVwCZK3PcKYWrr+55RUQTiXDxXHGbxzf8WuuRgftIB3hqZ5fykjOF/MC62cazsG/2ZDBedOnQ==}
engines: {node: '>=14.16'}
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:
@ -8607,6 +8630,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
@ -8843,6 +8875,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
@ -10531,6 +10571,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
@ -11472,6 +11518,8 @@ snapshots:
yoctocolors@1.0.0: {}
zalgo-promise@1.0.48: {}
zod-to-json-schema@3.24.2(zod@3.24.2):
dependencies:
zod: 3.24.2

View File

@ -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>

View File

@ -1,3 +1,6 @@
/**
* Tailwind Indicator, shows the current tailwind breakpoint
*/
export function TailwindIndicator() {
if (process.env.NODE_ENV === 'production') return null;

View File

@ -0,0 +1,70 @@
'use client';
import { DiscordIcon } from '@/components/icons/discord';
import WidgetBot from '@widgetbot/react-embed';
import { useEffect, useRef, useState } from 'react';
const WIDGET_WIDTH = 800;
const WIDGET_HEIGHT = 600;
const ICON_SIZE = 48;
/**
* Discord Widget, shows the channels and messages in the discord server
*/
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);
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: ICON_SIZE, height: ICON_SIZE }}
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: WIDGET_WIDTH, height: WIDGET_HEIGHT }}
>
<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={WIDGET_WIDTH}
height={WIDGET_HEIGHT}
/>
</div>
</div>
)}
</div>
);
}