Compare commits

...

2 Commits

Author SHA1 Message Date
hailin 5eae4464ef fix(mining-app): remove unnecessary token refresh on app startup
Users were being redirected to login page when clicking navigation
because the background token refresh was failing and clearing user state.

Token refresh should only happen when API returns 401, not on every app launch.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 20:28:07 -08:00
hailin d43a70de93 feat(mining-admin): implement complete system accounts feature
- Add system account types and display metadata
- Create API layer with getList and getSummary endpoints
- Add React Query hooks for data fetching
- Create AccountCard, AccountsTable, SummaryCards components
- Refactor page with tabs, refresh button, and error handling
- Add Alert UI component

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 20:27:59 -08:00
11 changed files with 770 additions and 114 deletions

View File

@ -1,128 +1,137 @@
'use client';
import { PageHeader } from '@/components/layout/page-header';
import { useQuery } from '@tanstack/react-query';
import { apiClient } from '@/lib/api/client';
import { formatDecimal } from '@/lib/utils/format';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Skeleton } from '@/components/ui/skeleton';
import { Building2, Flame, ShoppingCart, Coins } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { AlertCircle, RefreshCw } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { useQueryClient } from '@tanstack/react-query';
interface SystemAccount {
id: string;
accountType: string;
accountName: string;
balance: string;
description: string;
}
const accountIcons: Record<string, typeof Building2> = {
DISTRIBUTION_POOL: Coins,
BLACK_HOLE: Flame,
CIRCULATION_POOL: ShoppingCart,
SYSTEM_OPERATION: Building2,
SYSTEM_PROVINCE: Building2,
SYSTEM_CITY: Building2,
};
const accountColors: Record<string, string> = {
DISTRIBUTION_POOL: 'text-green-500',
BLACK_HOLE: 'text-orange-500',
CIRCULATION_POOL: 'text-blue-500',
SYSTEM_OPERATION: 'text-purple-500',
SYSTEM_PROVINCE: 'text-indigo-500',
SYSTEM_CITY: 'text-pink-500',
};
import {
useSystemAccountsSummary,
useCategorizedAccounts,
AccountCard,
AccountsTable,
SummaryCards,
} from '@/features/system-accounts';
export default function SystemAccountsPage() {
const { data: accounts, isLoading } = useQuery({
queryKey: ['system-accounts'],
queryFn: async () => {
const response = await apiClient.get('/system-accounts');
return response.data.data as SystemAccount[];
},
});
const queryClient = useQueryClient();
const mainAccounts = accounts?.filter((a) =>
['DISTRIBUTION_POOL', 'BLACK_HOLE', 'CIRCULATION_POOL'].includes(a.accountType)
);
const systemAccounts = accounts?.filter((a) =>
['SYSTEM_OPERATION', 'SYSTEM_PROVINCE', 'SYSTEM_CITY'].includes(a.accountType)
);
const {
data: summary,
isLoading: summaryLoading,
error: summaryError,
} = useSystemAccountsSummary();
const {
data: categorized,
isLoading: accountsLoading,
error: accountsError,
total,
} = useCategorizedAccounts();
const isLoading = summaryLoading || accountsLoading;
const hasError = summaryError || accountsError;
const handleRefresh = () => {
queryClient.invalidateQueries({ queryKey: ['system-accounts'] });
};
return (
<div className="space-y-6">
<PageHeader title="系统账户" description="查看系统账户余额" />
<PageHeader
title="系统账户"
description="查看系统账户余额和算力分布"
actions={
<Button
variant="outline"
size="sm"
onClick={handleRefresh}
disabled={isLoading}
>
<RefreshCw className={`h-4 w-4 mr-2 ${isLoading ? 'animate-spin' : ''}`} />
</Button>
}
/>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{isLoading
? [...Array(3)].map((_, i) => (
<Card key={i}>
<CardContent className="p-6">
<Skeleton className="h-20 w-full" />
</CardContent>
</Card>
))
: mainAccounts?.map((account) => {
const Icon = accountIcons[account.accountType] || Building2;
const color = accountColors[account.accountType] || 'text-gray-500';
return (
<Card key={account.id}>
<CardContent className="p-6">
<div className="flex items-start justify-between">
<div>
<p className="text-sm text-muted-foreground">{account.accountName}</p>
<p className="text-2xl font-bold font-mono mt-1">{formatDecimal(account.balance, 4)}</p>
<p className="text-xs text-muted-foreground mt-2">{account.description}</p>
</div>
<Icon className={`h-8 w-8 ${color}`} />
</div>
</CardContent>
</Card>
);
})}
</div>
{hasError && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>
</AlertDescription>
</Alert>
)}
<Card>
<CardHeader>
<CardTitle className="text-lg"></CardTitle>
</CardHeader>
<CardContent className="p-0">
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-right"></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{isLoading ? (
[...Array(3)].map((_, i) => (
<TableRow key={i}>
{[...Array(4)].map((_, j) => (
<TableCell key={j}>
<Skeleton className="h-4 w-full" />
</TableCell>
{/* Summary Cards */}
<SummaryCards summary={summary} isLoading={summaryLoading} />
{/* Main Content with Tabs */}
<Tabs defaultValue="overview" className="space-y-4">
<TabsList>
<TabsTrigger value="overview"></TabsTrigger>
<TabsTrigger value="all"></TabsTrigger>
</TabsList>
<TabsContent value="overview" className="space-y-6">
{/* Main Pools - Card Grid */}
{categorized.mainPools.length > 0 && (
<div>
<h3 className="text-lg font-semibold mb-4"></h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
{accountsLoading
? [...Array(3)].map((_, i) => (
<Card key={i}>
<CardContent className="p-6">
<Skeleton className="h-20 w-full" />
</CardContent>
</Card>
))
: categorized.mainPools.map((account) => (
<AccountCard key={account.accountType} account={account} />
))}
</TableRow>
))
) : (
systemAccounts?.map((account) => (
<TableRow key={account.id}>
<TableCell className="font-mono">{account.accountType}</TableCell>
<TableCell>{account.accountName}</TableCell>
<TableCell className="text-right font-mono">{formatDecimal(account.balance, 4)}</TableCell>
<TableCell className="text-muted-foreground">{account.description}</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</CardContent>
</Card>
</div>
</div>
)}
{/* System Distribution Accounts - Table */}
<AccountsTable
title="系统分配账户"
accounts={categorized.systemAccounts}
isLoading={accountsLoading}
showSyncInfo
/>
{/* Fixed Accounts if any */}
{categorized.fixedAccounts.length > 0 && (
<AccountsTable
title="固定账户"
accounts={categorized.fixedAccounts}
isLoading={accountsLoading}
showSyncInfo
/>
)}
</TabsContent>
<TabsContent value="all" className="space-y-6">
{/* All Accounts in one table */}
<AccountsTable
title={`全部系统账户 (${total})`}
accounts={[
...categorized.mainPools,
...categorized.systemAccounts,
...categorized.fixedAccounts,
...categorized.otherAccounts,
]}
isLoading={accountsLoading}
showSyncInfo
/>
</TabsContent>
</Tabs>
</div>
);
}

View File

@ -0,0 +1,59 @@
import * as React from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const alertVariants = cva(
'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
{
variants: {
variant: {
default: 'bg-background text-foreground',
destructive:
'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
},
},
defaultVariants: {
variant: 'default',
},
}
);
const Alert = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
<div
ref={ref}
role="alert"
className={cn(alertVariants({ variant }), className)}
{...props}
/>
));
Alert.displayName = 'Alert';
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h5
ref={ref}
className={cn('mb-1 font-medium leading-none tracking-tight', className)}
{...props}
/>
));
AlertTitle.displayName = 'AlertTitle';
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('text-sm [&_p]:leading-relaxed', className)}
{...props}
/>
));
AlertDescription.displayName = 'AlertDescription';
export { Alert, AlertTitle, AlertDescription };

