chore: support show admin user page in demo website
This commit is contained in:
parent
50f44fb84c
commit
e284de79a8
@ -78,9 +78,10 @@ NEXT_PUBLIC_STRIPE_PRICE_LIFETIME=""
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Configurations
|
# Configurations
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Disable image optimization
|
# 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
|
||||||
|
RUN_AS_DEMO_WEBSITE="false"
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Analytics
|
# Analytics
|
||||||
|
@ -441,6 +441,7 @@
|
|||||||
"title": "Admin",
|
"title": "Admin",
|
||||||
"users": {
|
"users": {
|
||||||
"title": "Users",
|
"title": "Users",
|
||||||
|
"fakeData": "Note: Faked data for demonstration, some features are disabled",
|
||||||
"error": "Failed to get users",
|
"error": "Failed to get users",
|
||||||
"search": "Search users...",
|
"search": "Search users...",
|
||||||
"columns": {
|
"columns": {
|
||||||
|
@ -442,6 +442,7 @@
|
|||||||
"title": "系统管理",
|
"title": "系统管理",
|
||||||
"users": {
|
"users": {
|
||||||
"title": "用户管理",
|
"title": "用户管理",
|
||||||
|
"fakeData": "注:只为演示功能,数据为假数据,封禁功能不可用",
|
||||||
"error": "获取用户失败",
|
"error": "获取用户失败",
|
||||||
"search": "搜索用户...",
|
"search": "搜索用户...",
|
||||||
"columns": {
|
"columns": {
|
||||||
|
@ -51,6 +51,9 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) {
|
|||||||
const [banExpiresAt, setBanExpiresAt] = useState<Date | undefined>();
|
const [banExpiresAt, setBanExpiresAt] = useState<Date | undefined>();
|
||||||
const triggerRefresh = useUsersStore((state) => state.triggerRefresh);
|
const triggerRefresh = useUsersStore((state) => state.triggerRefresh);
|
||||||
|
|
||||||
|
// show fake data in demo website
|
||||||
|
const isDemo = process.env.NEXT_PUBLIC_DEMO_WEBSITE === 'true';
|
||||||
|
|
||||||
const handleBan = async () => {
|
const handleBan = async () => {
|
||||||
if (!banReason) {
|
if (!banReason) {
|
||||||
setError(t('ban.error'));
|
setError(t('ban.error'));
|
||||||
@ -131,7 +134,7 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) {
|
|||||||
className="size-8 border"
|
className="size-8 border"
|
||||||
/>
|
/>
|
||||||
<span className="hover:underline hover:underline-offset-4">
|
<span className="hover:underline hover:underline-offset-4">
|
||||||
{user.name}
|
{isDemo ? 'MkSaaS User' : user.name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
@ -145,8 +148,10 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) {
|
|||||||
className="size-12 border"
|
className="size-12 border"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<DrawerTitle>{user.name}</DrawerTitle>
|
<DrawerTitle>{isDemo ? 'MkSaaS User' : user.name}</DrawerTitle>
|
||||||
<DrawerDescription>{user.email}</DrawerDescription>
|
<DrawerDescription>
|
||||||
|
{isDemo ? 'example@mksaas.com' : user.email}
|
||||||
|
</DrawerDescription>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DrawerHeader>
|
</DrawerHeader>
|
||||||
@ -158,7 +163,7 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) {
|
|||||||
variant={user.role === 'admin' ? 'default' : 'outline'}
|
variant={user.role === 'admin' ? 'default' : 'outline'}
|
||||||
className="px-1.5"
|
className="px-1.5"
|
||||||
>
|
>
|
||||||
{t(user.role === 'admin' ? 'admin' : 'user')}
|
{user.role === 'admin' ? t('admin') : t('user')}
|
||||||
</Badge>
|
</Badge>
|
||||||
{/* email verified */}
|
{/* email verified */}
|
||||||
<Badge variant="outline" className="px-1.5 hover:bg-accent">
|
<Badge variant="outline" className="px-1.5 hover:bg-accent">
|
||||||
@ -201,10 +206,10 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) {
|
|||||||
{/* ban or unban user */}
|
{/* ban or unban user */}
|
||||||
{user.banned ? (
|
{user.banned ? (
|
||||||
<div className="grid gap-4">
|
<div className="grid gap-4">
|
||||||
<div className="text-muted-foreground">
|
<div className="">
|
||||||
{t('ban.reason')}: {user.banReason}
|
{t('ban.reason')}: {user.banReason}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-muted-foreground">
|
<div className="">
|
||||||
{t('ban.expires')}:{' '}
|
{t('ban.expires')}:{' '}
|
||||||
{(user.banExpires && formatDate(user.banExpires)) ||
|
{(user.banExpires && formatDate(user.banExpires)) ||
|
||||||
t('ban.never')}
|
t('ban.never')}
|
||||||
@ -212,7 +217,7 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) {
|
|||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={handleUnban}
|
onClick={handleUnban}
|
||||||
disabled={isLoading}
|
disabled={isLoading || isDemo}
|
||||||
className="mt-4 cursor-pointer"
|
className="mt-4 cursor-pointer"
|
||||||
>
|
>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
@ -271,7 +276,7 @@ export function UserDetailViewer({ user }: UserDetailViewerProps) {
|
|||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
disabled={isLoading || !banReason}
|
disabled={isLoading || !banReason || isDemo}
|
||||||
className="mt-4 cursor-pointer"
|
className="mt-4 cursor-pointer"
|
||||||
>
|
>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
|
@ -119,6 +119,9 @@ export function UsersTable({
|
|||||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
||||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
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
|
// Map column IDs to translation keys
|
||||||
const columnIdToTranslationKey = {
|
const columnIdToTranslationKey = {
|
||||||
name: 'columns.name' as const,
|
name: 'columns.name' as const,
|
||||||
@ -165,7 +168,7 @@ export function UsersTable({
|
|||||||
) : (
|
) : (
|
||||||
<MailQuestionIcon className="stroke-red-500 dark:stroke-red-400" />
|
<MailQuestionIcon className="stroke-red-500 dark:stroke-red-400" />
|
||||||
)}
|
)}
|
||||||
{user.email}
|
{isDemo ? 'example@mksaas.com' : user.email}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -185,7 +188,7 @@ export function UsersTable({
|
|||||||
variant={role === 'admin' ? 'default' : 'outline'}
|
variant={role === 'admin' ? 'default' : 'outline'}
|
||||||
className="px-1.5"
|
className="px-1.5"
|
||||||
>
|
>
|
||||||
{t(role === 'admin' ? 'admin' : 'user')}
|
{role === 'admin' ? t('admin') : t('user')}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -224,7 +227,7 @@ export function UsersTable({
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="hover:underline hover:underline-offset-4"
|
className="hover:underline hover:underline-offset-4"
|
||||||
>
|
>
|
||||||
{user.customerId}
|
{!isDemo ? user.customerId : 'cus_abcdef123456'}
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
'-'
|
'-'
|
||||||
@ -323,15 +326,20 @@ export function UsersTable({
|
|||||||
return (
|
return (
|
||||||
<div className="w-full flex-col justify-start gap-6 space-y-4">
|
<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">
|
<div className="flex items-center justify-between px-4 lg:px-6 gap-4">
|
||||||
<Input
|
<div className="flex flex-1 items-center gap-4">
|
||||||
placeholder={t('search')}
|
<Input
|
||||||
value={search}
|
placeholder={t('search')}
|
||||||
onChange={(event) => {
|
value={search}
|
||||||
onSearch(event.target.value);
|
onChange={(event) => {
|
||||||
onPageChange(0);
|
onSearch(event.target.value);
|
||||||
}}
|
onPageChange(0);
|
||||||
className="max-w-sm"
|
}}
|
||||||
/>
|
className="max-w-sm"
|
||||||
|
/>
|
||||||
|
{isDemo && (
|
||||||
|
<span className="text-sm text-primary">{t('fakeData')}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="outline" size="sm" className="cursor-pointer">
|
<Button variant="outline" size="sm" className="cursor-pointer">
|
||||||
|
@ -27,6 +27,9 @@ import { useTranslations } from 'next-intl';
|
|||||||
export function getSidebarLinks(): NestedMenuItem[] {
|
export function getSidebarLinks(): NestedMenuItem[] {
|
||||||
const t = useTranslations('Dashboard');
|
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 [
|
return [
|
||||||
{
|
{
|
||||||
title: t('dashboard.title'),
|
title: t('dashboard.title'),
|
||||||
@ -37,7 +40,7 @@ export function getSidebarLinks(): NestedMenuItem[] {
|
|||||||
{
|
{
|
||||||
title: t('admin.title'),
|
title: t('admin.title'),
|
||||||
icon: <SettingsIcon className="size-4 shrink-0" />,
|
icon: <SettingsIcon className="size-4 shrink-0" />,
|
||||||
authorizeOnly: ['admin'],
|
authorizeOnly: isDemo ? ['admin', 'user'] : ['admin'],
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: t('admin.users.title'),
|
title: t('admin.users.title'),
|
||||||
|
Loading…
Reference in New Issue
Block a user