chore: support show admin user page in demo website

This commit is contained in:
javayhu 2025-05-11 14:50:13 +08:00
parent 50f44fb84c
commit e284de79a8
6 changed files with 42 additions and 23 deletions

View File

@ -78,9 +78,10 @@ NEXT_PUBLIC_STRIPE_PRICE_LIFETIME=""
# -----------------------------------------------------------------------------
# Configurations
# -----------------------------------------------------------------------------
# Disable image optimization
# -----------------------------------------------------------------------------
# Disable image optimization, check out next.config.ts for more details
DISABLE_IMAGE_OPTIMIZATION="false"
# Run this website as demo website, in most cases, you should set this to false
RUN_AS_DEMO_WEBSITE="false"
# -----------------------------------------------------------------------------
# Analytics

View File

@ -441,6 +441,7 @@
"title": "Admin",
"users": {
"title": "Users",
"fakeData": "Note: Faked data for demonstration, some features are disabled",
"error": "Failed to get users",
"search": "Search users...",
"columns": {

View File

@ -442,6 +442,7 @@
"title": "系统管理",
"users": {
"title": "用户管理",
"fakeData": "注:只为演示功能,数据为假数据,封禁功能不可用",
"error": "获取用户失败",
"search": "搜索用户...",
"columns": {

View File

@ -51,6 +51,9 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) {
const [banExpiresAt, setBanExpiresAt] = useState<Date | undefined>();
const triggerRefresh = useUsersStore((state) => state.triggerRefresh);
// show fake data in demo website
const isDemo = process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true';
const handleBan = async () => {
if (!banReason) {
setError(t('ban.error'));
@ -131,7 +134,7 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) {
className="size-8 border"
/>
<span className="hover:underline hover:underline-offset-4">
{user.name}
{isDemo ? 'MkSaaS User' : user.name}
</span>
</div>
</Button>
@ -145,8 +148,10 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) {
className="size-12 border"
/>
<div>
<DrawerTitle>{user.name}</DrawerTitle>
<DrawerDescription>{user.email}</DrawerDescription>
<DrawerTitle>{isDemo ? 'MkSaaS User' : user.name}</DrawerTitle>
<DrawerDescription>
{isDemo ? 'example@mksaas.com' : user.email}
</DrawerDescription>
</div>
</div>
</DrawerHeader>
@ -158,7 +163,7 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) {
variant={user.role === 'admin' ? 'default' : 'outline'}
className="px-1.5"
>
{t(user.role === 'admin' ? 'admin' : 'user')}
{user.role === 'admin' ? t('admin') : t('user')}
</Badge>
{/* email verified */}
<Badge variant="outline" className="px-1.5 hover:bg-accent">
@ -201,10 +206,10 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) {
{/* ban or unban user */}
{user.banned ? (
<div className="grid gap-4">
<div className="text-muted-foreground">
<div className="">
{t('ban.reason')}: {user.banReason}
</div>
<div className="text-muted-foreground">
<div className="">
{t('ban.expires')}:{' '}
{(user.banExpires && formatDate(user.banExpires)) ||
t('ban.never')}
@ -212,7 +217,7 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) {
<Button
variant="destructive"
onClick={handleUnban}
disabled={isLoading}
disabled={isLoading || isDemo}
className="mt-4 cursor-pointer"
>
{isLoading && (
@ -271,7 +276,7 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) {
<Button
type="submit"
variant="destructive"
disabled={isLoading || !banReason}
disabled={isLoading || !banReason || isDemo}
className="mt-4 cursor-pointer"
>
{isLoading && (

View File

@ -119,6 +119,9 @@ export function UsersTable({
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
// show fake data in demo website
const isDemo = process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true';
// Map column IDs to translation keys
const columnIdToTranslationKey = {
name: 'columns.name' as const,
@ -165,7 +168,7 @@ export function UsersTable({
) : (
<MailQuestionIcon className="stroke-red-500 dark:stroke-red-400" />
)}
{user.email}
{isDemo ? 'example@mksaas.com' : user.email}
</Badge>
</div>
);
@ -185,7 +188,7 @@ export function UsersTable({
variant={role === 'admin' ? 'default' : 'outline'}
className="px-1.5"
>
{t(role === 'admin' ? 'admin' : 'user')}
{role === 'admin' ? t('admin') : t('user')}
</Badge>
</div>
);
@ -224,7 +227,7 @@ export function UsersTable({
rel="noopener noreferrer"
className="hover:underline hover:underline-offset-4"
>
{user.customerId}
{!isDemo ? user.customerId : 'cus_abcdef123456'}
</a>
) : (
'-'
@ -323,15 +326,20 @@ export function UsersTable({
return (
<div className="w-full flex-col justify-start gap-6 space-y-4">
<div className="flex items-center justify-between px-4 lg:px-6 gap-4">
<Input
placeholder={t('search')}
value={search}
onChange={(event) => {
onSearch(event.target.value);
onPageChange(0);
}}
className="max-w-sm"
/>
<div className="flex flex-1 items-center gap-4">
<Input
placeholder={t('search')}
value={search}
onChange={(event) => {
onSearch(event.target.value);
onPageChange(0);
}}
className="max-w-sm"
/>
{isDemo && (
<span className="text-sm text-primary">{t('fakeData')}</span>
)}
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" className="cursor-pointer">

View File

@ -27,6 +27,9 @@ import { useTranslations } from 'next-intl';
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';
return [
{
title: t('dashboard.title'),
@ -37,7 +40,7 @@ export function getSidebarLinks(): NestedMenuItem[] {
{
title: t('admin.title'),
icon: <SettingsIcon className="size-4 shrink-0" />,
authorizeOnly: ['admin'],
authorizeOnly: isDemo ? ['admin', 'user'] : ['admin'],
items: [
{
title: t('admin.users.title'),