View File

@ -0,0 +1,43 @@
import { apiClient } from '@/lib/api/client';
import type {
SystemAccount,
SystemAccountsResponse,
SystemAccountsSummary,
} from '@/types/system-account';
export const systemAccountsApi = {
/**
* Get all system accounts (merged local + synced data)
*/
getList: async (): Promise<SystemAccountsResponse> => {
const response = await apiClient.get('/system-accounts');
return response.data.data;
},
/**
* Get system accounts summary with mining config and circulation pool
*/
getSummary: async (): Promise<SystemAccountsSummary> => {
const response = await apiClient.get('/system-accounts/summary');
return response.data.data;
},
};
// Helper to categorize accounts for display
export function categorizeAccounts(accounts: SystemAccount[]) {
const mainPools = ['DISTRIBUTION_POOL', 'BLACK_HOLE', 'CIRCULATION_POOL'];
const systemAccounts = ['SYSTEM_OPERATION', 'SYSTEM_PROVINCE', 'SYSTEM_CITY'];
const fixedAccounts = ['COST_ACCOUNT', 'OPERATION_ACCOUNT', 'HQ_COMMUNITY', 'RWAD_POOL_PENDING'];
return {
mainPools: accounts.filter((a) => mainPools.includes(a.accountType)),
systemAccounts: accounts.filter((a) => systemAccounts.includes(a.accountType)),
fixedAccounts: accounts.filter((a) => fixedAccounts.includes(a.accountType)),
otherAccounts: accounts.filter(
(a) =>
!mainPools.includes(a.accountType) &&
!systemAccounts.includes(a.accountType) &&
!fixedAccounts.includes(a.accountType)
),
};
}

