feat: 修改代码结构

This commit is contained in:
speakeloudest 2025-08-15 03:22:14 -07:00
parent 77e246198f
commit 5524d25d9a
24 changed files with 128 additions and 77 deletions

View File

@ -1,5 +1,5 @@
# Default Language
NEXT_PUBLIC_DEFAULT_LANGUAGE=en-US
NEXT_PUBLIC_DEFAULT_LANGUAGE=zh-CN
# Site URL and API URL
NEXT_PUBLIC_SITE_URL=https://admin.ppanel.dev

View File

@ -62,20 +62,52 @@ export function Invite() {
</TableRow>
<TableRow>
<TableCell>
<Label>{t('inviteCommissionPercentage')}</Label>
<p className='text-muted-foreground text-xs'>
{t('inviteCommissionPercentageDescription')}
</p>
<Label></Label>
<p className='text-muted-foreground text-xs'></p>
</TableCell>
<TableCell className='text-right'>
<EnhancedInput
placeholder={t('inputPlaceholder')}
value={data?.referral_percentage}
value={data?.first_yearly_purchase_percentage}
type='number'
min={0}
max={100}
suffix='%'
onValueBlur={(value) => updateConfig('referral_percentage', value)}
onValueBlur={(value) => updateConfig('first_yearly_purchase_percentage', value)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Label></Label>
<p className='text-muted-foreground text-xs'></p>
</TableCell>
<TableCell className='text-right'>
<EnhancedInput
placeholder={t('inputPlaceholder')}
value={data?.first_purchase_percentage}
type='number'
min={0}
max={100}
suffix='%'
onValueBlur={(value) => updateConfig('first_purchase_percentage', value)}
/>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Label></Label>
<p className='text-muted-foreground text-xs'></p>
</TableCell>
<TableCell className='text-right'>
<EnhancedInput
placeholder={t('inputPlaceholder')}
value={data?.non_first_purchase_percentage}
type='number'
min={0}
max={100}
suffix='%'
onValueBlur={(value) => updateConfig('non_first_purchase_percentage', value)}
/>
</TableCell>
</TableRow>

View File

@ -656,9 +656,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
{
name: 'discount',
type: 'number',
min: 0.01,
max: 100,
step: 0.01,
placeholder: t('form.discountPercent'),
suffix: '%',
},

View File

@ -1,5 +1,5 @@
# Default Language
NEXT_PUBLIC_DEFAULT_LANGUAGE=en-US
NEXT_PUBLIC_DEFAULT_LANGUAGE=zh-CN
# Site URL and API URL
NEXT_PUBLIC_SITE_URL=https://user.ppanel.dev

View File

@ -18,7 +18,7 @@ export function Header() {
const pathname = usePathname();
const items = useMemo(() => findNavByUrl(pathname), [pathname]);
return (
<header className='flex h-[84px] w-full items-center justify-between gap-2 md:hidden'>
<header className='box-content flex h-[84px] items-center justify-between gap-2 px-4 pt-4 md:hidden'>
<SidebarTrigger />
<Breadcrumb>
<BreadcrumbList className={'text-[36px] font-semibold'}>

View File

@ -13,8 +13,8 @@ export default async function DashboardLayout({ children }: { children: React.Re
<SidebarLeft className='w-[288px] border-r-0 bg-transparent lg:flex' />
<SidebarInset className='relative flex-grow overflow-hidden'>
{/*<LanguageSwitch />*/}
<div className='h-[calc(100vh-56px)] flex-grow gap-4 overflow-auto p-4'>
<Header />
<Header />
<div className='h-[calc(100vh-84px)] flex-grow gap-4 overflow-auto p-4 pt-0 sm:pt-9'>
{children}
</div>
</SidebarInset>

View File

@ -31,13 +31,13 @@ export default function Page() {
}}
renderItem={(item) => {
return (
<Card className='rounded-[32px] border border-[#D9D9D9] pb-5 pl-3 pr-3 pt-3 shadow-[0px_0px_4.5px_0px_rgba(0,0,0,0.25)] sm:pl-16'>
<Card className='rounded-[32px] border border-[#D9D9D9] pb-5 pl-5 pr-4 pt-3 shadow-[0px_0px_4.5px_0px_rgba(0,0,0,0.25)] sm:pl-16'>
<div className={'flex justify-between'}>
<div>
<div className={'text-[#666]'}>{t('orderNo')}</div>
<p className='text-xs text-[#4D4D4D]'>{item.order_no}</p>
</div>
<div>
<div className={'hidden sm:block'}>
{item.status === 1 ? (
<>
<AiroButton
@ -64,8 +64,20 @@ export default function Page() {
</AiroButton>
)}
</div>
<div className={'sm:hidden'}>
<AiroButton
variant={'dangerLink'}
className={'min-w-0 px-0'}
onClick={async () => {
await closeOrder({ orderNo: item.order_no });
ref.current?.refresh();
}}
>
{t('cancelOrder')}
</AiroButton>
</div>
</div>
<CardContent className='px-0 py-3 text-sm'>
<CardContent className='px-0 py-3 pb-0 text-sm sm:pb-6'>
<ul className='grid grid-cols-2 gap-3 *:flex *:flex-col lg:grid-cols-4'>
<li>
<span className='text-[#225BA9]'>{t('name')}</span>
@ -88,6 +100,24 @@ export default function Page() {
<time>{formatDate(item.created_at)}</time>
</li>
</ul>
<div className={'mt-2 flex justify-center sm:hidden'}>
{item.status === 1 ? (
<>
<AiroButton variant={'primary'} className={'ml-2'} asChild>
<Link key='payment' href={`/payment?order_no=${item.order_no}`}>
{t('payment')}
</Link>
</AiroButton>
</>
) : (
<AiroButton
variant={'default'}
onClick={() => OrderDetailDialogRef?.current?.show(item.order_no)}
>
{t('detail')}
</AiroButton>
)}
</div>
</CardContent>
</Card>
);

View File

@ -38,7 +38,7 @@ export default function Page() {
</div>
<div
className={
'-mt-5 text-right text-lg font-bold text-[#666666] sm:mt-0 sm:text-center sm:font-medium'
'mt-0 text-right text-lg font-bold text-[#666666] sm:mt-0 sm:text-center sm:font-medium'
}
>
{t('description')}

View File

@ -158,8 +158,8 @@ export default function Page() {
}}
renderItem={(item) => {
return (
<Card className='overflow-hidden rounded-[32px] sm:pl-16'>
<CardHeader className='flex flex-row items-center justify-between gap-2 space-y-0 bg-transparent p-3 pb-0 sm:pb-3'>
<Card className='overflow-hidden rounded-[20px] pl-5 sm:rounded-[32px] sm:pl-16'>
<CardHeader className='flex flex-row items-center justify-between gap-2 space-y-0 bg-transparent p-3 pb-0 pl-0 sm:pb-3'>
<CardTitle>
<span
className={cn(
@ -220,7 +220,7 @@ export default function Page() {
</CardDescription>
</CardHeader>
<CardContent className='p-3 text-[10px] sm:text-sm'>
<CardContent className='p-3 pl-0 text-[10px] sm:text-sm'>
<ul className='grid grid-cols-2 gap-3 *:flex *:flex-col lg:grid-cols-3'>
<li>
<span className='font-normal text-[#225BA9]'>{t('title')}</span>

View File

@ -87,7 +87,7 @@ export default function LoginForm({
className={
'h-[60px] rounded-[20px] text-xl shadow-[inset_0_0_7.6px_0_#00000040]'
}
placeholder='password'
placeholder='Password'
type='password'
{...field}
/>

View File

@ -108,7 +108,7 @@ export default async function RootLayout({
<body
suppressHydrationWarning
// ${fontSans.variable} ${fontMono.variable}
className={`size-full min-h-[calc(100dvh-env(safe-area-inset-top))] font-sans ${inter.className} antialiased`}
className={`size-full min-h-[calc(100dvh-env(safe-area-inset-top))] ${inter.className} antialiased`}
>
<NextIntlClientProvider messages={messages}>
<NextTopLoader showSpinner={false} />

View File

@ -0,0 +1,4 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="30" height="30" rx="15" fill="currentColor"/>
<path d="M14.244 8.936H15.756V11.33H20.726V18.162H19.256V17.392H15.756V22.068H14.244V17.392H10.758V18.162H9.288V11.33H14.244V8.936ZM10.758 15.964H14.244V12.758H10.758V15.964ZM15.756 15.964H19.256V12.758H15.756V15.964Z" fill="#0F2C53"/>
</svg>

After

Width:  |  Height:  |  Size: 398 B

View File

@ -1,4 +1,4 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="30" height="30" rx="15" fill="#EAEAEA"/>
<rect width="30" height="30" rx="15" fill="currentColor"/>
<path d="M13.5879 15.7266H9.39258V17.9473H14.1738V19.5H7.62891V10.8633H13.9629V12.3926H9.39258V14.2266H13.5879V15.7266ZM22.5645 19.5H20.7598L17.2324 13.3652V19.5H15.5508V10.8633H17.4434L20.8828 16.8926V10.8633H22.5645V19.5Z" fill="#0F2C53"/>
</svg>

Before

Width:  |  Height:  |  Size: 399 B

After

Width:  |  Height:  |  Size: 404 B

View File

@ -34,10 +34,10 @@ const PlanTabs: React.FC<PlanTabProps> = (props) => {
>
<TabsList className='relative mb-8 grid h-[74px] grid-cols-2 flex-wrap rounded-full bg-[#EAEAEA] p-2.5 md:mb-16'>
{tabValue === 'year' ? (
<span className='absolute -top-8 left-16 z-10 rounded-md bg-[#E22C2E] px-2 py-0.5 text-[10px] font-bold leading-none text-white shadow sm:text-xs'>
<span className='absolute -top-8 left-16 z-10 rounded-sm bg-[#E22C2E] px-2 py-0.5 text-[10px] font-bold leading-none text-white shadow sm:text-xs'>
-{props.discount}%{/* 小三角箭头 */}
<span
className='absolute right-0 top-[80%] h-10 w-2 bg-[#E22C2E]'
className='absolute right-0 top-[75%] h-10 w-2 bg-[#E22C2E]'
style={{ clipPath: 'polygon(100% 0, 100% 100%, 0 0)' }}
/>
</span>

View File

@ -74,8 +74,8 @@ export default function Affiliate() {
{inviteListData?.total}
</div>
<div className={'grid grid-cols-2 gap-[10px] sm:grid-cols-1 sm:gap-5 lg:grid-cols-2'}>
<div className='rounded-[20px] bg-[#EAEAEA] px-4 py-2 shadow-sm transition-all duration-300 hover:shadow-md sm:py-4'>
<p className='font-medium text-[#666] opacity-80 sm:mb-3 sm:text-sm'>
<div className='rounded-[20px] border-2 border-[#EAEAEA] bg-[#EAEAEA] px-4 py-2 shadow-sm transition-all duration-300 hover:shadow-md sm:py-4'>
<p className='flex justify-between font-medium text-[#666] opacity-80 sm:mb-3 sm:text-sm'>
{t('totalCommission')}
</p>
<p className='text-xl font-bold text-[#225BA9] sm:text-2xl'>

View File

@ -45,7 +45,7 @@ export default function LanguageSwitch() {
return (
<div>
<div
className='flex cursor-pointer items-center'
className='flex items-center rounded-full'
onClick={() => {
console.log(locale, 111);
if (locale === 'en-US') {
@ -55,7 +55,10 @@ export default function LanguageSwitch() {
}
}}
>
<SvgIcon name={'language'} />
<SvgIcon
name={locale === 'en-US' ? 'language' : 'language-zh'}
className={'cursor-pointer text-[#EAEAEA] hover:text-[#B5C9E2]'}
/>
<span className='sr-only'>{languages[locale as keyof typeof languages]}</span>
</div>
</div>

View File

@ -1,6 +1,7 @@
'use client';
import PaymentMethods from '@/components/subscribe/payment-methods';
import PlanTabs from '@/components/SubscribePlan/PlanTabs/PlanTabs';
import useGlobalStore from '@/config/use-global';
import { preCreateOrder, renewal } from '@/services/user/order';
import { useQuery } from '@tanstack/react-query';
@ -14,7 +15,6 @@ import {
DialogTrigger,
} from '@workspace/airo-ui/components/dialog';
import { Separator } from '@workspace/airo-ui/components/separator';
import { Tabs, TabsList, TabsTrigger } from '@workspace/airo-ui/components/tabs';
import { LoaderCircle } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { useRouter } from 'next/navigation';
@ -115,11 +115,9 @@ export default function Renewal({ id, subscribe, className }: Readonly<RenewalPr
{t('renewSubscription')}
</div>
<div>
<Tabs
defaultValue='year'
className='mt-8 text-center sm:mt-6'
value={tabValue}
onValueChange={(val) => {
<PlanTabs
tabValue={tabValue}
setTabValue={(val) => {
if (val === 'year') {
handleChange('quantity', 12);
} else if (val === 'month') {
@ -127,32 +125,8 @@ export default function Renewal({ id, subscribe, className }: Readonly<RenewalPr
}
setTabValue(val);
}}
>
<TabsList className='relative mb-8 h-[74px] flex-wrap rounded-full bg-[#EAEAEA] p-2.5'>
{tabValue === 'year' ? (
<span className='absolute -top-8 left-16 z-10 rounded-md bg-[#E22C2E] px-2 py-0.5 text-[10px] font-bold leading-none text-white shadow sm:text-xs'>
{t('discount20')}
{/* 小三角箭头 */}
<span
className='absolute right-0 top-[80%] h-10 w-2 bg-[#E22C2E]'
style={{ clipPath: 'polygon(100% 0, 100% 100%, 0 0)' }}
/>
</span>
) : null}
<TabsTrigger
className='rounded-full px-8 py-3.5 text-xl data-[state=active]:bg-[#0F2C53] data-[state=active]:text-white md:px-12'
value='year'
>
{t('yearlyPlan')}
</TabsTrigger>
<TabsTrigger
className='rounded-full px-8 py-3.5 text-xl data-[state=active]:bg-[#0F2C53] data-[state=active]:text-white md:px-12'
value='month'
>
{t('monthlyPlan')}
</TabsTrigger>
</TabsList>
</Tabs>
discount={20}
/>
</div>
<div className='w-full'>

View File

@ -63,7 +63,7 @@ export function UserNav({ from = '' }: { from?: string }) {
forceMount
align='end'
side={from === 'profile' ? (isMobile ? 'top' : 'right') : undefined}
className='mt-[1px] flex w-[var(--sidebar-width)] flex-col gap-1 rounded-[28px] border-0 bg-transparent pl-[26px] pr-4 shadow-none md:w-48 md:gap-3 md:border md:bg-white md:p-3'
className='mt-[1px] flex w-[var(--sidebar-width)] flex-col gap-1 rounded-[28px] border-0 bg-transparent p-0 pl-[26px] pr-4 shadow-none md:w-48 md:gap-3 md:border md:bg-white md:p-3'
style={{
'--sidebar-width': '14rem',
}}

View File

@ -12,6 +12,7 @@ const buttonVariants = cva(
default: 'bg-[#0F2C53] text-primary-foreground shadow hover:bg-[#225BA9]',
primary: 'bg-[#225BA9] text-primary-foreground shadow hover:bg-[#0F2C53] ',
danger: 'bg-[#FF4248] text-primary-foreground shadow hover:bg-[#E22C2E]',
dangerLink: 'text-[#E22C2E] ',
destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
outline:
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',

View File

@ -8,7 +8,7 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
<input
type={type}
className={cn(
'border-input file:text-foreground placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 w-full rounded-md border bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
'border-input file:text-foreground placeholder:text-muted-foreground focus-visible:ring-ring flex h-9 w-full rounded-md border bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
className,
)}
ref={ref}

View File

@ -35,7 +35,7 @@ export function ColumnFilter<TData>({ table, params, filters }: ColumnFilterProp
return (
<Combobox
key={param.key}
className='w-32'
className='w-24'
placeholder={param.placeholder || 'Choose...'}
value={filters[param.key] || ''}
onChange={(value) => {
@ -48,7 +48,7 @@ export function ColumnFilter<TData>({ table, params, filters }: ColumnFilterProp
return (
<Input
key={param.key}
className='w-16 rounded-[20px] text-xs shadow-[inset_0_0_7.6px_0_#00000040] sm:w-64 sm:text-xl'
className='w-36 rounded-[20px] text-xs shadow-[inset_0_0_7.6px_0_#00000040] sm:w-64 sm:text-xl'
placeholder={param.placeholder || 'Search...'}
value={filters[param.key] || ''}
onChange={(event) => updateFilter(param.key, event.target.value)}

View File

@ -134,13 +134,22 @@ export function ProList<TData, TValue extends Record<string, unknown>>({
return (
<div className='flex max-w-full flex-col gap-2 sm:gap-4'>
<div className='flex flex-wrap-reverse items-center justify-between gap-4'>
<div>
<div className={'w-full'}>
{params ? (
<ColumnFilter
table={table}
params={params}
filters={Object.fromEntries(columnFilters.map((item) => [item.id, item.value]))}
/>
<div className='flex w-full justify-between'>
<ColumnFilter
table={table}
params={params}
filters={Object.fromEntries(columnFilters.map((item) => [item.id, item.value]))}
/>
<Button
variant='outline'
className='h-8 w-8 rounded-full bg-[#D9D9D9] p-2 sm:hidden'
onClick={fetchData}
>
<RefreshCcw className='h-4 w-4 text-[#848484]' />
</Button>
</div>
) : (
header?.title
)}
@ -150,7 +159,7 @@ export function ProList<TData, TValue extends Record<string, unknown>>({
<>
<Button
variant='outline'
className='h-8 w-8 rounded-full bg-[#D9D9D9] p-2'
className='hidden h-8 w-8 rounded-full bg-[#D9D9D9] p-2 sm:block'
onClick={fetchData}
>
<RefreshCcw className='h-4 w-4 text-[#848484]' />

View File

@ -60,8 +60,8 @@ install_global_package() {
# Check and install required global npm packages
install_global_package "@lobehub/i18n-cli"
install_global_package "@lobehub/commit-cli"
# install_global_package "@lobehub/commit-cli"
# Run lobe-commit interactively
echo "Running lobe-commit -i..."
lobe-commit -i || echo "lobe-commit failed. Skipping."
# echo "Running lobe-commit -i..."
# lobe-commit -i || echo "lobe-commit failed. Skipping."

View File

@ -9,8 +9,8 @@ mkdir -p $OUT_DIR
# Declare an array of projects to build
PROJECTS=(
"ppanel-admin-web:apps/admin:3000"
"ppanel-user-web:apps/user:3001"
"ppanel-admin-web:apps/admin:3001"
"ppanel-user-web:apps/user:3002"
)
# Step 1: Install dependencies