动态生成购买套餐 & 取消初次进后台修改密码的弹窗 & 添加总折扣字段 & 发布v1.5.0版本
This commit is contained in:
@@ -34,20 +34,12 @@ export type LoginSchema = zod.infer<typeof smsSchema | typeof pwdSchema>
|
||||
export default function LoginCard() {
|
||||
const router = useRouter()
|
||||
const refreshProfile = useProfileStore(store => store.refreshProfile)
|
||||
const [mode, setMode] = useState<LoginMode>('phone_code')
|
||||
const [mode, setMode] = useState<LoginMode>('password')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
|
||||
const updateLoginMode = (mode: LoginMode) => {
|
||||
sessionStorage.setItem('login_mode', mode)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const mode = sessionStorage.getItem('login_mode')
|
||||
if (mode) {
|
||||
setMode(mode as LoginMode)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const form = useForm<LoginSchema>({
|
||||
resolver: zodResolver(mode === 'phone_code' ? smsSchema : pwdSchema),
|
||||
defaultValues: {
|
||||
@@ -55,7 +47,16 @@ export default function LoginCard() {
|
||||
password: '',
|
||||
remember: false,
|
||||
},
|
||||
mode: 'onChange',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const savedMode = sessionStorage.getItem('login_mode') as LoginMode
|
||||
if (savedMode && savedMode === 'phone_code') {
|
||||
setMode(savedMode)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handler = form.handleSubmit(async (data) => {
|
||||
setSubmitting(true)
|
||||
try {
|
||||
@@ -93,13 +94,14 @@ export default function LoginCard() {
|
||||
<Tabs
|
||||
value={mode}
|
||||
onValueChange={(val) => {
|
||||
setMode(val as typeof mode)
|
||||
form.reset({username: form.getValues('username'), password: '', remember: false})
|
||||
setMode(val as LoginMode)
|
||||
form.reset({username: '', password: '', remember: false})
|
||||
form.clearErrors()
|
||||
}}
|
||||
className="mb-6">
|
||||
<TabsList className="w-full p-0 bg-white">
|
||||
<Tab value="password">密码登录</Tab>
|
||||
<Tab value="phone_code">验证码登录</Tab>
|
||||
<Tab value="phone_code">验证码登录/注册</Tab>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
<Form<LoginSchema> className="space-y-6" form={form} handler={handler}>
|
||||
@@ -124,6 +126,7 @@ export default function LoginCard() {
|
||||
className="h-10"
|
||||
placeholder="请输入验证码"
|
||||
autoComplete="one-time-code"
|
||||
disabled={submitting}
|
||||
/>
|
||||
<SendMsgByUsername/>
|
||||
</div>
|
||||
@@ -137,6 +140,7 @@ export default function LoginCard() {
|
||||
placeholder="至少6位密码,需包含字母和数字"
|
||||
autoComplete="current-password"
|
||||
minLength={6}
|
||||
disabled={submitting}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -162,6 +166,7 @@ export default function LoginCard() {
|
||||
id={id}
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
disabled={submitting}
|
||||
/>
|
||||
<div className="space-y-1 leading-none">
|
||||
<Label>保持登录</Label>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import {ReactNode, useContext, useState} from 'react'
|
||||
import {ReactNode, useContext, useEffect, useState} from 'react'
|
||||
import Wrap from '@/components/wrap'
|
||||
import Image, {StaticImageData} from 'next/image'
|
||||
import Link from 'next/link'
|
||||
@@ -8,7 +8,10 @@ import prod from '@/assets/header/product/prod.svg'
|
||||
import custom from '@/assets/header/product/custom.svg'
|
||||
import {useRouter} from 'next/navigation'
|
||||
import {HeaderContext} from './common'
|
||||
import {Product} from '@/lib/models/product'
|
||||
import {listProductHome} from '@/actions/product'
|
||||
|
||||
export type ProductItem = Product
|
||||
type TabType = 'domestic' | 'oversea'
|
||||
|
||||
export default function ProductMenu() {
|
||||
@@ -53,24 +56,41 @@ export function Tab(props: {
|
||||
}
|
||||
|
||||
export function Domestic(props: {}) {
|
||||
const [productList, setProductList] = useState<Product[]>([])
|
||||
useEffect(() => {
|
||||
const fetchProducts = async () => {
|
||||
const res = await listProductHome({})
|
||||
if (res.success) {
|
||||
setProductList(res.data)
|
||||
}
|
||||
}
|
||||
fetchProducts()
|
||||
}, [])
|
||||
const shortProduct = productList.find(p => p.name?.includes('短效') || p.code === 'short')
|
||||
const longProduct = productList.find(p => p.name?.includes('长效') || p.code === 'long')
|
||||
|
||||
return (
|
||||
<section role="tabpanel" className="flex-auto">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
|
||||
<div className="grid grid-cols-1 gap-3">
|
||||
<ProductCard
|
||||
icon={prod}
|
||||
label="短效动态 IP"
|
||||
discount="最低4.5折"
|
||||
desc="全国 300+ 城市级定位节点,IP 池资源充足,自动高频切换。适用于数据采集、市场调研、SEO 优化等高并发场景。稳定可靠,响应迅速,助力业务高效运转。"
|
||||
href="/product?type=short"
|
||||
/>
|
||||
<ProductCard
|
||||
icon={prod}
|
||||
label="长效静态 IP"
|
||||
discount="最低4.5折"
|
||||
desc="IP 存活时长可达数小时至数天,连接稳定不掉线。适用于账号养号、社交运营、电商管理等需要持续在线的场景。优质线路保障,为您的长期业务保驾护航。"
|
||||
href="/product?type=long"
|
||||
/>
|
||||
{shortProduct && (
|
||||
<ProductCard
|
||||
icon={prod}
|
||||
label="短效动态 IP"
|
||||
discount="最低4.5折"
|
||||
desc="全国 300+ 城市级定位节点,IP 池资源充足,自动高频切换。适用于数据采集、市场调研、SEO 优化等高并发场景。稳定可靠,响应迅速,助力业务高效运转。"
|
||||
href={`/product?type=${shortProduct.code}`}
|
||||
/>
|
||||
)}
|
||||
{longProduct && (
|
||||
<ProductCard
|
||||
icon={prod}
|
||||
label="长效动态 IP"
|
||||
discount="最低4.5折"
|
||||
desc="IP 存活时长可达数小时至数天,连接稳定不掉线。适用于账号养号、社交运营、电商管理等需要持续在线的场景。优质线路保障,为您的长期业务保驾护航。"
|
||||
href={`/product?type=${longProduct.code}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<ProductCard
|
||||
|
||||
@@ -282,7 +282,7 @@ function ProfileOrLogin() {
|
||||
<span>登录</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/login"
|
||||
href="/login?tab=sms"
|
||||
className={[
|
||||
`w-20 lg:w-24 h-10 lg:h-12 bg-linear-to-r rounded-sm flex items-center justify-center lg:text-lg text-white`,
|
||||
`transition-colors duration-200 ease-in-out`,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import {ReactNode, Suspense, use, useState} from 'react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import {ChangePasswordDialog} from '@/components/composites/dialogs/change-password-dialog'
|
||||
import {RealnameAuthDialog} from '@/components/composites/dialogs/realname-auth-dialog'
|
||||
import UserCenter from '@/components/composites/user-center'
|
||||
import {Button} from '@/components/ui/button'
|
||||
@@ -75,6 +74,7 @@ export function Content(props: {children: ReactNode}) {
|
||||
}
|
||||
function ContentResolved() {
|
||||
const profile = use(useProfileStore(store => store.profile))
|
||||
|
||||
if (profile)
|
||||
return (
|
||||
<>
|
||||
@@ -82,10 +82,10 @@ function ContentResolved() {
|
||||
triggerClassName="hidden"
|
||||
defaultOpen={!profile.id_token}
|
||||
/>
|
||||
<ChangePasswordDialog
|
||||
{/* <ChangePasswordDialog
|
||||
triggerClassName="hidden"
|
||||
defaultOpen={!profile.has_password}
|
||||
/>
|
||||
/> */}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -78,8 +78,8 @@ export function BasicForm(props: {
|
||||
}) {
|
||||
const schema = z.object({
|
||||
username: z.string(),
|
||||
email: z.string(),
|
||||
contact_qq: z.string(),
|
||||
email: z.string().email('请输入正确的邮箱格式').or(z.literal('')),
|
||||
contact_qq: z.string().regex(/^\d*$/, 'QQ号只能包含数字'),
|
||||
contact_wechat: z.string(),
|
||||
})
|
||||
type Schema = z.infer<typeof schema>
|
||||
|
||||
@@ -3,16 +3,17 @@ import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card'
|
||||
import {CheckCircle} from 'lucide-react'
|
||||
import Image from 'next/image'
|
||||
import banner from '@/app/admin/identify/_assets/banner.webp'
|
||||
import RechargeModal from '@/components/composites/recharge'
|
||||
import {RealnameAuthDialog} from '@/components/composites/dialogs/realname-auth-dialog'
|
||||
import {ChangePasswordDialog} from '@/components/composites/dialogs/change-password-dialog'
|
||||
import {getProfile} from '@/actions/auth'
|
||||
import {Aftersale, BasicForm} from './clients'
|
||||
import {Button} from '@/components/ui/button'
|
||||
import Link from 'next/link'
|
||||
|
||||
export type ProfilePageProps = {}
|
||||
|
||||
export default async function ProfilePage(props: ProfilePageProps) {
|
||||
const profile = await getProfile()
|
||||
|
||||
if (!profile.success) {
|
||||
return (
|
||||
<Page>
|
||||
@@ -22,6 +23,7 @@ export default async function ProfilePage(props: ProfilePageProps) {
|
||||
}
|
||||
|
||||
const user = profile.data
|
||||
|
||||
return (
|
||||
<Page className="lg:flex-row lg:items-stretch md:flex-col max-sm:flex-col">
|
||||
<div className="flex-3/4 flex flex-col gap-4">
|
||||
@@ -34,26 +36,13 @@ export default async function ProfilePage(props: ProfilePageProps) {
|
||||
|
||||
{/* 块信息 */}
|
||||
<div className="flex gap-4 max-md:flex-col max-sm:flex-col">
|
||||
|
||||
{/* <Card className="flex-1 ">
|
||||
<CardHeader>
|
||||
<CardTitle className="font-normal">账户余额(元)</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-auto flex justify-between items-center px-8">
|
||||
<p className="text-xl">{user.balance}</p>
|
||||
<RechargeModal classNames={{
|
||||
trigger: `h-10 px-6`,
|
||||
}}/>
|
||||
</CardContent>
|
||||
</Card> */}
|
||||
|
||||
<Card className="flex-1 ">
|
||||
<CardHeader>
|
||||
<CardTitle className="font-normal">修改密码</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-auto flex justify-between items-center px-8">
|
||||
<p>{user.phone}</p>
|
||||
<ChangePasswordDialog triggerClassName="w-24 h-9"/>
|
||||
<ChangePasswordDialog triggerClassName="w-24 h-9" phone={user?.phone}/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -66,10 +55,13 @@ export default async function ProfilePage(props: ProfilePageProps) {
|
||||
? (
|
||||
<>
|
||||
<p className="text-sm">为了保障您的账户安全和正常使用服务,请您尽快完成实名认证</p>
|
||||
<RealnameAuthDialog
|
||||
defaultOpen={!user.id_token}
|
||||
{/* <RealnameAuthDialog
|
||||
// defaultOpen={!user.id_token}
|
||||
triggerClassName="w-24"
|
||||
/>
|
||||
/> */}
|
||||
<Link href="/admin/identify">
|
||||
<Button>立即认证</Button>
|
||||
</Link>
|
||||
</>
|
||||
)
|
||||
: (
|
||||
|
||||
@@ -219,6 +219,17 @@ export default function ResourceList({resourceType}: ResourceListProps) {
|
||||
header: '开通时间',
|
||||
cell: ({row}) => formatDateTime(row.original.created_at),
|
||||
},
|
||||
{
|
||||
header: '状态',
|
||||
cell: ({row}) => {
|
||||
const isActive = row.original.active
|
||||
return (
|
||||
<span className={isActive ? 'text-green-500' : 'text-red-500'}>
|
||||
{isActive ? '启用' : '禁用'}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
// 短效资源增加到期时间列
|
||||
|
||||
Reference in New Issue
Block a user