View File

@ -0,0 +1,65 @@
'use client';
import { Card, CardContent } from '@/components/ui/card';
import { formatDecimal } from '@/lib/utils/format';
import { getAccountDisplayInfo, type SystemAccount } from '@/types/system-account';
import { Coins, Flame, ShoppingCart, Building2, Wallet, Landmark, Box } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
const iconMap = {
coins: Coins,
flame: Flame,
'shopping-cart': ShoppingCart,
building: Building2,
wallet: Wallet,
landmark: Landmark,
box: Box,
};
interface AccountCardProps {
account: SystemAccount;
}
export function AccountCard({ account }: AccountCardProps) {
const displayInfo = getAccountDisplayInfo(account.accountType);
const Icon = iconMap[displayInfo.icon] || Building2;
// Use contributionBalance if available (synced), otherwise totalContribution (local)
const balance = account.contributionBalance || account.totalContribution || '0';
const displayName = account.name || displayInfo.label;
return (
<Card className="hover:shadow-md transition-shadow">
<CardContent className="p-6">
<div className="flex items-start justify-between">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<p className="text-sm font-medium text-muted-foreground truncate">
{displayName}
</p>
{account.source === 'synced' && (
<Badge variant="outline" className="text-xs shrink-0">
</Badge>
)}
</div>
<p className="text-2xl font-bold font-mono mt-1">
{formatDecimal(balance, 4)}
</p>
<p className="text-xs text-muted-foreground mt-2 line-clamp-2">
{account.description || displayInfo.description}
</p>
{account.contributionNeverExpires && (
<Badge variant="secondary" className="mt-2 text-xs">
</Badge>
)}
</div>
<div className={`p-3 rounded-lg ${displayInfo.bgColor} shrink-0 ml-4`}>
<Icon className={`h-6 w-6 ${displayInfo.color}`} />
</div>
</div>
</CardContent>
</Card>
);
}

View File

