add tarrifs

This commit is contained in:
smanylov
2026-02-03 17:31:27 +07:00
parent 1e199de7b2
commit e0606c5486
10 changed files with 161 additions and 156 deletions
+1
View File
@@ -1,4 +1,5 @@
'use server' 'use server'
import { getSessionData, deleteSession } from '@/app/actions/session'; import { getSessionData, deleteSession } from '@/app/actions/session';
import { testUserData, API_BASE_URL } from '@/app/actions/definitions'; import { testUserData, API_BASE_URL } from '@/app/actions/definitions';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
-16
View File
@@ -170,8 +170,6 @@ export async function registration(
const validatedFields = SignupFormSchema.safeParse(dataToValidate); const validatedFields = SignupFormSchema.safeParse(dataToValidate);
console.log(`account-type ${accountType}`)
if (!validatedFields.success) { if (!validatedFields.success) {
const errors: Record<string, string> = {} const errors: Record<string, string> = {}
JSON.parse(validatedFields.error.message).forEach((obj: { JSON.parse(validatedFields.error.message).forEach((obj: {
@@ -199,19 +197,6 @@ export async function registration(
}; };
} }
/*
return {
previousState: {
fullName,
email,
password,
confirm_password,
phone,
agree,
...(accountType === 'b2b' && { companyName })
}
}; */
try { try {
const { fullName, email, password, phone } = validatedFields.data; const { fullName, email, password, phone } = validatedFields.data;
const response = await fetch(`${API_BASE_URL}/api/v1/data`, { const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
@@ -243,7 +228,6 @@ export async function registration(
if (parsed.message_desc === 'Operation successful') { if (parsed.message_desc === 'Operation successful') {
await createSession(parsed.message_body.token, email as string); await createSession(parsed.message_body.token, email as string);
console.log('succes registration');
} else { } else {
throw parsed; throw parsed;
} }
+18 -9
View File
@@ -35,6 +35,9 @@ interface ValidationConfig {
const defaultConfig = validationConfig as ValidationConfig; const defaultConfig = validationConfig as ValidationConfig;
let cachedSignupSchema: any = null;
let schemaPromise: Promise<any> | null = null;
async function loadConfigFromBackend(accountType?: 'b2b' | 'b2c'): Promise<any> { async function loadConfigFromBackend(accountType?: 'b2b' | 'b2c'): Promise<any> {
try { try {
const response = await fetch('/api/validation-config'); const response = await fetch('/api/validation-config');
@@ -48,16 +51,22 @@ async function loadConfigFromBackend(accountType?: 'b2b' | 'b2c'): Promise<any>
} }
export async function getSignupFormSchema(schemaType?: 'b2b' | 'b2c' | 'resetPassword') { export async function getSignupFormSchema(schemaType?: 'b2b' | 'b2c' | 'resetPassword') {
let schemaPromise: Promise<any> | null = null; if (cachedSignupSchema) {
return cachedSignupSchema;
}
schemaPromise = (async () => { if (!schemaPromise) {
const config = await loadConfigFromBackend(); schemaPromise = (async () => {
if (schemaType === 'resetPassword') { const config = await loadConfigFromBackend();
return createValidationSchemaForPssword(config); if (schemaType === 'resetPassword') {
} else { cachedSignupSchema = createValidationSchemaForPssword(config);
return createValidationSchema(config, schemaType); } else {
} cachedSignupSchema = createValidationSchema(config, schemaType);
})(); }
return cachedSignupSchema;
})();
}
return await schemaPromise; return await schemaPromise;
} }
+2 -2
View File
@@ -38,8 +38,8 @@ export function createValidationSchema(config: ValidationConfig, accountType?: '
.trim(); .trim();
} }
// companyName // fullName
if (config.companyName && accountType === 'b2b') { if (config.companyName && accountType !== 'b2c') {
schema.companyName = z schema.companyName = z
.string() .string()
.trim() .trim()
+1
View File
@@ -214,6 +214,7 @@
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer; cursor: pointer;
overflow: visible; overflow: visible;
max-width: 350px;
.plan-header { .plan-header {
text-align: center; text-align: center;
+1 -1
View File
@@ -84,7 +84,7 @@ export default function RegisterForm() {
if (fieldName === 'phone') { if (fieldName === 'phone') {
return schema.safeParse(value.replace(/[-\(\)\s]/g, '')); return schema.safeParse(value.replace(/[-\(\)\s]/g, ''));
} }
if (fieldName === 'companyName') { if (fieldName === 'companyName' && accountType === 'b2c') {
return { success: true, data: value }; return { success: true, data: value };
} }
return schema.safeParse(value); return schema.safeParse(value);
+2 -1
View File
@@ -22,7 +22,8 @@ export default function UserMenuButton() {
queryFn: () => { queryFn: () => {
return getUserData(); return getUserData();
} }
}); })
useClickOutside( useClickOutside(
menuRef, menuRef,
+57 -111
View File
@@ -1,8 +1,10 @@
<<<<<<< HEAD
'use client' 'use client'
import { useEffect } from 'react'; import { useEffect } from 'react';
import { getUserData } from '@/app/actions/action'; import { getUserData } from '@/app/actions/action';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { convertBytes } from '@/app/lib/convertBytes';
export default function PaymentTabTariffs() { export default function PaymentTabTariffs() {
const { const {
@@ -17,14 +19,12 @@ export default function PaymentTabTariffs() {
} }
}); });
useEffect(() => { =======
console.log('show => userData'); export default function PaymentTabTariffs() {
console.log(userData); >>>>>>> parent of 1e199de (edit registration)
}, [])
return ( return (
<div className="tab-content"> <div className="tab-content">
<div className="billing-toggle"> <div className="billing-toggle" style={{ display: 'none' }}>
<div> <div>
<button className="billing-btn active">💳 Помесячно</button> <button className="billing-btn active">💳 Помесячно</button>
<button className="billing-btn"> <button className="billing-btn">
@@ -34,111 +34,57 @@ export default function PaymentTabTariffs() {
</div> </div>
</div> </div>
<div className="subscription-grid"> <div className="subscription-grid">
<div className="plan-card animate-card">
<div className="plan-header"> {userData.tariffs?.map(((item: {
<div className="plan-icon">🚀</div> id: number,
<h3 className="plan-name">СТАРТ</h3> name: string,
<div className="plan-price">900</div> price: number,
<div className="plan-period">в месяц</div> tokens: number,
<div className="plan-tokens">100 токенов включено</div> maxFilesCount: number,
</div> diskSize: number
<ul className="plan-features"> }) => {
<li className="plan-feature"> return (
<span className="feature-icon"></span> <div
100 файлов в месяц className="plan-card animate-card"
</li> key={item.id}
<li className="plan-feature"> >
<span className="feature-icon"></span> <div className="plan-header">
1 ГБ хранилища <div className="plan-icon">🚀</div>
</li> <h3 className="plan-name">
<li className="plan-feature"> {item.name}
<span className="feature-icon"></span> </h3>
До 10 МБ на файл <div className="plan-price">
</li> {item.price}
<li className="plan-feature"> </div>
<span className="feature-icon"></span> <div className="plan-period">в месяц</div>
Базовые модули защиты <div className="plan-tokens">
</li> {item.tokens} токенов включено</div>
<li className="plan-feature"> </div>
<span className="feature-icon"></span> <ul className="plan-features">
Поддержка 24/7 <li className="plan-feature">
</li> <span className="feature-icon"></span>
<li className="plan-feature"> {item.maxFilesCount} файлов в месяц
<span className="feature-icon not"></span> </li>
API доступ <li className="plan-feature">
</li> <span className="feature-icon"></span>
</ul> {convertBytes(item.diskSize)} хранилища
</div> </li>
<div className="plan-card animate-card"> <li className="plan-feature">
<div className="plan-header"> <span className="feature-icon"></span>
<div className="plan-icon">🚀</div> Базовые модули защиты
<h3 className="plan-name">ПРЕМИУМ</h3> </li>
<div className="plan-price">3500</div> <li className="plan-feature">
<div className="plan-period">в месяц</div> <span className="feature-icon"></span>
<div className="plan-tokens">100 токенов включено</div> Поддержка 24/7
</div> </li>
<ul className="plan-features"> <li className="plan-feature">
<li className="plan-feature"> <span className="feature-icon not"></span>
<span className="feature-icon"></span> API доступ
100 файлов в месяц </li>
</li> </ul>
<li className="plan-feature"> </div>
<span className="feature-icon"></span> )
1 ГБ хранилища }))}
</li>
<li className="plan-feature">
<span className="feature-icon"></span>
До 10 МБ на файл
</li>
<li className="plan-feature">
<span className="feature-icon"></span>
Базовые модули защиты
</li>
<li className="plan-feature">
<span className="feature-icon"></span>
Поддержка 24/7
</li>
<li className="plan-feature">
<span className="feature-icon not"></span>
API доступ
</li>
</ul>
</div>
<div className="plan-card animate-card">
<div className="plan-header">
<div className="plan-icon">🚀</div>
<h3 className="plan-name">ПРОФИ</h3>
<div className="plan-price">9500</div>
<div className="plan-period">в месяц</div>
<div className="plan-tokens">100 токенов включено</div>
</div>
<ul className="plan-features">
<li className="plan-feature">
<span className="feature-icon"></span>
100 файлов в месяц
</li>
<li className="plan-feature">
<span className="feature-icon"></span>
1 ГБ хранилища
</li>
<li className="plan-feature">
<span className="feature-icon"></span>
До 10 МБ на файл
</li>
<li className="plan-feature">
<span className="feature-icon"></span>
Базовые модули защиты
</li>
<li className="plan-feature">
<span className="feature-icon"></span>
Поддержка 24/7
</li>
<li className="plan-feature">
<span className="feature-icon not"></span>
API доступ
</li>
</ul>
</div>
</div> </div>
</div> </div>
) )
+27 -6
View File
@@ -1,28 +1,47 @@
'use client'
import { getUserData } from '@/app/actions/action';
import { useQuery } from '@tanstack/react-query';
import { useEffect } from 'react';
import {convertBytes} from '@/app/lib/convertBytes';
export default function UserUsage() { export default function UserUsage() {
const {
data: userData,
isLoading,
isError,
error
} = useQuery({
queryKey: ['userData'],
queryFn: () => {
return getUserData();
}
});
return ( return (
<div className="usage-stats"> <div className="usage-stats">
<h3> <h3>
📊 Ваше использование Ваше использование
</h3> </h3>
<div className="stats-grid"> <div className="stats-grid">
<div className="stat-card"> <div className="stat-card">
<div className="stat-value">0 / 2,000</div> <div className="stat-value">0 / {userData?.tariffInfo?.tariff?.maxFilesCount}</div>
<div className="stat-label">Файлов в месяц</div> <div className="stat-label">Файлов в месяц</div>
<div className="progress-bar"> <div className="progress-bar">
<div <div
className="progress-fill" className="progress-fill"
style={{ width: '50%' }} style={{ width: '0%' }}
> >
</div> </div>
</div> </div>
</div> </div>
<div className="stat-card"> <div className="stat-card">
<div className="stat-value">0 / 20 ГБ</div> <div className="stat-value">0 / {convertBytes(userData?.tariffInfo?.tariff?.diskSize)}</div>
<div className="stat-label">Объём хранилища</div> <div className="stat-label">Объём хранилища</div>
<div className="progress-bar"> <div className="progress-bar">
<div <div
className="progress-fill" className="progress-fill"
style={{ width: '50%' }} style={{ width: '0%' }}
> >
</div> </div>
</div> </div>
@@ -32,7 +51,9 @@ export default function UserUsage() {
<div className="stat-label">Токенов на балансе</div> <div className="stat-label">Токенов на балансе</div>
</div> </div>
<div className="stat-card"> <div className="stat-card">
<div className="stat-value">ПРОФИ</div> <div className="stat-value">
{userData?.tariffInfo?.tariff?.name}
</div>
<div className="stat-label">Текущий план</div> <div className="stat-label">Текущий план</div>
</div> </div>
</div> </div>
+52 -10
View File
@@ -1,7 +1,9 @@
'use client' 'use client'
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { useActionState, useState } from 'react' import { useActionState, useState, useEffect } from 'react';
import { getUserData } from '@/app/actions/action';
import { useQuery } from '@tanstack/react-query';
export default function PersonalDataSettings() { export default function PersonalDataSettings() {
const [errorMessage, formAction, isPending] = useActionState( const [errorMessage, formAction, isPending] = useActionState(
@@ -11,6 +13,23 @@ export default function PersonalDataSettings() {
undefined undefined
); );
const {
data: userData,
isLoading,
isError,
error
} = useQuery({
queryKey: ['userData'],
queryFn: () => {
return getUserData();
}
});
useEffect(() => {
console.log('show => userData');
console.log(userData);
}, [])
const [selectedValue, setSelectedValue] = useState('male'); const [selectedValue, setSelectedValue] = useState('male');
const t = useTranslations('Global'); const t = useTranslations('Global');
@@ -26,19 +45,37 @@ export default function PersonalDataSettings() {
<label className="form-label"> <label className="form-label">
{t('full-name')}: {t('full-name')}:
</label> </label>
<input type="text" name="full_name" className="form-input" /> <input
</div> type="text"
<div className="form-group"> name="full_name"
<label className="form-label"> className="form-input"
{t('company')}: defaultValue={userData.fullName}
</label> />
<input type="text" name="company" className="form-input" />
</div> </div>
{userData.company && (
<div className="form-group">
<label className="form-label">
{t('company')}:
</label>
<input
type="text"
name="company"
className="form-input"
defaultValue={userData.company}
/>
</div>
)}
<div className="form-group"> <div className="form-group">
<label className="form-label"> <label className="form-label">
{t('email')}: {t('email')}:
</label> </label>
<input type="text" name="email" className="form-input" disabled={true}/> <input
type="text"
name="email"
className="form-input"
defaultValue={userData.email}
disabled={true}
/>
<small> <small>
{t('email-cant-change')} {t('email-cant-change')}
</small> </small>
@@ -47,7 +84,12 @@ export default function PersonalDataSettings() {
<label className="form-label"> <label className="form-label">
{t('phone')}: {t('phone')}:
</label> </label>
<input type="phone" name="phone" className="form-input" /> <input
type="phone"
name="phone"
className="form-input"
defaultValue={userData.phone}
/>
</div> </div>
<button type="submit" className="btn btn-primary"> <button type="submit" className="btn btn-primary">
{t('save-changes')} {t('save-changes')}