@ -0,0 +1,134 @@
'use client';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Skeleton } from '@/components/ui/skeleton';
import { formatDecimal } from '@/lib/utils/format';
import { getAccountDisplayInfo, type SystemAccount } from '@/types/system-account';
import { formatDistanceToNow } from 'date-fns';
import { zhCN } from 'date-fns/locale';
interface AccountsTableProps {
title: string;
accounts: SystemAccount[];
isLoading?: boolean;
showSyncInfo?: boolean;
}
export function AccountsTable({
title,
accounts,
isLoading = false,
showSyncInfo = false,
}: AccountsTableProps) {
return (
<Card>
<CardHeader>
<CardTitle className="text-lg flex items-center gap-2">
{title}
{accounts.length > 0 && (
<Badge variant="secondary" className="text-xs">
{accounts.length}
</Badge>
)}
</CardTitle>
</CardHeader>
<CardContent className="p-0">
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[200px]"></TableHead>
<TableHead></TableHead>
<TableHead className="text-right">/</TableHead>
<TableHead></TableHead>
{showSyncInfo && <TableHead></TableHead>}
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{isLoading ? (
[...Array(3)].map((_, i) => (
<TableRow key={i}>
{[...Array(showSyncInfo ? 6 : 5)].map((_, j) => (
<TableCell key={j}>
<Skeleton className="h-4 w-full" />
</TableCell>
))}
</TableRow>
))
) : accounts.length === 0 ? (
<TableRow>
<TableCell
colSpan={showSyncInfo ? 6 : 5}
className="text-center text-muted-foreground py-8"
>
</TableCell>
</TableRow>
) : (
accounts.map((account) => {
const displayInfo = getAccountDisplayInfo(account.accountType);
const balance = account.contributionBalance || account.totalContribution || '0';
return (
<TableRow key={account.accountType}>
<TableCell>
<div className="flex items-center gap-2">
<span
className={`inline-block w-2 h-2 rounded-full ${displayInfo.color.replace('text-', 'bg-')}`}
/>
<code className="text-xs bg-muted px-1.5 py-0.5 rounded">
{account.accountType}
</code>
</div>
</TableCell>
<TableCell className="font-medium">
{account.name || displayInfo.label}
</TableCell>
<TableCell className="text-right font-mono">
{formatDecimal(balance, 4)}
{account.contributionNeverExpires && (
<Badge variant="outline" className="ml-2 text-xs">
</Badge>
)}
</TableCell>
<TableCell>
<Badge
variant={account.source === 'synced' ? 'default' : 'secondary'}
className="text-xs"
>
{account.source === 'synced' ? 'CDC同步' : '本地'}
</Badge>
</TableCell>
{showSyncInfo && (
<TableCell className="text-muted-foreground text-sm">
{account.syncedAt
? formatDistanceToNow(new Date(account.syncedAt), {
addSuffix: true,
locale: zhCN,
})
: '-'}
</TableCell>
)}
<TableCell className="text-muted-foreground text-sm max-w-[200px] truncate">
{account.description || displayInfo.description}
</TableCell>
</TableRow>
);
})
)}
</TableBody>
</Table>
</CardContent>
</Card>
);
}

View File

@ -0,0 +1,3 @@
export { AccountCard } from './account-card';
export { AccountsTable } from './accounts-table';
export { SummaryCards } from './summary-cards';

View File

@ -0,0 +1,132 @@
'use client';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Skeleton } from '@/components/ui/skeleton';
import { formatDecimal, formatNumber } from '@/lib/utils/format';
import type { SystemAccountsSummary } from '@/types/system-account';
import { Database, Coins, Activity, RefreshCcw } from 'lucide-react';
interface SummaryCardsProps {
summary: SystemAccountsSummary | undefined;
isLoading?: boolean;
}
export function SummaryCards({ summary, isLoading = false }: SummaryCardsProps) {
if (isLoading) {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{[...Array(4)].map((_, i) => (
<Card key={i}>
<CardContent className="p-6">
<Skeleton className="h-20 w-full" />
</CardContent>
</Card>
))}
</div>
);
}
if (!summary) {
return null;
}
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{/* System Accounts Count */}
<Card>
<CardContent className="p-6">
<div className="flex items-start justify-between">
<div>
<p className="text-sm text-muted-foreground"></p>
<p className="text-2xl font-bold mt-1">{summary.systemAccounts.count}</p>
<p className="text-xs text-muted-foreground mt-2">
: {formatDecimal(summary.systemAccounts.totalContribution, 4)}
</p>
</div>
<div className="p-3 rounded-lg bg-blue-50">
<Database className="h-6 w-6 text-blue-600" />
</div>
</div>
</CardContent>
</Card>
{/* Synced Contributions */}
<Card>
<CardContent className="p-6">
<div className="flex items-start justify-between">
<div>
<p className="text-sm text-muted-foreground"></p>
<p className="text-2xl font-bold mt-1">{summary.syncedContributions.count}</p>
<p className="text-xs text-muted-foreground mt-2">
: {formatDecimal(summary.syncedContributions.totalBalance, 4)}
</p>
</div>
<div className="p-3 rounded-lg bg-green-50">
<RefreshCcw className="h-6 w-6 text-green-600" />
</div>
</div>
</CardContent>
</Card>
{/* Mining Config */}
<Card>
<CardContent className="p-6">
<div className="flex items-start justify-between">
<div>
<p className="text-sm text-muted-foreground flex items-center gap-2">
{summary.miningConfig?.isActive && (
<Badge variant="default" className="text-xs">
</Badge>
)}
</p>
{summary.miningConfig ? (
<>
<p className="text-2xl font-bold mt-1">
{summary.miningConfig.currentEra}
</p>
<p className="text-xs text-muted-foreground mt-2">
: {formatDecimal(summary.miningConfig.remainingDistribution, 2)}
</p>
</>
) : (
<p className="text-sm text-muted-foreground mt-2"></p>
)}
</div>
<div className="p-3 rounded-lg bg-purple-50">
<Activity className="h-6 w-6 text-purple-600" />
</div>
</div>
</CardContent>
</Card>
{/* Circulation Pool */}
<Card>
<CardContent className="p-6">
<div className="flex items-start justify-between">
<div>
<p className="text-sm text-muted-foreground"></p>
{summary.circulationPool ? (
<>
<p className="text-2xl font-bold mt-1">
{formatDecimal(summary.circulationPool.totalShares, 2)}
</p>
<p className="text-xs text-muted-foreground mt-2">
: {formatDecimal(summary.circulationPool.totalCash, 2)} USDT
</p>
</>
) : (
<p className="text-sm text-muted-foreground mt-2"></p>
)}
</div>
<div className="p-3 rounded-lg bg-amber-50">
<Coins className="h-6 w-6 text-amber-600" />
</div>
</div>
</CardContent>
</Card>
</div>
);
}

View File

@ -0,0 +1,48 @@
import { useQuery } from '@tanstack/react-query';
import { systemAccountsApi, categorizeAccounts } from '../api/system-accounts.api';
import { useMemo } from 'react';
/**
* Hook to fetch all system accounts
*/
export function useSystemAccounts() {
return useQuery({
queryKey: ['system-accounts'],
queryFn: systemAccountsApi.getList,
});
}
/**
* Hook to fetch system accounts summary (includes mining config and circulation pool)
*/
export function useSystemAccountsSummary() {
return useQuery({
queryKey: ['system-accounts', 'summary'],
queryFn: systemAccountsApi.getSummary,
});
}
/**
* Hook to get categorized system accounts for display
*/
export function useCategorizedAccounts() {
const { data, ...rest } = useSystemAccounts();
const categorized = useMemo(() => {
if (!data?.accounts) {
return {
mainPools: [],
systemAccounts: [],
fixedAccounts: [],
otherAccounts: [],
};
}
return categorizeAccounts(data.accounts);
}, [data?.accounts]);
return {
...rest,
data: categorized,
total: data?.total ?? 0,
};
}

View File

@ -0,0 +1,12 @@
// API
export { systemAccountsApi, categorizeAccounts } from './api/system-accounts.api';
// Hooks
export {
useSystemAccounts,
useSystemAccountsSummary,
useCategorizedAccounts,
} from './hooks/use-system-accounts';
// Components
export { AccountCard, AccountsTable, SummaryCards } from './components';

View File

@ -0,0 +1,152 @@
// System account types based on backend SystemAccountsService
export type SystemAccountType =
| 'DISTRIBUTION_POOL'
| 'BLACK_HOLE'
| 'CIRCULATION_POOL'
| 'SYSTEM_OPERATION'
| 'SYSTEM_PROVINCE'
| 'SYSTEM_CITY'
| 'COST_ACCOUNT'
| 'OPERATION_ACCOUNT'
| 'HQ_COMMUNITY'
| 'RWAD_POOL_PENDING';
export type AccountSource = 'local' | 'synced';
export interface SystemAccount {
accountType: SystemAccountType;
name?: string;
description?: string;
totalContribution?: string;
contributionBalance?: string;
contributionNeverExpires?: boolean;
syncedAt?: string;
createdAt?: string;
source: AccountSource;
}
export interface SystemAccountsResponse {
accounts: SystemAccount[];
total: number;
}
export interface MiningConfig {
totalShares: string;
distributionPool: string;
remainingDistribution: string;
currentEra: number;
isActive: boolean;
activatedAt: string | null;
}
export interface CirculationPool {
totalShares: string;
totalCash: string;
}
export interface SystemAccountsSummary {
systemAccounts: {
count: number;
totalContribution: string;
};
syncedContributions: {
count: number;
totalBalance: string;
};
miningConfig: MiningConfig | null;
circulationPool: CirculationPool | null;
}
// Account display metadata
export interface AccountDisplayInfo {
label: string;
description: string;
color: string;
bgColor: string;
icon: 'coins' | 'flame' | 'shopping-cart' | 'building' | 'wallet' | 'landmark' | 'box';
}
export const ACCOUNT_DISPLAY_INFO: Record<string, AccountDisplayInfo> = {
DISTRIBUTION_POOL: {
label: '分发池',
description: '用于挖矿分发的代币池',
color: 'text-green-600',
bgColor: 'bg-green-50',
icon: 'coins',
},
BLACK_HOLE: {
label: '黑洞账户',
description: '永久销毁的代币',
color: 'text-orange-600',
bgColor: 'bg-orange-50',
icon: 'flame',
},
CIRCULATION_POOL: {
label: '流通池',
description: '市场流通中的代币',
color: 'text-blue-600',
bgColor: 'bg-blue-50',
icon: 'shopping-cart',
},
SYSTEM_OPERATION: {
label: '系统运营账户',
description: '系统运营分配账户',
color: 'text-purple-600',
bgColor: 'bg-purple-50',
icon: 'building',
},
SYSTEM_PROVINCE: {
label: '省级系统账户',
description: '无授权商省级账户',
color: 'text-indigo-600',
bgColor: 'bg-indigo-50',
icon: 'landmark',
},
SYSTEM_CITY: {
label: '市级系统账户',
description: '无授权商市级账户',
color: 'text-pink-600',
bgColor: 'bg-pink-50',
icon: 'landmark',
},
COST_ACCOUNT: {
label: '成本账户',
description: '成本分配账户',
color: 'text-slate-600',
bgColor: 'bg-slate-50',
icon: 'wallet',
},
OPERATION_ACCOUNT: {
label: '运营账户',
description: '12% 运营收入账户',
color: 'text-teal-600',
bgColor: 'bg-teal-50',
icon: 'building',
},
HQ_COMMUNITY: {
label: '总部社区账户',
description: '总部社区分配账户',
color: 'text-cyan-600',
bgColor: 'bg-cyan-50',
icon: 'building',
},
RWAD_POOL_PENDING: {
label: 'RWAD 待注入池',
description: 'RWAD 代币待注入池',
color: 'text-amber-600',
bgColor: 'bg-amber-50',
icon: 'box',
},
};
// Helper function to get display info
export function getAccountDisplayInfo(accountType: string): AccountDisplayInfo {
return ACCOUNT_DISPLAY_INFO[accountType] || {
label: accountType,
description: '',
color: 'text-gray-600',
bgColor: 'bg-gray-50',
icon: 'building',
};
}

View File

@ -33,12 +33,11 @@ class _SplashPageState extends ConsumerState<SplashPage> {
final userState = ref.read(userNotifierProvider);
if (userState.isLoggedIn) {
// token
// token
// token API 401
if (mounted) {
context.go(Routes.contribution);
}
// token API 401
ref.read(userNotifierProvider.notifier).refreshTokenIfNeeded();
} else {
context.go(Routes.login);
}