This commit is contained in:
azizziy
2025-05-20 17:02:10 +05:00
commit c01e852a59
257 changed files with 27766 additions and 0 deletions

7
.dockerignore Normal file
View File

@@ -0,0 +1,7 @@
Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.next
.git

3
.eslintrc.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

1
.example.env Normal file
View File

@@ -0,0 +1 @@
NEXT_PUBLIC_BACKEND_BASE_URL=string

38
.gitignore vendored Normal file
View File

@@ -0,0 +1,38 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
/.idea
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
.env

20
.prettierrc Normal file
View File

@@ -0,0 +1,20 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"singleQuote": true,
"jsxSingleQuote": true,
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "avoid",
"printWidth": 140,
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxBracketSameLine": false,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"useTabs": false,
"vueIndentScriptAndStyle": false,
"rangeStart": 0
}

22
Dockerfile Normal file
View File

@@ -0,0 +1,22 @@
# Use an official Node.js runtime as the base image
FROM node:18-alpine AS base
# Set the working directory in the container
WORKDIR /app
# Copy the package.json and package-lock.json to the working directory
COPY package*.json ./
# Install npm dependencies
RUN npm install --force
# Copy the rest of your application code to the working directory
COPY . .
# Expose the port your application is listening on (if any)
EXPOSE 3000
RUN npm run build
# Start the application with "npm run dev"
CMD ["npm", "run", "start"]

22
Dockerfile-dev Normal file
View File

@@ -0,0 +1,22 @@
# Use an official Node.js runtime as the base image
FROM node:18-alpine AS base
# Set the working directory in the container
WORKDIR /app
# Copy the package.json and package-lock.json to the working directory
COPY package*.json ./
# Install npm dependencies
RUN npm install --force
# Copy the rest of your application code to the working directory
COPY . .
# Expose the port your application is listening on (if any)
EXPOSE 3000
RUN npm run build:dev
# Start the application with "npm run dev"
CMD ["npm", "run", "start"]

10
README.md Normal file
View File

@@ -0,0 +1,10 @@
# C-POST
## Example app router with react-query
https://github.com/wpcodevo/nextjs13-react-query/blob/main/src/app/hydration/page.tsx
## DOCKER BUILD IMAGE
### docker build -t cpost-front .
## DOCKER RUN IMAGE
### docker run -p 3000:3000 cpost-front

17
dc-front-dev.yml Normal file
View File

@@ -0,0 +1,17 @@
services:
frontend:
build:
context: .
dockerfile: Dockerfile-dev
image: cpost_frontend_dev
container_name: cpost_frontend_dev
expose:
- "3000"
networks:
cpost-network:
ipv4_address: 10.10.0.16
restart: "always"
networks:
cpost-network:
name: cpostnetwork
external: true

16
dc-front.yml Normal file
View File

@@ -0,0 +1,16 @@
services:
frontend:
build:
context: .
dockerfile: Dockerfile
container_name: cpost_frontend
expose:
- "3000"
networks:
cpost-network:
ipv4_address: 10.10.0.4
restart: "always"
networks:
cpost-network:
name: cpostnetwork
external: true

8
environment.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'production' | 'development' | 'test';
NEXT_PUBLIC_BASE_URL: string;
NEXT_PUBLIC_BACKEND_BASE_URL: string;
}
}

11
i18n.ts Normal file
View File

@@ -0,0 +1,11 @@
import { notFound } from 'next/navigation';
import { getRequestConfig } from 'next-intl/server';
import { locales } from '@/helpers/constants';
export default getRequestConfig(async ({ locale }) => {
if (!locales.includes(locale as any)) notFound();
return {
messages: (await import(`./messages/${locale}.json`)).default,
};
});

225
messages/cn.json Normal file
View File

@@ -0,0 +1,225 @@
{
"main_page": "主页",
"home": "首页",
"services": "服务",
"news": "新闻",
"about_us": "关于我们",
"tariffs": "收费标准",
"contacts": "联系我们",
"check_order": "查看订单",
"hero_title": "从中国运送货物到乌兹别克斯坦",
"hero_subtitle": "您想知道如何将您的购物从中国运送到乌兹别克斯坦吗?",
"good_quality": "高质量",
"fast": "快速",
"online_watching": "在线跟踪",
"safe_warehouse": "安全仓库",
"we_proud_to_serve_global": "我们以为全球提供最佳的货运和运输解决方案感到自豪。",
"our_services": "我们的服务",
"air_transport": "空运",
"air_transport__subtitle": "CPost提供各类大型和重型货物的空运服务。",
"car_transport": "汽车运输",
"car_transport__subtitle": "传统汽车运输,价格便宜,灵活性强。",
"water_transport": "水运",
"water_transport__subtitle": "CPost专注于海运货物。",
"railway_transport": "铁路运输",
"railway_transport__subtitle": "铁路运输是CPost主要业务方向之一。公司提供所需吨位的集装箱和各类货车运输。",
"warehouse_yivu_and_guanchjou": "义乌和广州仓储服务",
"warehouse_yivu_and_guanchjou__subtitle": "CPost在中国提供仓储物流服务与从中国运输的车辆同时进行。",
"customs_office_service": "海关清关服务",
"customs_office_service__subtitle": "CPost重视客户的时间并提供货物的海关申报服务。按照HS和TN编码对货物进行分类。",
"pricing": {
"title": "价格",
"limit": "交货时间:",
"min_weight": "最小重量",
"more": "更多详情",
"pricing1": {
"title": "空运",
"price": "每千克从$10起",
"limit": "7-14天",
"min_weight": "1千克"
},
"pricing2": {
"title": "快递汽车运输",
"price": "每千克从$8起",
"limit": "30天",
"min_weight": "1千克"
},
"pricing3": {
"title": "汽车运输",
"price": "按协议定价",
"limit": "20-25天",
"min_weight": "1立方米"
},
"pricing4": {
"title": "铁路运输",
"price": "按协议定价",
"limit": "35-40天",
"min_weight": "1立方米"
}
},
"our_partners": "我们的合作伙伴",
"news_subtitle": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"contacts_subtitle": "Creative mouse is here for you to make sure you can build a more resilient portfolio. We curate bespoke offerings across alternative.",
"address": "地址",
"email": "电子邮件",
"phone": "电话",
"telegram": "Telegram",
"instagram": "Instagram",
"real_address": "塔什干市奥尔马佐尔区_______街",
"faq": {
"title": "常见问题",
"subtitle": "Creative mouse is here for you to make sure you can build a more resilient portfolio. We curate bespoke offerings across alternative.",
"faq1": {
"question": "我如何实时追踪我的货物?",
"answer": "大多数物流公司通过其网站或移动应用提供在线追踪服务。您可以输入跟踪号以获取货物的实时位置和状态更新。"
}
},
"footer_site_info_text": "可靠且快速的物流服务提供商",
"privacy_policy": "隐私政策",
"terms_of_use": "使用条款",
"sign_up_to_tg": "注册以获取最新消息、公司见解和CPost更新。",
"your_email": "您的电子邮件",
"pages": "页面",
"info": "信息",
"join_tg": "加入我们的Telegram",
"all_rights_reserved": "版权所有",
"calc_obj": {
"title": "计算器",
"subtitle": "选择您的订单的运输类型和重量,以了解运费。",
"colvo_sht": "数量(件)",
"volume": "体积(立方米)",
"weight_of_product": "货物重量...",
"calc_value": "计算"
},
"check_order_obj": {
"title": "包裹追踪",
"subtitle": "输入从在线商店收到的包裹条形码。",
"enter_check_code": "输入条形码...",
"select_delivery_type": "选择运输类型",
"delivery_type_avia": "航空",
"delivery_type_car": "汽车"
},
"price": "价格",
"__dash": "------------------------------------DASHBOARD------------------------------------------------------",
"dashboard": "仪表板",
"parties": "派对",
"create_party": "创建派对",
"update_party": "更新派对",
"create_box": "创建箱子",
"create_packet": "創建包",
"update_box": "更新箱子",
"update_package": "程式包更新",
"update_item": "更新物品",
"no": "编号",
"id": "ID",
"name": "名称",
"party_status": "派对状态",
"box_status": "箱子状态",
"actions": "操作",
"reset_filter": "重置过滤器",
"search": "搜索",
"boxes": "套餐",
"packet": "套餐",
"filter": "过滤器",
"party_name": "派对名称",
"box_type": "箱子类型",
"box_number": "箱子编号",
"net_weight": "净重",
"gross_weight": "毛重",
"box_size": "箱子大小",
"box_weight": "箱子重量",
"volume": "体积",
"netto": "净重",
"brutto": "毛重",
"description": "描述",
"add_product": "添加产品",
"add_box": "添加箱子",
"edit": "编辑",
"delete": "删除",
"cargo_id": "貨物編號",
"please_enter_use_id": "请输入使用ID",
"quantity": "数量",
"weight": "重量",
"add_more": "添加更多",
"create": "创建",
"cancel": "取消",
"update": "更新",
"clients": "客户",
"client": "客戶",
"create_client": "创建客户",
"client_name": "客户名称",
"fullName": "全名",
"products": "产品",
"product_name": "产品名称",
"track_id": "跟踪ID",
"party": "派对",
"staffs": "员工",
"create_staff": "创建员工",
"username": "用户名",
"role": "角色",
"status": "状态",
"active": "活跃",
"not_active": "非活跃",
"log_out": "登出",
"password": "密码",
"create_invoice": "创建发票",
"hasInvoice": "有发票",
"required": "必需",
"total_price": "总价",
"not_found": "未找到",
"loading": "加载",
"count_of_boxes": "箱子数量",
"count_of_items": "物品数量",
"download_excel": "下载Excel",
"enter_party_name_to_find": "输入要查找的派对名称...",
"enter_box_name_to_find": "输入要查找的箱子名称...",
"enter_client_name_to_find": "输入要查找的客户名称...",
"filter_packet_name": "封包名稱過濾器",
"filter_party_name": "派对名称过滤器",
"filter_box_name": "箱子名称过滤器",
"filter_track_id": "跟踪ID过滤器",
"filter_client_name": "客户名称过滤器",
"filter_item_name": "产品名称过滤器",
"filter_by_box_status": "箱子状态过滤器",
"filter_by_party_status": "派对状态过滤器",
"COLLECTING": "收集中",
"ON_THE_WAY": "在路上",
"ARRIVED": "已到达",
"READY_TO_INVOICE": "准备开发票",
"READY": "准备",
"IN_CUSTOMS": "在海關",
"IN_WAREHOUSE": "接觸客戶",
"DELIVERED": "發表",
"add_photo_to_item": "將照片加入項目",
"photo": "照片",
"summa": "總結",
"avia_cargo_id": "航空貨物編號",
"auto_cargo_id": "自動貨物編號",
"update_client": "更新客戶端",
"pinfl": "平弗",
"passportSeries": "護照系列",
"search_available_in_trekid": "此處您只能透過 TrekID 搜尋產品",
"party_total_brutto": "黨的整體粗糙度",
"date_of_birth": "出生日期",
"are_you_sure_delete_client_id": "您確定要刪除客戶端 {id} 嗎?",
"download_all_items": "下載所有項目",
"download_all_clients": "下載所有客戶端",
"are_you_sure_delete_party": "您确定要删除货物批次{name}吗",
"passport": "護照",
"weight_of_items": "產品重量",
"update_packet": "編輯套件",
"party_weight": "大量重量"
}

224
messages/en.json Normal file
View File

@@ -0,0 +1,224 @@
{
"main_page": "Main Page",
"home": "Home",
"services": "Services",
"news": "News",
"about_us": "About Us",
"tariffs": "Tariffs",
"contacts": "Contacts",
"check_order": "Check Order",
"hero_title": "Delivery of Goods from China to Uzbekistan",
"hero_subtitle": "Do you wonder how to deliver your purchases from China to Uzbekistan?",
"good_quality": "High Quality",
"fast": "Fast",
"online_watching": "Online Tracking",
"safe_warehouse": "Secure Warehouse",
"we_proud_to_serve_global": "We take pride in providing the best freight and transportation solutions worldwide.",
"our_services": "Our Services",
"air_transport": "Air Transport",
"air_transport__subtitle": "CPost offers air transportation for a wide range of large-sized and heavy goods.",
"car_transport": "Car Transport",
"car_transport__subtitle": "Traditional automobile transport, convenient prices, and maneuverability.",
"water_transport": "Water Transport",
"water_transport__subtitle": "CPost specializes in transporting goods by sea.",
"railway_transport": "Railway Transport",
"railway_transport__subtitle": "Railway transport is one of the main directions of CPost activity. The company transports with necessary tonnage containers and all types of wagons.",
"warehouse_yivu_and_guanchjou": "Warehouse Services in Yiwu and Guangzhou",
"warehouse_yivu_and_guanchjou__subtitle": "CPost offers the use of warehouse logistics services in China simultaneously with transporting vehicles from China.",
"customs_office_service": "Customs Clearance Services",
"customs_office_service__subtitle": "CPost values its customers' time and offers customs declaration services for goods. Classification of goods by HS and TN codes.",
"pricing": {
"title": "Pricing",
"limit": "Delivery time:",
"min_weight": "Minimum Weight",
"more": "More Details",
"pricing1": {
"title": "Air Transport",
"price": "From $10/KG",
"limit": "7-14 days",
"min_weight": "1kg"
},
"pricing2": {
"title": "Express Car Transport",
"price": "From $8/KG",
"limit": "30 days",
"min_weight": "1kg"
},
"pricing3": {
"title": "Car Shipping",
"price": "Based on agreement",
"limit": "20-25 days",
"min_weight": "1m3"
},
"pricing4": {
"title": "Railway Transport",
"price": "Based on agreement",
"limit": "35-40 days",
"min_weight": "1m3"
}
},
"our_partners": "Our Partners",
"news_subtitle": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"contacts_subtitle": "Creative mouse is here for you to make sure you can build a more resilient portfolio. We curate bespoke offerings across alternative.",
"address": "Address",
"email": "Email",
"phone": "Phone",
"telegram": "Telegram",
"instagram": "Instagram",
"real_address": "Tashkent city, Olmazor district, _______ street",
"faq": {
"title": "Frequently Asked Questions",
"subtitle": "Creative mouse is here for you to make sure you can build a more resilient portfolio. We curate bespoke offerings across alternative.",
"faq1": {
"question": "How can I track my shipment in real-time?",
"answer": "Most logistics companies provide online tracking services through their websites or mobile apps. You can enter your tracking number to get real-time updates on the delivery location and status."
}
},
"footer_site_info_text": "Reliable and Fast Logistics Service Provider",
"privacy_policy": "Privacy Policy",
"terms_of_use": "Terms of Use",
"sign_up_to_tg": "Sign upfor the latest news, company insights, and CPost updates.",
"your_email": "Your email",
"pages": "Pages",
"info": "Information",
"join_tg": "Join our telegram",
"all_rights_reserved": "All Rights Reserved",
"calc_obj": {
"title": "Calculator",
"subtitle": "Select the delivery type and weight of your order to find out the delivery cost",
"colvo_sht": "Quantity (pcs)",
"volume": "Volume (m3)",
"weight_of_product": "Weight of the cargo...",
"calc_value": "Calculate"
},
"check_order_obj": {
"title": "Parcel Tracking",
"subtitle": "Enter the barcode of the parcel received from the online store.",
"enter_check_code": "Enter the barcode...",
"select_delivery_type": "Select the delivery type",
"delivery_type_avia": "Air",
"delivery_type_car": "Auto"
},
"price": "Price",
"__dash": "------------------------------------DASHBOARD------------------------------------------------------",
"dashboard": "Dashboard",
"parties": "Parties",
"create_party": "Create party",
"update_party": "Update party",
"create_box": "Create box",
"create_packet": "Create packet",
"update_box": "Update box",
"update_package": "Update packet",
"update_item": "Update item",
"no": "No",
"id": "ID",
"name": "Name",
"party_status": "Party status",
"box_status": "Box status",
"actions": "Actions",
"reset_filter": "Reset filter",
"search": "Search",
"boxes": "Boxes",
"packet": "Packets",
"filter": "Filter",
"party_name": "Party name",
"box_type": "Box type",
"box_number": "Box number",
"net_weight": "Net weight",
"gross_weight": "Brutto",
"box_size": "Box size",
"box_weight": "Box weight",
"volume": "Volume",
"netto": "Netto",
"brutto": "Brutto",
"description": "Description",
"add_product": "Add product",
"add_box": "Add box",
"edit": "Edit",
"delete": "Delete",
"cargo_id": "Cargo id",
"please_enter_use_id": "Please enter use id",
"quantity": "Quantity",
"weight": "Weight",
"add_more": "Add more",
"create": "Create",
"cancel": "Cancel",
"update": "Update",
"clients": "Clients",
"client": "Client",
"create_client": "Create client",
"client_name": "Client name",
"fullName": "Fullname",
"products": "Products",
"product_name": "Product name",
"track_id": "Track id",
"party": "Party",
"staffs": "Staffs",
"create_staff": "Create staff",
"username": "User name",
"role": "Role",
"status": "Status",
"active": "Active",
"not_active": "Not active",
"log_out": "Log out",
"password": "Password",
"create_invoice": "Create invoice",
"hasInvoice": "Has invoice",
"required": "Required",
"total_price": "Total price",
"not_found": "Not found",
"loading": "Loading",
"count_of_boxes": "Count of boxes",
"count_of_items": "Count of items",
"download_excel": "Download excel",
"enter_party_name_to_find": "Enter party name to find...",
"enter_box_name_to_find": "Enter box name to find...",
"enter_client_name_to_find": "Enter client name to find...",
"filter_packet_name": "Packet name filter",
"filter_party_name": "Party name filter",
"filter_box_name": "Box name filter",
"filter_track_id": "Track id filter",
"filter_client_name": "Client name filter",
"filter_item_name": "Product name filter",
"filter_by_box_status": "Box Status filter",
"filter_by_party_status": "Party Status filter",
"COLLECTING": "Collecting",
"ON_THE_WAY": "On the way",
"ARRIVED": "Arrived",
"READY_TO_INVOICE": "Ready to invoice",
"READY": "Ready",
"IN_CUSTOMS": "At customs",
"IN_WAREHOUSE": "Reaching out to customers",
"DELIVERED": "Delivered",
"add_photo_to_item": "Add photo to item",
"photo": "photo",
"summa": "summa",
"avia_cargo_id": "Avia cargo id",
"auto_cargo_id": "Auto cargo id",
"update_client": "Update client",
"pinfl": "Pinfl",
"passportSeries": "Passport series",
"search_available_in_trekid": "Here you can only search for a product by TrekID",
"party_total_brutto": "Party's total brutto",
"date_of_birth": "Date of birth",
"are_you_sure_delete_client_id": "Are you sure to delete client {id}?",
"download_all_items": "Download all items",
"download_all_clients": "Download all clients",
"are_you_sure_delete_party": "Are you sure to delete party {name}?",
"passport": "Passport",
"weight_of_items": "Products weight",
"update_packet": "Edit Paket",
"party_weight": "Batch weight"
}

225
messages/ru.json Normal file
View File

@@ -0,0 +1,225 @@
{
"main_page": "Главная страница",
"home": "Главная",
"services": "Услуги",
"news": "Новости",
"about_us": "О нас",
"tariffs": "Тарифы",
"contacts": "Контакты",
"check_order": "Проверить заказ",
"hero_title": "Доставка товаров из Китая в Узбекистан",
"hero_subtitle": "Хотите узнать, как доставить свои покупки из Китая в Узбекистан?",
"good_quality": "Высокое качество",
"fast": "Быстро",
"online_watching": "Онлайн отслеживание",
"safe_warehouse": "Безопасный склад",
"we_proud_to_serve_global": "Мы гордимся предоставлением лучших решений в области грузоперевозок и транспортировки по всему миру.",
"our_services": "Наши услуги",
"air_transport": "Авиаперевозки",
"air_transport__subtitle": "CPost предлагает авиаперевозки для широкого спектра крупногабаритных и тяжелых товаров.",
"car_transport": "Автоперевозки",
"car_transport__subtitle": "Традиционные автоперевозки, удобные цены и маневренность.",
"water_transport": "Морские перевозки",
"water_transport__subtitle": "CPost специализируется на транспортировке товаров морским путем.",
"railway_transport": "Железнодорожные перевозки",
"railway_transport__subtitle": "Железнодорожные перевозки - одно из основных направлений деятельности CPost. Компания транспортирует контейнеры с необходимой грузоподъемностью и все виды вагонов.",
"warehouse_yivu_and_guanchjou": "Складские услуги в Йиву и Гуанчжоу",
"warehouse_yivu_and_guanchjou__subtitle": "CPost предлагает использование услуг складской логистики в Китае параллельно с транспортировкой транспортных средств из Китая.",
"customs_office_service": "Таможенные услуги",
"customs_office_service__subtitle": "CPost ценит время своих клиентов и предлагает услуги по таможенному оформлению товаров. Классификация товаров по кодам HS и TN.",
"pricing": {
"title": "Ценообразование",
"limit": "Срок доставки:",
"min_weight": "Минимальный вес",
"more": "Подробнее",
"pricing1": {
"title": "Авиаперевозки",
"price": "От $10/кг",
"limit": "7-14 дней",
"min_weight": "1кг"
},
"pricing2": {
"title": "Экспресс автоперевозки",
"price": "От $8/кг",
"limit": "30 дней",
"min_weight": "1кг"
},
"pricing3": {
"title": "Автомобильные перевозки",
"price": "По соглашению",
"limit": "20-25 дней",
"min_weight": "1м3"
},
"pricing4": {
"title": "Железнодорожные перевозки",
"price": "По соглашению",
"limit": "35-40 дней",
"min_weight": "1м3"
}
},
"our_partners": "Наши партнеры",
"news_subtitle": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"contacts_subtitle": "Creative mouse здесь для вас, чтобы убедиться, что вы можете построить более устойчивый портфель. Мы создаем индивидуальные предложения в альтернативной области.",
"address": "Адрес",
"email": "Электронная почта",
"phone": "Телефон",
"telegram": "Telegram",
"instagram": "Instagram",
"real_address": "Город Ташкент, район Ольмазор, улица _______",
"faq": {
"title": "Часто задаваемые вопросы",
"subtitle": "Creative mouse здесь для вас, чтобы убедиться, что вы можете построить более устойчивый портфель. Мы создаем индивидуальные предложения в альтернативной области.",
"faq1": {
"question": "Как я могу отслеживать мою отправку в реальном времени?",
"answer": "Большинство логистических компаний предоставляют услуги онлайн-отслеживания через свои веб-сайты или мобильные приложения. Вы можете ввести свой номер отслеживания, чтобы получить обновления о местоположении и статусе доставки в реальном времени."
}
},
"footer_site_info_text": "Надежный и быстрый поставщик логистических услуг",
"privacy_policy": "Политика конфиденциальности",
"terms_of_use": "Условия использования",
"sign_up_to_tg": "Подпишитесь на последние новости, корпоративные исследования и обновления CPost.",
"your_email": "Ваш адрес электронной почты",
"pages": "Страницы",
"info": "Информация",
"join_tg": "Присоединяйтесь к нашему телеграмм-каналу",
"all_rights_reserved": "Все права защищены",
"calc_obj": {
"title": "Калькулятор",
"subtitle": "Выберите тип доставки и вес вашего заказа, чтобы узнать стоимость доставки",
"colvo_sht": "Количество (шт)",
"volume": "Объем (м3)",
"weight_of_product": "Вес груза...",
"calc_value": "Рассчитать"
},
"check_order_obj": {
"title": "Отслеживание посылки",
"subtitle": "Введите штрих-код посылки, полученной из интернет-магазина.",
"enter_check_code": "Введите штрих-код...",
"select_delivery_type": "Выберите тип доставки",
"delivery_type_avia": "Авиа",
"delivery_type_car": "Авто"
},
"price": "Цена",
"__dash": "------------------------------------ПАНЕЛЬ УПРАВЛЕНИЯ------------------------------------------------------",
"dashboard": "Панель управления",
"parties": "Партии",
"create_party": "Создать партию",
"update_party": "Обновить партию",
"create_box": "Создать коробку",
"create_packet": "Создать пакет",
"update_box": "Обновить коробку",
"update_package": "Обновление пакета",
"update_item": "Обновить товар",
"no": "Нет",
"id": "ID",
"name": "Название",
"party_status": "Статус партии",
"box_status": "Статус ящика",
"actions": "Действия",
"reset_filter": "Сбросить фильтр",
"search": "Поиск",
"boxes": "Коробки",
"packet": "Пакеты",
"filter": "Фильтр",
"party_name": "Название партии",
"box_type": "Тип коробки",
"box_number": "Номер коробки",
"net_weight": "Вес нетто",
"gross_weight": "Вес брутто",
"box_size": "Размер коробки",
"box_weight": "Вес коробки",
"volume": "Объем",
"netto": "Нетто",
"brutto": "Брутто",
"description": "Описание",
"add_product": "Добавить товар",
"add_box": "Добавить коробку",
"edit": "Редактировать",
"delete": "Удалить",
"cargo_id": "ID груза",
"please_enter_use_id": "Введите ID пользователя",
"quantity": "Количество",
"weight": "Вес",
"add_more": "Добавить еще",
"create": "Создать",
"cancel": "Отмена",
"update": "Обновить",
"clients": "Клиенты",
"client": "Клиент",
"create_client": "Создать клиента",
"client_name": "Имя клиента",
"fullName": "Полное имя",
"products": "Товары",
"product_name": "Название товара",
"track_id": "ID трека",
"party": "Партия",
"staffs": "Сотрудники",
"create_staff": "Создать сотрудника",
"username": "Имя пользователя",
"role": "Роль",
"status": "Статус",
"active": "Активный",
"not_active": "Неактивный",
"log_out": "Выйти",
"password": "Пароль",
"create_invoice": "Создать счет",
"hasInvoice": "Есть счет",
"required": "Обязательно",
"total_price": "Общая стоимость",
"not_found": "Не найдено",
"loading": "Загрузка",
"count_of_boxes": "Количество коробок",
"count_of_items": "Количество товаров",
"download_excel": "Скачать Excel",
"enter_party_name_to_find": "Введите имя партии для поиска...",
"enter_box_name_to_find": "Введите имя коробки для поиска...",
"enter_client_name_to_find": "Введите имя клиента для поиска...",
"filter_packet_name": "Фильтр имени пакета",
"filter_party_name": "Фильтр по имени партии",
"filter_box_name": "Фильтр по имени коробки",
"filter_track_id": "Фильтр по идентификатору отслеживания",
"filter_client_name": "Фильтр по имени клиента",
"filter_item_name": "Фильтр по имени продукта",
"filter_by_box_status": "Фильтр по статусу коробки",
"filter_by_party_status": "Фильтр по статусу партии",
"COLLECTING": "Сбор",
"ON_THE_WAY": "В пути",
"ARRIVED": "Прибыл",
"READY_TO_INVOICE": "Готов к выставлению счета",
"READY": "Готов",
"IN_CUSTOMS": "На таможне",
"IN_WAREHOUSE": "Это приближается",
"DELIVERED": "Доставленный",
"add_photo_to_item": "Добавить фотографию к элементу",
"photo": "Фото",
"summa": "Сумма",
"avia_cargo_id": "Идентификатор авиагруза",
"auto_cargo_id": "Идентификатор авто груза",
"update_client": "Обновить Клиента",
"pinfl": "Пинфл",
"passportSeries": "Серия паспорта",
"search_available_in_trekid": "Здесь вы можете искать товар только по TrekID.",
"party_total_brutto": "Полное брутто партии",
"date_of_birth": "Дата рождения",
"are_you_sure_delete_client_id": "Вы уверены, что хотите удалить клиента {id}?",
"download_all_items": "Скачать все продуктов",
"download_all_clients": "Скачать всех клиентов",
"are_you_sure_delete_party": "Вы уверены, что хотите удалить партию груза {name}?",
"passport": "Паспорт",
"weight_of_items": "Вес продукта",
"update_packet": "Редактировать пакет",
"party_weight": "Вес партии"
}

224
messages/uz.json Normal file
View File

@@ -0,0 +1,224 @@
{
"main_page": "Bosh Sahifa",
"home": "Bosh sahifa",
"services": "Xizmatlar",
"news": "Yangiliklar",
"about_us": "Biz Haqimizda",
"tariffs": "Tariflar",
"contacts": "Kontaktlar",
"check_order": "Buyurtmani Tekshirish",
"hero_title": "Xitoydan O'zbekistonga Mahsulot Yetkazib Berish",
"hero_subtitle": "Xitoydan sotib olingan mahsulotlaringizni O'zbekistonga qanday yetkazib berishni bilsangizmi?",
"good_quality": "Yuqori sifat",
"fast": "Tezkor",
"online_watching": "Onlayn Kuzatish",
"safe_warehouse": "Xavfsiz Ombor",
"we_proud_to_serve_global": "Biz dunyo bo'yligicha eng yaxshi yuk va transport yechimlari bilan iftixorlanamiz.",
"our_services": "Bizning Xizmatlarimiz",
"air_transport": "Havo Yo'li",
"air_transport__subtitle": "CPost katta hajmli va og'ir mahsulotlarni yo'lga qo'yish uchun havo yo'li yetkazib beradi.",
"car_transport": "Avtotrport",
"car_transport__subtitle": "Odatiy avtotransport, qulay narxlar va manevr.",
"water_transport": "Su Yo'li",
"water_transport__subtitle": "CPost maxsusligi su yo'li bilan mahsulotlarni yo'lga qo'yadi.",
"railway_transport": "Temir Yo'li Yo'li",
"railway_transport__subtitle": "Temir yo'li transporti CPost faoliyatining asosiy yo'nalishlaridan biri. Kompaniya zarur tovar konteynerlari va barcha turlaridagi vagonlar bilan transport qiladi.",
"warehouse_yivu_and_guanchjou": "Yiwu va Guanchjoudagi Ombor Xizmatlari",
"warehouse_yivu_and_guanchjou__subtitle": "CPost Xitoydan transport vositalarini yetkazib berish bilan bir vaqtning o'zida Xitoyda ombor loyixasi xizmatlaridan foydalanishni taklif qiladi.",
"customs_office_service": "Ma'lumotlar Markazi Xizmatlari",
"customs_office_service__subtitle": "CPost mijozlari vaqtini qadrlaydi va tovarlar uchun ma'lumotnoma ma'lumotlari taqdim qiladi. Mahsulotlarni HS va TN kodalari bo'yicha tartiblash.",
"pricing": {
"title": "Narxlar",
"limit": "Yetkazib berish vaqti:",
"min_weight": "Minimal Og'irlik",
"more": "Batafsil",
"pricing1": {
"title": "Havo Yo'li Yo'li",
"price": "$10/KG dan",
"limit": "7-14 kun",
"min_weight": "1kg"
},
"pricing2": {
"title": "Express Avtotransport",
"price": "$8/KG dan",
"limit": "30 kun",
"min_weight": "1kg"
},
"pricing3": {
"title": "Avtomobil Yo'li Yo'li",
"price": "Kelishuv asosida",
"limit": "20-25 kun",
"min_weight": "1m3"
},
"pricing4": {
"title": "Temir Yo'li Yo'li",
"price": "Kelishuv asosida",
"limit": "35-40 kun",
"min_weight": "1m3"
}
},
"our_partners": "Bizning Hamkorlarimiz",
"news_subtitle": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"contacts_subtitle": "Kreativ quyuq siz uchun bu joyda, sizga zaminli portfelingizni qurishingiz uchun. Biz alternativ tomonlar bo'yicha maxsus takliflar tuzamiz.",
"address": "Manzil",
"email": "Elektron pochta",
"phone": "Telefon",
"telegram": "Telegram",
"instagram": "Instagram",
"real_address": "Toshkent shahri, Olmazor tumani, _______ ko'chasi",
"faq": {
"title": "Tez-tez So'raladigan Savollar",
"subtitle": "Kreativ quyuq siz uchun bu joyda, sizga zaminli portfelingizni qurishingiz uchun. Biz alternativ tomonlar bo'yicha maxsus takliflar tuzamiz.",
"faq1": {
"question": "Men qanday qilib yukni real vaqtda kuzatib boring?",
"answer": "Ko'plab logistika kompaniyalari o'z saytlari yoki mobil ilovalari orqali onlayn kuzatish xizmatlarini taqdim qiladilar. Siz o'zingizning kuzatish raqamingizni kiriting, yetkazib berish joyini va holatini haqiqiy vaqt ichida yangilashlarni olish uchun."
}
},
"footer_site_info_text": "Ishonchli va Tezkor Logistika Xizmati Taqdimoti",
"privacy_policy": "Maxfiylik Siyosati",
"terms_of_use": "Foydalanish shartlari",
"sign_up_to_tg": "Eng so'nggi yangiliklar, kompaniya ma'lumotlari va CPost yangilanishlari uchun ro'yxatdan o'ting.",
"your_email": "Sizning elektron pochtangiz",
"pages": "Sahifalar",
"info": "Ma'lumot",
"join_tg": "Bizning telegramm jamoamizga qo'shiling",
"all_rights_reserved": "Barcha huquqlar himoyalangan",
"calc_obj": {
"title": "Kalkulyator",
"subtitle": "Buyurtma turi va og'irlikni tanlang, yetkazib berish narxini bilib olish uchun",
"colvo_sht": "Miqdori (donalar)",
"volume": "Hajmi (m3)",
"weight_of_product": "Yuk og'irliği...",
"calc_value": "Hisoblash"
},
"check_order_obj": {
"title": "Parsel Kuzatish",
"subtitle": "Onlayn do'kondan olingan parselning shtrix-kodini kiriting.",
"enter_check_code": "Shtrix-kodni kiriting...",
"select_delivery_type": "Yetkazib berish turlarini tanlang",
"delivery_type_avia": "Havo",
"delivery_type_car": "Avtomobil"
},
"price": "Narx",
"__dash": "------------------------------------BOSHQARUV PANELI------------------------------------------------------",
"dashboard": "Boshqaruv paneli",
"parties": "Partiyalar",
"create_party": "Partiya yaratish",
"update_party": "Partiyani yangilash",
"create_box": "Qutni yaratish",
"create_packet": "Paket yaratish",
"update_box": "Qutni yangilash",
"update_package": "Paketni yangilash",
"update_item": "Mahsulotni yangilash",
"no": "Yo'q",
"id": "ID",
"name": "Ism",
"party_status": "Partiya holati",
"actions": "Harakatlar",
"reset_filter": "Filtrni qayta o'rnatish",
"search": "Qidiruv",
"boxes": "Qutilar",
"packet": "Paketlar",
"filter": "Filtr",
"party_name": "Partiya nomi",
"box_type": "Qutni turi",
"box_number": "Qutni raqami",
"net_weight": "Netto og'irlik",
"gross_weight": "Brutto vazni",
"box_size": "Qutni o'lchami",
"box_weight": "Qut og'irliği",
"volume": "Hajm",
"netto": "Netto",
"brutto": "Brutto",
"description": "Ta'rif",
"add_product": "Mahsulot qo'shish",
"add_box": "Qut qo'shish",
"edit": "Tahrirlash",
"delete": "O'chirish",
"cargo_id": "Kargo ID",
"please_enter_use_id": "Iltimos, foydalanuvchi ID sini kiriting",
"quantity": "Miqdor",
"weight": "Og'irlik",
"add_more": "Qo'shish",
"create": "Yaratish",
"cancel": "Bekor qilish",
"update": "Yangilash",
"clients": "Mijozlar",
"client": "Mijoz",
"create_client": "Mijoz yaratish",
"client_name": "Mijoz nomi",
"fullName": "To'liq ism",
"products": "Mahsulotlar",
"product_name": "Mahsulot nomi",
"track_id": "Trassirovka ID",
"party": "Partiya",
"staffs": "Xodimlar",
"create_staff": "Xodim yaratish",
"username": "Foydalanuvchi nomi",
"role": "Rol",
"status": "Holat",
"active": "Faol",
"not_active": "Faol emas",
"log_out": "Chiqish",
"password": "Parol",
"create_invoice": "Sinfakt yaratish",
"hasInvoice": "Invoice mavjud",
"required": "Talab qilinadi",
"total_price": "Umumiy narxi",
"not_found": "Topilmadi",
"loading": "Yuklanmoqda",
"count_of_boxes": "Qutilar soni",
"count_of_items": "Mahsulotlar soni",
"download_excel": "Excelni yuklash",
"weight_of_items": "Mahsulotlar og'irligi",
"enter_party_name_to_find": "Qidirish uchun partiya nomini kiriting...",
"enter_box_name_to_find": "Qidirish uchun qutini nomini kiriting...",
"enter_client_name_to_find": "Qidirish uchun mijoz nomini kiriting...",
"filter_packet_name": "Paket nomi filtri",
"filter_party_name": "Partiya nomi filtri",
"filter_box_name": "Quti nomi filtri",
"filter_track_id": "Kuzatish ID filtri",
"filter_client_name": "Mijoz nomi filtri",
"filter_item_name": "Mahsulot nomi filtri",
"filter_by_box_status": "Qut statusi filtri",
"filter_by_party_status": "Partiya holati filtri",
"COLLECTING": "To'plash",
"ON_THE_WAY": "Yo'lda",
"ARRIVED": "Yetib keldi",
"READY_TO_INVOICE": "Hisob-fakturaga tayyor",
"READY": "Tayyor",
"IN_CUSTOMS": "Bojxonada",
"IN_WAREHOUSE": "Yetkizilyabdi",
"DELIVERED": "Yetkazildi",
"add_photo_to_item": "Mahsulotga rasm qo'shish",
"photo": "Rasm",
"summa": "Summa",
"avia_cargo_id": "Aviatsiya yuk identifikatori",
"auto_cargo_id": "Avto yuk identifikatori",
"update_client": "Mijozni tahrirlash",
"pinfl": "Pinfl",
"passportSeries": "Passport seriya",
"search_available_in_trekid": "Bu yerda faqat TrekID orqali mahsulotni qidirishingiz mumkin",
"party_total_brutto": "Partiyaning umumiy bruttosi",
"date_of_birth": "Tug'ilgan kuni",
"are_you_sure_delete_client_id": "Rostdan ham {id} id lik klientni o'chirmoqchimisiz?",
"download_all_items": "Barcha mahsulotlarni yuklab olish",
"download_all_clients": "Barcha mijozlarni yuklab olish",
"are_you_sure_delete_party": "{name} yuk partiyasini o'chirishga ishonchingiz komilmi?",
"passport": "Pasport",
"update_packet": "Paketni Tahrirlash",
"party_weight": "Partiya og'irligi"
}

25
next.config.js Normal file
View File

@@ -0,0 +1,25 @@
const withNextIntl = require('next-intl/plugin')('./i18n.ts');
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '**',
},
{
protocol: 'http',
hostname: '**',
},
],
},
output: 'standalone',
reactStrictMode: false,
typescript: {
ignoreBuildErrors: false,
},
};
module.exports = withNextIntl(nextConfig);

8579
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

75
package.json Normal file
View File

@@ -0,0 +1,75 @@
{
"name": "c-post",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "cross-env NEXT_PUBLIC_API_URL=https://cpcargo.uz next dev --port=3080",
"build": "next build",
"build:dev": "cross-env NEXT_PUBLIC_API_URL=https://cpcargo.uz next build",
"build:prod": "cross-env NEXT_PUBLIC_API_URL=https://api.cpost-express.uz next build",
"start": "next start",
"lint": "next lint",
"test": "vitest",
"check-types": "tsc --noemit",
"format-code": "prettier --write .",
"prepare": "husky install"
},
"dependencies": {
"@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.15.3",
"@mui/material": "^5.15.3",
"@mui/material-nextjs": "^5.15.3",
"@tanstack/react-query": "^4.35.0",
"@tanstack/react-query-next-experimental": "^5.17.9",
"aos": "^2.3.4",
"axios": "^1.6.5",
"cookies-next": "^4.1.0",
"i18next": "^23.7.16",
"lodash.clonedeep": "^4.5.0",
"lodash.debounce": "^4.0.8",
"lodash.get": "^4.4.2",
"lodash.omit": "^4.5.0",
"next": "14.0.4",
"next-i18next": "^15.2.0",
"next-intl": "^3.4.2",
"nextjs-progressbar": "^0.0.16",
"react": "^18",
"react-dom": "^18",
"react-hook-form": "^7.49.3",
"react-hot-toast": "^2.4.1",
"react-i18next": "^14.0.0",
"react-select": "^5.8.0",
"simplebar-react": "^3.2.4",
"swiper": "^11.0.5",
"use-dehydrated-state": "^0.1.0",
"zustand": "^4.4.7"
},
"devDependencies": {
"@tanstack/react-query-devtools": "^4.35.3",
"@types/aos": "^3.0.7",
"@types/i18next": "^13.0.0",
"@types/lodash.clonedeep": "^4.5.9",
"@types/lodash.debounce": "^4.0.9",
"@types/lodash.get": "^4.4.9",
"@types/lodash.omit": "^4.5.9",
"@types/node": "^20",
"@types/parse-json": "^4.0.2",
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react-i18next": "^8.1.0",
"@types/react-select": "^5.0.1",
"@vitejs/plugin-react": "^4.2.1",
"cross-env": "^7.0.3",
"eslint": "^8",
"eslint-config-next": "14.0.4",
"husky": "^8.0.0",
"jsdom": "^23.2.0",
"lint-staged": "^15.2.0",
"prettier": "^3.1.1",
"typescript": "^5",
"vitest": "^1.1.3"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/logo.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

8
public/robots.txt Normal file
View File

@@ -0,0 +1,8 @@
# sitecode:cpost-express
Host: https://cpost-express.uz
Sitemap: https://cpost-express.uz/sitemap.xml
User-agent: *
Disallow: */dashboard/*
Disallow: */login/*
Disallow: */news/*
Allow: /

11
public/site.webmanifest Normal file
View File

@@ -0,0 +1,11 @@
{
"name": "CPost",
"short_name": "CPost",
"icons": [
{ "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

11
public/sitemap.xml Normal file
View File

@@ -0,0 +1,11 @@
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://cpost-express.uz/en</loc>
</sitemap>
<sitemap>
<loc>https://cpost-express.uz/ru</loc>
</sitemap>
<sitemap>
<loc>https://cpost-express.uz/uz</loc>
</sitemap>
</sitemapindex>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,256 @@
'use client';
import { useEffect, useState } from 'react';
import { Box, TextField, Typography, Grid, Button, Card, CardMedia, CardContent } from '@mui/material';
import Layout from '@/components/layout/landing-layout';
import Container from '@/components/common/Container';
import { useMyNavigation } from '@/hooks/useMyNavigation';
export default function UserEditForm() {
const { back, replace } = useMyNavigation();
const [userData, setUserData] = useState({
fullName: 'Helen Zhou',
birthDate: '1990-11-11',
phone: '+998 99 365 48 11',
serialNumber: 'AB342534',
pinfl: '12548965451254',
address: 'Toshkent sh. Uchtepa 12',
aviaID: 'AT010001',
cargoID: 'T010001',
branch: 'Toshkent sh',
});
const token = localStorage.getItem('token');
const [passportFrontImage, setPassportFrontImage] = useState<string | null>(
'https://a57.foxnews.com/static.foxnews.com/foxnews.com/content/uploads/2018/09/1200/675/Ireland-Plastic-Passports-1.jpg?ve=1&tl=1'
);
const [passportBackImage, setPassportBackImage] = useState<string | null>(
'https://nebula.wsimg.com/08bfbadc8356b6a0ba6fd1a05dbc4987?AccessKeyId=80E5C6BFDBBCCE7AC676&disposition=0&alloworigin=1'
);
const [userImage, setUserImage] = useState<string | null>(
'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?fm=jpg&q=60&w=3000&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Mnx8ZmVtYWxlJTIwcHJvZmlsZXxlbnwwfHwwfHx8MA%3D%3D'
);
useEffect(() => {
if (!token) {
replace('/');
}
}, []);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setUserData({ ...userData, [e.target.name]: e.target.value });
};
const handleImageUpload = (event: React.ChangeEvent<HTMLInputElement>, key?: string) => {
if (event.target.files && event.target.files[0]) {
const reader = new FileReader();
reader.onload = e => {
if (e.target) {
if (key === 'front') setPassportFrontImage(e.target.result as string);
else if (key === 'user') setUserImage(e.target.result as string);
else setPassportBackImage(e.target.result as string);
}
};
reader.readAsDataURL(event.target.files[0]);
}
};
return (
<Layout>
<Container>
<Box sx={{ p: { xs: 2, sm: 4 }, mx: 'auto' }}>
<Typography variant='h6' fontWeight='bold' mb={2}>
{"Ma'lumotlarni tahrirlash"}
</Typography>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<TextField fullWidth label="To'liq ism" name='fullName' value={userData.fullName} onChange={handleChange} />
</Grid>
<Grid item xs={12} sm={6}>
<TextField
fullWidth
type='date'
label="Tug'ilgan kun"
name='birthDate'
value={userData.birthDate}
onChange={handleChange}
InputLabelProps={{ shrink: true }}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField fullWidth label='Telefon raqam' name='phone' value={userData.phone} onChange={handleChange} />
</Grid>
<Grid item xs={12} sm={6}>
<TextField
fullWidth
label='Seriya raqam'
name='serialNumber'
value={userData.serialNumber}
onChange={handleChange}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField fullWidth label='PINFL' name='pinfl' value={userData.pinfl} onChange={handleChange} />
</Grid>
<Grid item xs={12} sm={6}>
<TextField fullWidth label='Qulay filial' name='branch' value={userData.branch} onChange={handleChange} />
</Grid>
<Grid item xs={12} sm={6}>
<TextField fullWidth label='Adres' name='address' value={userData.address} onChange={handleChange} />
</Grid>
<Grid item xs={12} sm={6}>
<TextField fullWidth label='Avia ID' name='aviaID' value={userData.aviaID} onChange={handleChange} />
</Grid>
<Grid item xs={12} sm={6}>
<TextField fullWidth label='Kargo ID' name='cargoID' value={userData.cargoID} onChange={handleChange} />
</Grid>
</Grid>
<Box mt={3} sx={{ display: 'flex', gap: '50px' }}>
<Box>
<Typography variant='body1' fontWeight='bold' mb={1}>
Rasmingiz
</Typography>
<input
type='file'
accept='image/*'
onChange={e => handleImageUpload(e, 'user')}
style={{ display: 'none' }}
id='user-upload'
/>
<label htmlFor='user-upload'>
<Card
sx={{
width: '80px',
height: '80px',
borderRadius: '50%',
cursor: 'pointer',
border: '1px dashed gray',
display: 'inline-block',
}}
>
{userImage ? (
<CardMedia
component='img'
sx={{
width: '100%',
height: '100%',
objectFit: 'cover',
objectPosition: 'center',
}}
image={userImage}
alt='Passport Image'
/>
) : (
<CardContent sx={{ textAlign: 'center', p: 4 }}>
<Typography variant='body2' color='gray'>
Yuklash uchun bosing
</Typography>
</CardContent>
)}
</Card>
</label>
</Box>
<Box>
<Typography variant='body1' fontWeight='bold' mb={1}>
Passport rasmi
</Typography>
<input
type='file'
accept='image/*'
onChange={e => handleImageUpload(e, 'front')}
style={{ display: 'none' }}
id='passport-front-upload'
/>
<input
type='file'
accept='image/*'
onChange={handleImageUpload}
style={{ display: 'none' }}
id='passport-back-upload'
/>
<Box sx={{ display: 'flex', gap: '15px' }}>
<label htmlFor='passport-front-upload'>
<Card
sx={{
maxWidth: 250,
cursor: 'pointer',
border: '1px dashed gray',
display: 'inline-block',
}}
>
{passportFrontImage ? (
<CardMedia
component='img'
height='140'
sx={{
maxWidth: '200px',
maxHeight: '200px',
objectFit: 'contain',
objectPosition: 'center',
}}
image={passportFrontImage}
alt='Passport Image'
/>
) : (
<CardContent sx={{ textAlign: 'center', p: 4 }}>
<Typography variant='body2' color='gray'>
Yuklash uchun bosing
</Typography>
</CardContent>
)}
</Card>
</label>
{(!!passportFrontImage || !!passportBackImage) && (
<label htmlFor='passport-back-upload'>
<Card
sx={{
maxWidth: 250,
cursor: 'pointer',
border: '1px dashed gray',
display: 'inline-block',
}}
>
{!!passportBackImage ? (
<CardMedia
component='img'
height='140'
sx={{
maxWidth: '200px',
maxHeight: '200px',
objectFit: 'contain',
objectPosition: 'center',
}}
image={passportBackImage}
alt='Passport Image'
/>
) : (
<CardContent sx={{ textAlign: 'center', p: 4 }}>
<Typography variant='body2' color='gray'>
Yuklash uchun bosing
</Typography>
</CardContent>
)}
</Card>
</label>
)}
</Box>
</Box>
</Box>
<Box mt={3} display='flex' justifyContent='flex-end' gap={2}>
<Button onClick={back} variant='outlined' color='primary'>
Bekor qilish
</Button>
<Button variant='contained' color='primary'>
Saqlash
</Button>
</Box>
</Box>
</Container>
</Layout>
);
}

View File

@@ -0,0 +1,99 @@
'use client';
import React from 'react';
import { Box, Typography, Grid, Button } from '@mui/material';
import { IconCopy } from '@/components/common/Icons';
export default function Page() {
const data = [
{
title: '🚚 Avto Kargo',
recipient: '777( TT-001)',
phone: '15532640276',
address: '主洗车旁1号门777( TT-001)',
},
{
title: '✈️ Avia Kargo',
recipient: '乌兹777( TT-001)',
phone: '18922155990',
address: '楼档口 777( TT-001) AVTO',
},
];
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text).catch(err => {
console.error('Nusxa olishda xatolik yuz berdi:', err);
});
};
return (
<Box p={2}>
<Box sx={{display: 'flex', flexDirection: 'column', gap: '35px'}}>
{data.map((item, index) => (
<Box key={index}>
<Typography variant={'h6'} marginBottom={"10px"}>{item.title}</Typography>
<Box sx={{display: 'flex', flexDirection: 'column', gap: '10px'}}>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<span>:</span>
<Box
sx={{
borderBottom: '1px dashed #333',
flexGrow: 1,
position: 'relative',
bottom: '3px',
}}
/>
<Button
variant={'text'}
sx={{ display: 'flex', alignItems: 'center', gap: '3px', height: '19px' }}
onClick={() => copyToClipboard(item.recipient)}
>
{item.recipient}
<IconCopy />
</Button>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<span>:</span>
<Box
sx={{
borderBottom: '1px dashed #333',
flexGrow: 1,
position: 'relative',
bottom: '3px',
}}
/>
<Button
variant={'text'}
sx={{ display: 'flex', alignItems: 'center', gap: '3px', height: '19px' }}
onClick={() => copyToClipboard(item.phone)}
>
{item.phone}
<IconCopy />
</Button>
</Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<span>:</span>
<Box
sx={{
borderBottom: '1px dashed #333',
flexGrow: 1,
position: 'relative',
bottom: '3px',
}}
/>
<Button
variant={'text'}
sx={{ display: 'flex', alignItems: 'center', gap: '3px', height: '19px' }}
onClick={() => copyToClipboard(item.address)}
>
{item.address}
<IconCopy />
</Button>
</Box>
</Box>
</Box>
))}
</Box>
</Box>
);
}

View File

@@ -0,0 +1,241 @@
'use client';
import React, { useState } from 'react';
import { TextField, Button, Grid, Typography, Box, Card, CardMedia, CardActions, IconButton } from '@mui/material';
import { styled } from '@mui/material/styles';
import { toBase64 } from '@/helpers/toBase64';
import axios from 'axios';
import toast from 'react-hot-toast';
import PhotoCamera from '@mui/icons-material/PhotoCamera';
import DeleteIcon from '@mui/icons-material/Delete';
import EditIcon from '@mui/icons-material/Edit';
import { BASE_URI, BASE_URL } from '@/helpers/constants';
import myAxios from '@/helpers/myAxios';
import { useMyNavigation } from '@/hooks/useMyNavigation';
interface UserFormProps {
apiUrl: string;
}
const Input = styled('input')({
display: 'none',
});
const UserForm = () => {
const { push } = useMyNavigation();
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [passportSeries, setPassportSeries] = useState('');
const [pinfl, setPinfl] = useState('');
const [passportFront, setPassportFront] = useState<File | null>(null);
const [passportBack, setPassportBack] = useState<File | null>(null);
const [passportFrontPreview, setPassportFrontPreview] = useState<string | null>(null);
const [passportBackPreview, setPassportBackPreview] = useState<string | null>(null);
const [errors, setErrors] = useState<Record<string, string>>({});
const validateForm = () => {
const newErrors: Record<string, string> = {};
if (!firstName) newErrors.firstName = 'Ism majburiy!';
if (!lastName) newErrors.lastName = 'Familiya majburiy!';
if (!passportSeries) newErrors.passportSeries = 'Passport seriya raqami majburiy!';
if (!pinfl) newErrors.pinfl = 'PINFL (JSHSHR) majburiy!';
if (!passportFront) newErrors.passportFront = 'Passport oldi rasmi majburiy!';
if (!passportBack) newErrors.passportBack = 'Passport orqa rasmi majburiy!';
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handlePassportFrontChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0] || null;
if (file) {
setPassportFront(file);
const reader = new FileReader();
reader.onloadend = () => {
setPassportFrontPreview(reader.result as string);
};
reader.readAsDataURL(file);
}
};
const handlePassportBackChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0] || null;
if (file) {
setPassportBack(file);
const reader = new FileReader();
reader.onloadend = () => {
setPassportBackPreview(reader.result as string);
};
reader.readAsDataURL(file);
}
};
const clearPassportFront = () => {
setPassportFront(null);
setPassportFrontPreview(null);
};
const clearPassportBack = () => {
setPassportBack(null);
setPassportBackPreview(null);
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validateForm()) return;
try {
const frontBase64 = passportFront ? await toBase64(passportFront) : '';
const backBase64 = passportBack ? await toBase64(passportBack) : '';
const payload = {
fullName: firstName + ' ' + lastName,
passportSeries,
passportPin: pinfl,
passportFrontImage: frontBase64,
passportBackImage: backBase64,
};
const token = localStorage.getItem('token');
const response = await axios.post(`${BASE_URI}/me/passports`, payload, {
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
toast.success("Ma'lumotlar muvaffaqiyatli yuborildi!");
push('/profile/user-data');
} catch (error) {
console.error('API xatosi:', error);
toast.error("Ma'lumotlar yuborishda xatolik yuz berdi!");
}
};
return (
<Box sx={{ width: '100%', padding: '20px' }}>
<Typography variant='h5' align='center' gutterBottom>
{"Foydalanuvchi ma'lumotlari"}
</Typography>
<form onSubmit={handleSubmit}>
<Grid container spacing={2}>
<Grid item xs={12} sm={6}>
<TextField
label='Ism'
variant='outlined'
fullWidth
value={firstName}
onChange={e => setFirstName(e.target.value)}
required
error={!!errors.firstName}
helperText={errors.firstName}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
label='Familiya'
variant='outlined'
fullWidth
value={lastName}
onChange={e => setLastName(e.target.value)}
required
error={!!errors.lastName}
helperText={errors.lastName}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
label='Passport seriya raqami'
variant='outlined'
fullWidth
value={passportSeries}
onChange={e => setPassportSeries(e.target.value)}
required
error={!!errors.passportSeries}
helperText={errors.passportSeries}
/>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
label='PINFL (JSHSHR)'
variant='outlined'
fullWidth
value={pinfl}
onChange={e => setPinfl(e.target.value)}
required
error={!!errors.pinfl}
helperText={errors.pinfl}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Card>
<label htmlFor='passport-front-upload'>
<Input accept='image/*' id='passport-front-upload' type='file' onChange={handlePassportFrontChange} />
{passportFrontPreview ? (
<CardMedia component='img' height='140' image={passportFrontPreview} alt='Passport front' />
) : (
<Button variant='outlined' component='span' fullWidth startIcon={<PhotoCamera />}>
Passport oldi rasmi
</Button>
)}
</label>
{passportFrontPreview && (
<CardActions>
<IconButton onClick={clearPassportFront} aria-label='delete'>
<DeleteIcon />
</IconButton>
<label htmlFor='passport-front-upload'>
<IconButton component='span' aria-label='edit'>
<EditIcon />
</IconButton>
</label>
</CardActions>
)}
</Card>
{errors.passportFront && (
<Typography variant='caption' color='error'>
{errors.passportFront}
</Typography>
)}
</Grid>
<Grid item xs={12} sm={6}>
<Card>
<label htmlFor='passport-back-upload'>
<Input accept='image/*' id='passport-back-upload' type='file' onChange={handlePassportBackChange} />
{passportBackPreview ? (
<CardMedia component='img' height='140' image={passportBackPreview} alt='Passport back' />
) : (
<Button variant='outlined' component='span' fullWidth startIcon={<PhotoCamera />}>
Passport orqa rasmi
</Button>
)}
</label>
{passportBackPreview && (
<CardActions>
<IconButton onClick={clearPassportBack} aria-label='delete'>
<DeleteIcon />
</IconButton>
<label htmlFor='passport-back-upload'>
<IconButton component='span' aria-label='edit'>
<EditIcon />
</IconButton>
</label>
</CardActions>
)}
</Card>
{errors.passportBack && (
<Typography variant='caption' color='error'>
{errors.passportBack}
</Typography>
)}
</Grid>
<Grid item xs={12}>
<Button type='submit' variant='contained' color='primary' fullWidth>
Yuborish
</Button>
</Grid>
</Grid>
</form>
</Box>
);
};
export default UserForm;

View File

@@ -0,0 +1,9 @@
import React from 'react';
import IUserForm from './component/IUserForm';
const Home: React.FC = () => {
return <IUserForm />;
};
export default Home;

View File

@@ -0,0 +1,69 @@
'use client';
import { useState } from 'react';
import { Box, TextField, Button, Typography } from '@mui/material';
export default function Page() {
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [error, setError] = useState(false);
const handleChangePassword = (e: React.ChangeEvent<HTMLInputElement>) => {
setPassword(e.target.value);
if (confirmPassword || error) setError(e.target.value !== confirmPassword);
};
const handleConfirmPassword = (e: React.ChangeEvent<HTMLInputElement>) => {
setConfirmPassword(e.target.value);
setError(e.target.value !== password);
};
const handleSubmit = () => {
if (!error && password) {
setPassword('');
setConfirmPassword('');
}
};
return (
<Box sx={{ p: 2 }}>
<Typography variant='h6' fontWeight='bold' mb={2}>
{"Parolni o'zgartirish"}
</Typography>
<Box sx={{ display: 'flex', gap: '10px', alignItems: 'start' }}>
<TextField fullWidth label='Yangi parol' type='password' value={password} onChange={handleChangePassword} margin='normal' />
<TextField
fullWidth
label='Parolni tasdiqlash'
type='password'
value={confirmPassword}
onChange={handleConfirmPassword}
margin='normal'
error={error}
helperText={error ? 'Parollar mos kelmadi!' : ''}
/>
</Box>
<Box
sx={{
display: 'flex',
justifyContent: 'end',
transition: 'all 0.6s ease-out',
opacity: password ? '1' : '0',
}}
>
<Button
fullWidth
variant='contained'
color='primary'
sx={{ width: '180px' }}
onClick={handleSubmit}
disabled={error || !password || !confirmPassword}
>
Tasdiqlash
</Button>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,72 @@
'use client';
import { useState } from 'react';
import { Box, TextField, Button, Typography, useMediaQuery, useTheme } from '@mui/material';
import { useMyNavigation } from '@/hooks/useMyNavigation';
import { styled } from '@mui/material/styles';
const links_list = [
{
title: "Passport qo'shish",
path: '/add-passport',
},
{
title: "Parolni o'zgartirish",
path: '/edit-password',
},
];
const StyledButton = styled(Button)(({ theme }) => ({
padding: theme.spacing(2, 4),
borderRadius: theme.spacing(1),
fontSize: '1rem',
fontWeight: 'bold',
boxShadow: theme.shadows[2],
transition: 'transform 0.3s ease, box-shadow 0.3s ease',
backgroundColor: '#34495e',
color: 'white',
'&:hover': {
backgroundColor: '#2c3e50',
},
[theme.breakpoints.down('sm')]: {
// Kichik ekranlar uchun
width: '100%',
marginBottom: theme.spacing(2),
},
}));
export default function Page() {
const { push } = useMyNavigation();
const theme = useTheme();
const isDesktop = useMediaQuery(theme.breakpoints.up('md'));
const sidebarWidth = isDesktop ? 197 : 0;
const maxWidth = 1152;
const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm'));
const navigate_handler = (url: string) => {
push('/profile/settings' + url);
};
return (
<Box
sx={{
width: `calc(100% - ${sidebarWidth}px)`,
maxWidth: `${maxWidth}px`,
margin: '0 auto',
padding: '20px',
display: 'flex',
flexDirection: isSmallScreen ? 'column' : 'row', // Kichik ekranda ustunli layout
alignItems: 'center',
justifyContent: 'center',
gap: '20px',
}}
>
<StyledButton onClick={() => navigate_handler('/add-passport')} variant='contained'>
{"PASSPORT QO'SHISH"}
</StyledButton>
<StyledButton onClick={() => navigate_handler('/edit-password')} variant='contained'>
{"PAROLNI O'ZGARTIRISH"}
</StyledButton>
</Box>
);
}

View File

@@ -0,0 +1,378 @@
'use client';
import { Box, Button, Dialog, Grid, IconButton, Slide, Typography } from '@mui/material';
import { useMyNavigation } from '@/hooks/useMyNavigation';
import useMainFetch from '@/hooks/useMainFetch';
import { IUserData } from '@/app/[locale]/(views)/profile/components/IAboutUser';
import Loader from '@/components/common/Loader';
import { CloseIcon } from 'next/dist/client/components/react-dev-overlay/internal/icons/CloseIcon';
import { useState } from 'react';
interface IPassport {
active: boolean;
fullName: string;
passportBackImage: string;
passportFrontImage: string;
passportPin: string;
passportSeries: string;
}
const branches = [
{ value: 'TASHKENT_CITY', label: 'Toshkent shahri' },
{ value: 'FERGHANA', label: "Farg'ona viloyati" },
{ value: 'SAMARQAND', label: 'Samarqand viloyati' },
];
const Transition = (props: any) => <Slide direction='up' {...props} />;
const get_current_branches = (value: string | undefined) => branches.find(item => item.value === value)?.label || 'Toshkent';
export default function Page() {
const { push } = useMyNavigation();
const [selectedImage, setSelectedImage] = useState<string | null>(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const handleImageClick = (image: string) => {
setSelectedImage(image);
setIsModalOpen(true);
};
const handleModalClose = () => {
setIsModalOpen(false);
setSelectedImage(null);
};
const {data, isLoading} = useMainFetch({
key: "client-data",
endpoint: "/me"
})
const clientData = data?.data?.data
const passportsFetchData = useMainFetch({
key: 'passports',
endpoint: '/me/passports',
generateData: res => res.data,
});
const passportsData: IPassport[] = passportsFetchData?.data?.data || [];
const userFetchData = clientData as IUserData;
const userData = [
// {
// title: "To'liq ism",
// value: userFetchData?.name,
// },
{
title: 'Telefon raqam',
value: userFetchData?.phone,
},
{
title: 'Kargo ID',
value: userFetchData?.aviaCargoId,
},
{
title: "Tug'ilgan kun",
value: userFetchData?.dateOfBirth,
},
{
title: 'Adress',
value: userFetchData?.address,
},
{
title: 'Qulay filial',
value: get_current_branches(userFetchData?.region),
},
];
if (isLoading)
return (
<Box position={'relative'} sx={{ width: '100%', height: '100%', display: 'flex', justifyContent: 'center' }}>
<Loader />
</Box>
);
return (
<Box p={5}>
<Typography variant='h5' fontWeight='bold' mb={3}>
{"Shaxsiy ma'lumotlar"}
</Typography>
<Grid container spacing={0}>
{userData.map((item, index) => (
<Grid item xs={12} key={index} sx={{ borderBottom: '1px solid #e0e0e0', padding: '16px 0' }}>
<Typography variant='subtitle2' sx={{ fontWeight: 600, color: '#333', marginBottom: '4px' }}>
{item.title}
</Typography>
<Typography variant='body1' sx={{ color: '#555', fontSize: '1rem' }}>
{item.value || '...'}
</Typography>
</Grid>
))}
</Grid>
<Box sx={{ marginTop: '40px' }}>
{passportsData.length > 0 && (
<Box mt={3}>
<Typography variant='h5' fontWeight='bold' mb={3}>
{"Passport Ma'lumotlari"}
</Typography>
<Grid container spacing={3}>
{passportsData.map((passport, index) => (
<Grid item xs={12} sm={6} md={4} key={index}>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
p: 3,
borderRadius: 3,
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
border: '1px solid',
borderColor: 'grey.400',
transition: 'transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out',
'&:hover': {
transform: 'scale(1.03)',
boxShadow: '0 6px 18px rgba(0,0,0,0.25)',
},
}}
>
<Typography variant='h6' fontWeight='600' mb={2} color='text.primary'>
{passport.fullName}
</Typography>
<Grid container spacing={1} alignItems='center'>
<Grid item xs={6}>
<Box
onClick={() => handleImageClick(passport.passportFrontImage)}
sx={{
cursor: 'pointer',
borderRadius: 2,
overflow: 'hidden',
aspectRatio: '4/3',
}}
>
<Box
component='img'
src={passport.passportFrontImage}
alt='Passport oldi'
sx={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
</Box>
</Grid>
<Grid item xs={6}>
<Box
onClick={() => handleImageClick(passport.passportBackImage)}
sx={{
cursor: 'pointer',
borderRadius: 2,
overflow: 'hidden',
aspectRatio: '4/3',
}}
>
<Box
component='img'
src={passport.passportBackImage}
alt='Passport orqa'
sx={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
</Box>
</Grid>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
margin: '20px 0 10px',
}}
>
<Typography variant='body2' color='text.secondary'>
Seriya
</Typography>
<Box
sx={{
borderBottom: '2px dotted #99999999',
width: '100%',
position: 'relative',
top: '7px',
margin: '0 4px',
}}
></Box>
<Typography variant='body2' color='text.secondary'>
{passport.passportSeries}
</Typography>
</Box>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
marginBottom: '10px',
}}
>
<Typography variant='body2' color='text.secondary'>
PIN:
</Typography>
<Box
sx={{
borderBottom: '2px dotted #99999999',
width: '100%',
position: 'relative',
top: '7px',
margin: '0 4px',
}}
></Box>
<Typography variant='body2' color='text.secondary'>
{passport.passportPin}
</Typography>
</Box>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
}}
>
<Typography variant='body2' color='text.secondary'>
Holati:
</Typography>
<Box
sx={{
borderBottom: '2px dotted #99999999',
width: '100%',
position: 'relative',
top: '7px',
margin: '0 4px',
}}
></Box>
<Typography variant='body2' color='text.secondary'>
{passport.active ? 'Faol' : 'Faolmas'}
</Typography>
</Box>
</Grid>
</Box>
</Grid>
))}
</Grid>
</Box>
)}
</Box>
{/*<Box marginTop={'14px'}>*/}
{/* {passportsData.length > 0 && (*/}
{/* <Box marginTop={'24px'}>*/}
{/* <Typography variant='h6' fontWeight='bold' mb={2}>*/}
{/* {"Passport Ma'lumotlari"}*/}
{/* </Typography>*/}
{/* <Grid container spacing={3}>*/}
{/* {passportsData.map((passport, index) => (*/}
{/* <Grid item xs={12} sm={6} md={4} key={index}>*/}
{/* <Box*/}
{/* border={'1px solid #ddd'}*/}
{/* borderRadius={'8px'}*/}
{/* padding={'16px'}*/}
{/* boxShadow={'0 2px 4px rgba(0, 0, 0, 0.1)'}*/}
{/* >*/}
{/* <Typography variant='subtitle1' fontWeight='bold' gutterBottom>*/}
{/* {passport.fullName}*/}
{/* </Typography>*/}
{/* <Typography variant='body2' color='gray'>*/}
{/* Seriya: {passport.passportSeries}*/}
{/* </Typography>*/}
{/* <Typography variant='body2' color='gray'>*/}
{/* PIN: {passport.passportPin}*/}
{/* </Typography>*/}
{/* <Typography variant='body2' color='gray'>*/}
{/* Faol: {passport.active ? 'Ha' : "Yo'q"}*/}
{/* </Typography>*/}
{/* <Box sx={{ display: 'flex', gap: '8px', marginTop: '10px', justifyContent: 'center' }}>*/}
{/* <Box onClick={() => handleImageClick(passport.passportFrontImage)}>*/}
{/* <Box*/}
{/* component='img'*/}
{/* src={passport.passportFrontImage}*/}
{/* sx={{*/}
{/* width: '80px',*/}
{/* height: '80px',*/}
{/* objectFit: 'contain',*/}
{/* cursor: 'pointer',*/}
{/* }}*/}
{/* alt={'Passport oldi rasmi'}*/}
{/* />*/}
{/* </Box>*/}
{/* <Box onClick={() => handleImageClick(passport.passportBackImage)}>*/}
{/* <Box*/}
{/* component='img'*/}
{/* src={passport.passportBackImage}*/}
{/* sx={{*/}
{/* width: '80px',*/}
{/* height: '80px',*/}
{/* objectFit: 'contain',*/}
{/* cursor: 'pointer',*/}
{/* }}*/}
{/* alt={'Passport orqa taraf rasmi'}*/}
{/* />*/}
{/* </Box>*/}
{/* </Box>*/}
{/* </Box>*/}
{/* </Grid>*/}
{/* ))}*/}
{/* </Grid>*/}
{/* </Box>*/}
{/* )}*/}
{/*</Box>*/}
<Dialog
open={isModalOpen}
TransitionComponent={Transition}
keepMounted
onClose={handleModalClose}
aria-describedby='alert-dialog-slide-description'
fullWidth
>
<IconButton
aria-label='close'
onClick={handleModalClose}
sx={{
position: 'absolute',
right: 8,
top: 8,
color: theme => theme.palette.grey[500],
}}
>
<CloseIcon />
</IconButton>
{selectedImage && (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
padding: '24px',
}}
>
<Box
component='img'
src={selectedImage}
sx={{
maxWidth: '100%',
maxHeight: '80vh',
objectFit: 'contain',
}}
alt={'Kattalashtirilgan rasm'}
/>
</Box>
)}
</Dialog>
{/*<Box sx={{ display: 'flex', justifyContent: 'end', marginTop: '8px' }}>*/}
{/* <Button onClick={editHandler} sx={{ textTransform: 'capitalize' }}>*/}
{/* {'Tahrirlash'}*/}
{/* </Button>*/}
{/*</Box>*/}
</Box>
);
}

View File

@@ -0,0 +1,89 @@
'use client';
import { Box, Typography, Avatar, Divider, Button } from '@mui/material';
import { IconExit } from '@/components/common/Icons';
import { useRouter } from 'next/navigation';
import useMainFetch from '@/hooks/useMainFetch';
export interface IUserData {
name: string;
phone: string;
passportSeries: string;
pinfl: string;
dateOfBirth: string;
address: string;
region: string;
passportFront: string;
passportBack: string;
aviaCargoId: string | null;
autoCargoId: string | null;
registered: boolean;
}
export default function IAboutUser() {
const router = useRouter();
const exitHandler = () => {
router.push('/');
localStorage.clear();
};
const fetchData = useMainFetch({
key: 'user-data',
endpoint: '/me',
generateData: res => res.data.data,
});
const userData = fetchData.data as IUserData;
return (
<Box sx={{ p: { xs: 2, sm: 3, md: 4 }, mx: 'auto' }}>
<Box display={'flex'} position={'relative'} justifyContent={{ xs: 'center', md: 'space-between' }} alignItems={'center'}>
<Box
display='flex'
alignItems='center'
flexDirection={{ xs: 'column', md: 'row' }}
textAlign={{ xs: 'center', md: 'left' }}
mb={3}
>
{/*<Avatar*/}
{/* src='https://images.unsplash.com/photo-1438761681033-6461ffad8d80?fm=jpg&q=60&w=3000&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Mnx8ZmVtYWxlJTIwcHJvZmlsZXxlbnwwfHwwfHx8MA%3D%3D'*/}
{/* alt='Helen Zhou'*/}
{/* sx={{ width: 80, height: 80, mr: { sm: 2, xs: 0 }, mb: { xs: 1, sm: 0 } }}*/}
{/*/>*/}
<Box>
<Typography variant='h5' fontWeight='bold'>
{userData?.name}
</Typography>
{/*<Typography color='green'>Aktiv</Typography>*/}
<Typography color={userData?.registered ? "green" : "red"}>
{userData?.registered ? "Aktiv" : "Noaktiv"}
</Typography>
</Box>
</Box>
{/*<Button onClick={exitHandler} variant={'text'} color={"inherit"} sx={{display: 'flex', alignItems: 'center', gap: '5px'}}>*/}
<Button
onClick={exitHandler}
variant={'text'}
color={'inherit'}
sx={{
display: 'flex',
opacity: '0.7',
alignItems: 'center',
gap: '5px',
position: { xs: 'absolute' },
right: '0',
top: { xs: '0', md: '18px' },
}}
>
<IconExit />
<Box display={{ xs: 'none', md: 'inline-block' }}>Chiqish</Box>
</Button>
</Box>
<Divider sx={{ mb: { xs: 0 } }} />
</Box>
);
}

View File

@@ -0,0 +1,37 @@
'use client';
import { Box, List, ListItemButton, ListItemText } from '@mui/material';
import { usePathname } from 'next/navigation';
import { IconCard, IconPage, IconProducts, IconSettings } from '@/components/common/Icons';
const menuItems = [
{ value: '', label: 'Mahsulotlar', icon: IconProducts },
{ value: '/user-data', label: "Ma'lumotlarim", icon: IconPage },
// { value: '/id-and-address', label: "ID / Adres", icon: IconCard },
{ value: '/settings', label: "Sozlamalar", icon: IconSettings },
];
export function ISidebar({ locale }: { locale: string }) {
const pathname = usePathname();
return (
// <Box width={{xs: 86, md: 240}} bgcolor='#fff' p={2}>
<Box display={{xs: 'flex', md: 'block'}} bgcolor='#fff' p={2}>
{/*<List disablePadding>*/}
<List disablePadding sx={{display: {xs: 'flex', md: 'inline-block'}, marginX: 'auto'}}>
{menuItems.map(item => (
<ListItemButton
key={item.value}
href={`/${locale}/profile/${item.value}`}
selected={pathname.split('profile').at(-1) === item.value}
sx={{ display: 'flex', alignItems: 'center', gap: '10px' }}
>
{!!item.icon && <item.icon color={pathname.split('profile').at(-1) === item.value ? '#758CA3' : '#777'} />}
<ListItemText primary={item.label} sx={{ fontWeight: 500, display: {xs: "none", md: "block" }}} />
</ListItemButton>
))}
</List>
</Box>
);
}

View File

@@ -0,0 +1,25 @@
'use client'
import { Box } from '@mui/material';
import { ISidebar } from '@/app/[locale]/(views)/profile/components/ISidebar';
import LandingLayout from '@/components/layout/landing-layout';
import IAboutUser from '@/app/[locale]/(views)/profile/components/IAboutUser';
import Container from '@/components/common/Container';
export default function Layout({ children, params }: { children: React.ReactNode; params: { locale: string } }) {
return (
<LandingLayout>
<Container>
<IAboutUser />
{/*<Box display='flex' bgcolor='#f8f8f8' minHeight='50vh'>*/}
<Box display='flex' flexDirection={{xs: 'column', md: 'row'}} bgcolor='#f8f8f8' minHeight='50vh'>
<ISidebar locale={params.locale} />
<Box flex={1}>
{children}
</Box>
</Box>
</Container>
</LandingLayout>
);
}

View File

@@ -0,0 +1,150 @@
'use client';
import React, { useEffect, useState } from 'react';
import { Box, Tabs, Tab, Card, CardMedia, CardContent, Typography, Button, Chip, Avatar, Stack } from '@mui/material';
import { useMyNavigation } from '@/hooks/useMyNavigation';
import Loader from '@/components/common/Loader';
import useMainFetch from '@/hooks/useMainFetch';
import { IUserData } from '@/app/[locale]/(views)/profile/components/IAboutUser';
import { ColumnData, MyTable } from '@/components/common/MyTable';
import BasePagination from '@/components/ui-kit/BasePagination';
import { DEFAULT_PAGE_SIZE } from '@/helpers/constants';
import { PartyStatusList } from '@/data/party/party.model';
import { useMyTranslation } from '@/hooks/useMyTranslation';
interface IProduct {
id: string;
partyName: string;
boxName: string;
cargoId: string;
trekId: string;
name: string;
amount: number;
weight: number;
price: number;
totalPrice: number;
status: string;
hasImage: boolean;
imageUrl: string;
packetName: string;
}
interface IProductData {
data: {
data: IProduct[];
totalPages: number;
totalElements: number;
};
}
export default function ProfilePage() {
const t = useMyTranslation();
const { replace } = useMyNavigation();
const [category, setCategory] = useState('barchasi');
const [page, setPage] = useState(1);
const [pageSize] = useState(DEFAULT_PAGE_SIZE);
const fetchProductsData = useMainFetch({
key: 'products-data',
endpoint: '/me/products',
generateData: res => res.data,
});
const fetchUserData = useMainFetch({
key: 'user-data',
endpoint: '/me',
generateData: res => res.data,
});
const columns: ColumnData<IProduct>[] = [
{
dataKey: 'id',
label: 'No',
width: 70,
},
{
dataKey: 'name',
label: 'Nomi',
width: 200,
},
{
dataKey: 'trekId',
label: 'TrekID',
width: 130,
},
{
dataKey: 'status',
label: 'Status',
width: 160,
renderCell: data => {
return <Box width={160}>{t(data.status)}</Box>;
},
},
{
dataKey: 'packetName',
label: 'Paket',
width: 140,
},
{
dataKey: 'amount',
label: 'Miqdori',
width: 120,
},
// {
// dataKey: 'price',
// label: 'Narxi',
// width: 120,
// },
// {
// dataKey: 'totalPrice',
// label: 'Jami narxi',
// width: 120,
// },
{
dataKey: 'weight',
label: t("weight"),
width: 70,
},
];
const productsData = fetchProductsData.data as IProductData | undefined;
const userData = fetchUserData.data as IUserData | undefined;
useEffect(() => {
if (userData?.registered === false) {
replace('/profile/user-data');
}
}, [userData?.registered]);
const handleChange = (newPage: number) => {
setTimeout(() => {
setPage(newPage);
}, 100);
};
if (fetchUserData?.isLoading || fetchProductsData?.isLoading || !productsData)
return (
<Box position={'relative'} sx={{ width: '100%', height: '100%', display: 'flex', justifyContent: 'center' }}>
<Loader />
</Box>
);
return (
<Box sx={{ p: 2 }}>
<Box mt={2} display='flex' flexDirection='column' gap={2}>
<Box mb={6}>
<MyTable columns={columns} data={productsData?.data?.data} loading={false} />
</Box>
<Stack direction={'row'} justifyContent={'center'}>
<BasePagination
page={page}
pageSize={pageSize}
totalCount={productsData?.data?.totalPages || 0}
onChange={handleChange}
/>
</Stack>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,5 @@
import DashboardClientsPage from '@/routes/private/clients/DashboardClientsPage';
export default function Home() {
return <DashboardClientsPage />;
}

View File

@@ -0,0 +1,5 @@
import DashboardGoodsPage from '@/routes/private/items/DashboardItemsPage';
export default function Home() {
return <DashboardGoodsPage />;
}

View File

@@ -0,0 +1,5 @@
import DashboardLayout from '@/components/layout/dashboard-layout';
export default function RootLayout({ children, params: { locale } }: { children?: React.ReactNode; params: { locale: string } }) {
return <DashboardLayout>{children}</DashboardLayout>;
}

View File

@@ -0,0 +1,9 @@
import { CircularProgress, Stack } from '@mui/material';
export default function DashboardLoading() {
return (
<Stack justifyContent={'center'} alignItems={'center'} p={8}>
<CircularProgress size={'64px'} />
</Stack>
);
}

View File

@@ -0,0 +1,5 @@
import DashboardHome from '@/routes/private/dashboard-home';
export default function Home() {
return <DashboardHome />;
}

View File

@@ -0,0 +1,9 @@
import { CircularProgress, Stack } from '@mui/material';
export default function DashboardLoading() {
return (
<Stack justifyContent={'center'} alignItems={'center'} p={8}>
<CircularProgress size={'64px'} />
</Stack>
);
}

View File

@@ -0,0 +1,22 @@
'use client';
import DashboardCreateBoxPage from '@/routes/private/boxes-create/DashboardCreateBox';
import useRequest from '@/hooks/useRequest';
import { party_requests } from '@/data/party/party.requests';
import Loader from '@/components/common/Loader';
import React from 'react';
export default function Home() {
const partiesData = useRequest(() => party_requests.getAll({ status: 'COLLECTING' }), {
selectData(data) {
return data.data.data.data.map(p => ({ value: p.id, label: p.name }));
},
placeholderData: [],
});
if (partiesData.loading || !partiesData.data) {
return <Loader p={8} size={96} />;
}
return <DashboardCreateBoxPage partiesData={partiesData.data} />;
}

View File

@@ -0,0 +1,9 @@
import { CircularProgress, Stack } from '@mui/material';
export default function DashboardLoading() {
return (
<Stack justifyContent={'center'} alignItems={'center'} p={8}>
<CircularProgress size={'64px'} />
</Stack>
);
}

View File

@@ -0,0 +1,5 @@
import DashboardEditBoxPage from '@/routes/private/boxes-create/DashboardEditBox';
export default function Home() {
return <DashboardEditBoxPage />;
}

View File

@@ -0,0 +1,5 @@
import DashboardBoxesPage from '@/routes/private/boxes/DashboardBoxesPage';
export default function Home() {
return <DashboardBoxesPage />;
}

View File

@@ -0,0 +1,5 @@
import DashboardHome from '@/routes/private/dashboard-home';
export default function Home() {
return <DashboardHome />;
}

View File

@@ -0,0 +1,9 @@
import { CircularProgress, Stack } from '@mui/material';
export default function DashboardLoading() {
return (
<Stack justifyContent={'center'} alignItems={'center'} p={8}>
<CircularProgress size={'64px'} />
</Stack>
);
}

View File

@@ -0,0 +1,5 @@
import DashboardCreatePartyPage from '@/routes/private/parties-create/DashboardCreatePartyPage';
export default function Home() {
return <DashboardCreatePartyPage />;
}

View File

@@ -0,0 +1,9 @@
import { CircularProgress, Stack } from '@mui/material';
export default function DashboardLoading() {
return (
<Stack justifyContent={'center'} alignItems={'center'} p={8}>
<CircularProgress size={'64px'} />
</Stack>
);
}

View File

@@ -0,0 +1,5 @@
import DashboardEditPartyPage from '@/routes/private/parties-create/DashboardEditPartyPage';
export default function Home() {
return <DashboardEditPartyPage />;
}

View File

@@ -0,0 +1,5 @@
import DashboardPartiesPage from '@/routes/private/parties/DashboardPartiesPage';
export default function Home() {
return <DashboardPartiesPage />;
}

View File

@@ -0,0 +1,5 @@
import DashboardStaffsPage from '@/routes/private/staffs/DashboardStaffsPage';
export default function Home() {
return <DashboardStaffsPage />;
}

View File

@@ -0,0 +1,82 @@
:root {
--app-height: 100%;
}
html {
box-sizing: border-box;
scroll-behavior: smooth;
font-size: 16px;
}
body {
margin: 0;
padding: 0;
font-weight: 400;
font-size: 1rem;
font-style: normal;
background-color: #fff;
color: #555351;
font-family: "Inter", "Arial", sans-serif !important;
overflow-x: hidden;
}
.dashboard-layout {
font-family: 'SF Pro Display', sans-serif !important;
}
*,
*::after,
*::before {
box-sizing: inherit;
}
/* *:focus-visible {
outline: 2px dashed #479aff;
outline-offset: 3px;
} */
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
border: 0;
padding: 0;
clip: rect(0 0 0 0);
overflow: hidden;
}
.no-select {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* -------------------------- Number input ni chiziqchalarini ko'rsatmaslik ------------------ */
/* Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type='number'] {
-moz-appearance: textfield;
}
.brd {
border: 1px solid red !important;
}

133
src/app/[locale]/layout.tsx Normal file
View File

@@ -0,0 +1,133 @@
import './globals.css';
import './styles.css';
import 'swiper/css';
import 'swiper/css/autoplay';
import 'simplebar-react/dist/simplebar.min.css';
import 'aos/dist/aos.css';
// import type { Metadata } from 'next';
import { MainProvider } from '@/providers';
// export const metadata: Metadata = {
// title: 'CPost-Express',
// description: 'CPost-Express',
// };
export default function RootLayout({ children, params: { locale } }: { children?: React.ReactNode; params: { locale: string } }) {
const siteLanguage = locale || 'ru';
const seoRu = {
title: 'Cpost express: Авиапочта из Китая в Узбекистан',
desc: 'Cpost express ✈️Авиапочта из Китая в Узбекистан, ⏳Срок доставки 5-14 дней, 📦Минимальный заказ 0 кг, 📷Фото отчёт, 🛃Гарантия сохранности посылок, ☎️ +998 90-113-44-77 админ',
keywords:
'Cpost express, Авиапочта, Китай, Узбекистан, доставка, срок доставки, минимальный заказ, фото отчёт, гарантия сохранности, посылки, контакты, админ',
img: 'https://cpost-express.uz/logo.jpeg',
};
const seoUz = {
title: "Cpost express: Xitoydan O'zbekistonga aviapochta",
desc: "Cpost express ✈Xitoydan O'zbekistonga aviapoçta, ⏳Yetkazib berish muddati 5-14 kun, 📦Minimal buyurtma 0 kg, 📷Surat maqola, 🛃Poçta xavfsizligi kafolati, ☎️ +998 90-113-44-77 admin",
keywords:
"Cpost express, Aviapoçta, Xitoy, O'zbekiston, yetkazib berish, yetkazib berish muddati, minimal buyurtma, surat maqola, poçta xavfsizligi kafolati, aloqa ma'lumotlari, admin",
img: 'https://cpost-express.uz/logo.jpeg',
};
const seoEn = {
title: 'Cpost express: Air Mail from China to Uzbekistan',
desc: 'Cpost express ✈Air Mail from China to Uzbekistan, ⏳Delivery time 5-14 days, 📦Minimum order 0 kg, 📷Photo report, 🛃Guarantee of parcel safety, ☎️ +998 90-113-44-77 admin',
keywords:
'Cpost express, Air mail, China, Uzbekistan, delivery, delivery time, minimum order, photo report, guarantee of parcel safety, contacts, admin',
img: 'https://cpost-express.uz/logo.jpeg',
};
let seo = seoRu;
switch (siteLanguage) {
case 'ru': {
seo = seoRu;
break;
}
case 'uz': {
seo = seoUz;
break;
}
case 'en': {
seo = seoEn;
break;
}
default: {
seo = seoRu;
}
}
const alternates = [
{
link: `https://cpost-express.uz/ru`,
hreflang: 'ru',
},
{
link: `https://cpost-express.uz/en`,
hreflang: 'en',
},
{
link: `https://cpost-express.uz/uz`,
hreflang: 'uz',
},
];
return (
<html lang={siteLanguage}>
<head>
<title>{seo.title}</title>
<meta name='description' content={seo.desc} />
<meta name='keywords' content={seo.keywords} />
<meta property='og:type' content='website' />
<meta property='og:title' content={seo.title} />
<meta property='og:description' content={seo.desc} />
<meta property='og:url' content={seo.img} />
{alternates
.filter((i, index) => {
if (siteLanguage === 'ru') {
return index !== 0;
}
if (siteLanguage === 'en') {
return index !== 1;
}
if (siteLanguage === 'uz') {
return index !== 2;
}
})
.map(({ link, hreflang }) => {
return <link rel='alternate' href={link} hrefLang={hreflang} key={hreflang} />;
})}
<link rel='preconnect' href='https://fonts.googleapis.com' />
<link rel='preconnect' href='https://fonts.gstatic.com' crossOrigin='' />
<link href='https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap' rel='stylesheet' />
<link href='https://fonts.cdnfonts.com/css/sf-pro-display' rel='stylesheet' />
<meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1' />
<link rel='apple-touch-icon' sizes='180x180' href='/apple-touch-icon.png' />
<link rel='icon' type='image/png' sizes='32x32' href='/favicon-32x32.png' />
<link rel='icon' type='image/png' sizes='16x16' href='/favicon-16x16.png' />
<link rel='manifest' href='/site.webmanifest' />
</head>
<body>
<MainProvider>{children}</MainProvider>
</body>
</html>
);
}
// export async function generateStaticParams() {
// return [
// {
// locale: 'uz',
// },
// {
// locale: 'ru',
// },
// {
// locale: 'en',
// },
// ];
// }

View File

@@ -0,0 +1,95 @@
'use client';
import React, { useState } from 'react';
import { Box, FormControl, FormLabel, Paper, Stack, TextField, Typography } from '@mui/material';
import Container from '@/components/common/Container';
import useInput from '@/hooks/useInput';
import { notifyUnknownError } from '@/services/notification';
import { user_requests } from '@/data/user/user.requests';
import { useAuth } from '@/hooks/useAuth';
import BaseButton from '@/components/ui-kit/BaseButton';
import { useRouter } from 'next/navigation';
import { pageLinks } from '@/helpers/constants';
import { useLocale } from 'next-intl';
import { useMyNavigation } from '@/hooks/useMyNavigation';
type Props = {};
const LoginPage = (props: Props) => {
const locale = useLocale();
const navigation = useMyNavigation();
const { login } = useAuth();
const loginInput = useInput('');
const passInput = useInput('');
const [loading, setLoading] = useState(false);
const onSubmit = async (event: any) => {
event.preventDefault();
if (loginInput.value && passInput.value) {
try {
setLoading(true);
const response = await user_requests.login({
username: loginInput.value,
password: passInput.value,
});
login({ accessToken: response.data.data.accessToken });
navigation.push(pageLinks.dashboard.main.index);
} catch (error) {
notifyUnknownError(error);
} finally {
setLoading(false);
}
}
};
return (
<Box
sx={{
padding: '40px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
minHeight: '100vh',
backgroundColor: '#f1f1f1',
}}
>
<Container>
<Paper
sx={{
p: 5,
maxWidth: 400,
margin: '0 auto',
borderRadius: '16px',
}}
component={'form'}
onSubmit={onSubmit}
>
<Stack direction={'row'} justifyContent={'center'} alignItems={'center'} mb={3}>
<Typography sx={{ fontSize: '32px', fontWeight: 700, color: '#3489e4' }}>
Login
{/* <NextLink href={pageLinks.home}>CPost</NextLink> */}
</Typography>
</Stack>
<Stack spacing={2} mb={2}>
<label>
<FormLabel>Login</FormLabel>
<TextField type='text' name='login' onChange={loginInput.onChange} value={loginInput.value} fullWidth />
</label>
<label>
<FormLabel>Password</FormLabel>
<TextField type='password' name='password' onChange={passInput.onChange} value={passInput.value} fullWidth />
</label>
</Stack>
<BaseButton onClick={onSubmit} loading={loading} fullWidth size='large'>
Send
</BaseButton>
</Paper>
</Container>
</Box>
);
};
export default LoginPage;

View File

@@ -0,0 +1,10 @@
import React from 'react';
import Layout from '@/components/layout/landing-layout';
type Props = {};
const NewsPage = (props: Props) => {
return <Layout>NewsPage</Layout>;
};
export default NewsPage;

10
src/app/[locale]/page.tsx Normal file
View File

@@ -0,0 +1,10 @@
import Layout from '@/components/layout/landing-layout';
import Homepage from '@/routes/public/homepage';
export default function Home() {
return (
<Layout>
<Homepage />
</Layout>
);
}

View File

@@ -0,0 +1,14 @@
.hidden {
opacity: 0;
pointer-events: none;
}
.transition {
transition: all 0.6s ease-out;
}
.social-link {
display: flex;
justify-content: center;
align-items: center;
}

View File

@@ -0,0 +1,18 @@
'use client';
import { type PropsWithChildren, useRef } from 'react';
import type { StoreInterface, StoreType } from './store';
import { initializeStore, Provider } from './store';
// export interface PreloadedStoreInterface extends Pick<StoreInterface, 'lastUpdate'> {}
export interface PreloadedStoreInterface {}
export default function StoreProvider({ children, ...props }: PropsWithChildren<PreloadedStoreInterface>) {
const storeRef = useRef<StoreType>();
if (!storeRef.current) {
storeRef.current = initializeStore(props);
}
return <Provider value={storeRef.current}>{children}</Provider>;
}

59
src/clientStore/store.ts Normal file
View File

@@ -0,0 +1,59 @@
import { createContext, useContext } from 'react';
import { createStore, useStore as useZustandStore } from 'zustand';
import { PreloadedStoreInterface } from './StoreProvider';
export interface StoreInterface {
lastUpdate: number;
light: boolean;
count: number;
tick: (lastUpdate: number) => void;
increment: () => void;
decrement: () => void;
reset: () => void;
}
function getDefaultInitialState() {
return {
lastUpdate: new Date(1970, 1, 1).getTime(),
light: false,
count: 0,
} as const;
}
export type StoreType = ReturnType<typeof initializeStore>;
const storeContext = createContext<StoreType | null>(null);
export const Provider = storeContext.Provider;
export function useStore<T>(selector: (state: StoreInterface) => T) {
const store = useContext(storeContext);
if (!store) throw new Error('Store is missing the provider');
return useZustandStore(store, selector);
}
export function initializeStore(preloadedState: PreloadedStoreInterface) {
return createStore<StoreInterface>((set, get) => ({
...getDefaultInitialState(),
...preloadedState,
tick: lastUpdate =>
set({
lastUpdate,
light: !get().light,
}),
increment: () =>
set({
count: get().count + 1,
}),
decrement: () =>
set({
count: get().count - 1,
}),
reset: () =>
set({
count: getDefaultInitialState().count,
}),
}));
}

View File

@@ -0,0 +1,20 @@
'use client';
import { useEffect, useRef } from 'react';
// https://overreacted.io/making-setinterval-declarative-with-react-hooks/
export default function useInterval(callback: () => void, delay: number | undefined) {
const savedCallback = useRef<typeof callback>();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
const handler = () => savedCallback.current?.();
if (delay !== null) {
const id = setInterval(handler, delay);
return () => clearInterval(id);
}
}, [delay]);
}

View File

@@ -0,0 +1,146 @@
import * as React from 'react';
import { styled, alpha } from '@mui/material/styles';
import Menu, { MenuProps } from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import { CircularProgress, IconButton, PopoverOrigin, SvgIcon, Typography } from '@mui/material';
import { MoreVert } from '@mui/icons-material';
type PlacementType = {
anchorOrigin?: PopoverOrigin;
transformOrigin?: PopoverOrigin;
};
const StyledMenu = styled((props: MenuProps & { placement: Required<PlacementType> }) => (
<Menu elevation={0} anchorOrigin={props.placement.anchorOrigin} transformOrigin={props.placement.transformOrigin} {...props} />
))(({ theme }) => ({
'& .MuiPaper-root': {
borderRadius: 6,
marginTop: theme.spacing(1),
minWidth: 180,
color: theme.palette.mode === 'light' ? 'rgb(55, 65, 81)' : theme.palette.grey[300],
boxShadow:
'rgb(255, 255, 255) 0px 0px 0px 0px, rgba(0, 0, 0, 0.05) 0px 0px 0px 1px, rgba(0, 0, 0, 0.1) 0px 10px 15px -3px, rgba(0, 0, 0, 0.05) 0px 4px 6px -2px',
'& .MuiMenu-list': {
padding: '4px 0',
},
'& .MuiMenuItem-root': {
'& .MuiSvgIcon-root': {
fontSize: 18,
color: theme.palette.text.secondary,
marginRight: theme.spacing(1.5),
},
'&:active': {
backgroundColor: alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity),
},
},
},
}));
type ActionPopMenuProps = {
mainIcon?: React.ReactNode;
buttons: {
icon: React.ReactNode;
label: string;
onClick: () => void;
disabled?: boolean;
loading?: boolean;
dontCloseOnClick?: boolean;
}[];
placement?: PlacementType;
};
export default function ActionPopMenu({ buttons, mainIcon, placement }: ActionPopMenuProps) {
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
event.stopPropagation();
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<div>
<IconButton
id='demo-customized-button'
aria-controls={open ? 'demo-customized-menu' : undefined}
aria-haspopup='true'
aria-expanded={open ? 'true' : undefined}
onClick={handleClick}
color='primary'
>
{mainIcon ? mainIcon : <MoreVert />}
</IconButton>
<StyledMenu
id='demo-customized-menu'
MenuListProps={{
'aria-labelledby': 'demo-customized-button',
}}
anchorEl={anchorEl}
open={open}
onClose={handleClose}
onClick={e => e.stopPropagation()}
placement={{
anchorOrigin: placement?.anchorOrigin ?? {
vertical: 'bottom',
horizontal: 'right',
},
transformOrigin: placement?.transformOrigin ?? {
vertical: 'top',
horizontal: 'right',
},
}}
sx={{
'.MuiMenu-list': {
padding: '4px',
borderRadius: '8px',
},
}}
>
{buttons.map((btn, index) => {
return (
<MenuItem
onClick={event => {
event.stopPropagation();
btn.onClick();
if (!btn.dontCloseOnClick) handleClose();
}}
disableRipple
key={index}
sx={{
padding: '8px 12px',
gap: '8px',
}}
>
{btn.loading ? (
<CircularProgress size={16} />
) : (
<SvgIcon
sx={{
width: '16px',
height: '16px',
padding: 0,
}}
>
{btn.icon}
</SvgIcon>
)}
<Typography
sx={{
fontSize: '14px',
fontWeight: 500,
lineHeight: '16px',
letterSpacing: '-0.4px',
}}
>
{btn.label}
</Typography>
</MenuItem>
);
})}
</StyledMenu>
</div>
);
}

View File

@@ -0,0 +1,150 @@
import BaseButton from '@/components/ui-kit/BaseButton';
import BaseModal from '@/components/ui-kit/BaseModal';
import BaseReactSelect from '@/components/ui-kit/BaseReactSelect';
import { box_requests } from '@/data/box/box.requests';
import { customer_requests } from '@/data/customers/customer.requests';
import { invoice_requests } from '@/data/invoice/invoice.requests';
import { useMyTranslation } from '@/hooks/useMyTranslation';
import useRequest from '@/hooks/useRequest';
import { notifyUnknownError } from '@/services/notification';
import { Box, Stack, Typography, styled } from '@mui/material';
import React, { useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
const StyledBox = styled(Box)`
.title {
color: #000;
font-size: 20px;
font-style: normal;
font-weight: 600;
line-height: 24px;
margin-bottom: 28px;
}
.label {
color: #5d5850;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 24px;
margin-bottom: 8px;
}
`;
type Props = {
onClose: () => void;
open: boolean;
onSuccess: () => void;
defaultValues?: {
clientId?: number;
packetId?: number;
};
};
const AttachInvoiceModal = ({ onClose, open, onSuccess, defaultValues }: Props) => {
const t = useMyTranslation();
const [loading, setLoading] = useState(false);
const getClientsQuery = useRequest(() => customer_requests.getAll(), {
selectData(data) {
return data.data.data.data.map(i => ({ label: i.fullName, value: i.id }));
},
});
const getBoxesQuery = useRequest(() => box_requests.getAll(), {
selectData(data) {
return data.data.data.data.map(i => ({ value: i.id, label: i.name }));
},
});
const clientsList = getClientsQuery.data || [];
const boxesList = getBoxesQuery.data || [];
const {
register,
control,
handleSubmit,
formState: { errors },
} = useForm<{
clientId: number;
packetId: number;
}>({
defaultValues: {
...defaultValues,
},
});
const onSubmit = handleSubmit(async values => {
try {
setLoading(true);
await invoice_requests.create({ ...values });
onSuccess();
} catch (error) {
notifyUnknownError(error);
} finally {
setLoading(false);
}
});
return (
<BaseModal maxWidth='400px' onClose={onClose} open={open}>
<StyledBox component={'form'} onSubmit={onSubmit}>
<Typography className='title'>{t('create_invoice')}</Typography>
<Stack spacing={3.5}>
<Box>
<Typography className='label'>{t('role')}</Typography>
<Controller
name='clientId'
control={control}
render={({ field, fieldState, formState }) => {
return (
<BaseReactSelect
value={clientsList.find(p => p.value === field.value)}
onChange={(newValue: any) => {
field.onChange(newValue.value);
}}
onBlur={field.onBlur}
name={field.name}
options={clientsList}
/>
);
}}
/>
</Box>
<Box>
<Typography className='label'>{t('role')}</Typography>
<Controller
name='packetId'
control={control}
render={({ field, fieldState, formState }) => {
return (
<BaseReactSelect
value={boxesList.find(p => p.value === field.value)}
onChange={(newValue: any) => {
field.onChange(newValue.value);
}}
onBlur={field.onBlur}
name={field.name}
options={boxesList}
/>
);
}}
/>
</Box>
</Stack>
<Stack direction={'row'} justifyContent={'flex-start'} alignItems={'center'} spacing={3}>
<BaseButton colorVariant='blue' type='submit' loading={loading}>
{t('create')}
</BaseButton>
<BaseButton variant='outlined' type='button' colorVariant='blue-outlined' disabled={loading} onClick={onClose}>
{t('cancel')}
</BaseButton>
</Stack>
</StyledBox>
</BaseModal>
);
};
export default AttachInvoiceModal;

View File

@@ -0,0 +1,220 @@
'use client';
import BaseModal from '@/components/ui-kit/BaseModal';
import useInput from '@/hooks/useInput';
import { useMyTranslation } from '@/hooks/useMyTranslation';
import { Box, Grid, Modal, Stack, Typography, styled } from '@mui/material';
import React from 'react';
const StyledBox = styled(Box)`
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
.checkboxes-grid-wrapper {
max-width: 300px;
display: flex;
justify-content: center;
}
.checkboxes-grid {
margin-bottom: 24px;
}
.checkboxes-grid-label {
border-radius: 8px;
background: #fff;
flex-shrink: 0;
border: 1px solid #b2b2b2;
padding: 10px;
display: flex;
align-items: center;
justify-content: center;
width: auto;
cursor: pointer;
width: 64px;
height: 64px;
img {
width: 44px;
height: 44px;
}
&:focus-within,
&:has(input:checked) {
border: 1px solid #2d97ff;
box-shadow: 0 0 0 1px #2d97ff;
}
}
.calc-title {
color: #11579b;
text-align: center;
font-size: 24px;
font-style: normal;
font-weight: 700;
line-height: 28px;
margin-bottom: 8px;
}
.calc-subtitle {
color: #1e1e1e;
text-align: center;
font-size: 16px;
font-style: normal;
font-weight: 500;
margin-bottom: 24px;
}
.calc-input {
border-radius: 5.352px;
border: 1.338px solid rgba(17, 87, 155, 0.6);
background: #fff;
padding: 8px 10px;
width: 100%;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 16.056px;
color: #222;
&::placeholder {
color: #bababa;
}
&--btn {
background-color: #3489e4;
border: 0;
color: #fff;
cursor: pointer;
&:hover {
opacity: 0.8;
}
&:active {
opacity: 0.6;
}
}
}
.price-value {
color: #1f1f1f;
text-align: center;
font-size: 24px;
font-style: normal;
font-weight: 600;
line-height: 26.76px;
}
`;
type Props = {};
const Calculator = (props: Props) => {
const t = useMyTranslation();
const inputValue = useInput('');
const serviceInput = useInput('avia');
let result: string | number = 0;
if (serviceInput.value === 'avia' && !Number.isNaN(Number(inputValue.value))) {
result = (Number(inputValue.value) * 12).toFixed(2);
}
if (serviceInput.value === 'truck' && !Number.isNaN(Number(inputValue.value))) {
result = (Number(inputValue.value) * 9).toFixed(2);
}
return (
<StyledBox>
<Typography className='calc-title' id='modal-modal-title'>
{t('calc_obj.title')}
</Typography>
<Typography className='calc-subtitle' id='modal-modal-description'>
{t('calc_obj.subtitle')}
</Typography>
<Box className='checkboxes-grid-wrapper'>
<Grid container className='checkboxes-grid' justifyContent={'space-between'} spacing={'30px'}>
<Grid item xs={4} md={4}>
<label className='checkboxes-grid-label'>
<img src='/static/images/service1.png' alt='' />
<input
className='visually-hidden'
type='radio'
checked={serviceInput.value === 'avia'}
onChange={serviceInput.onChange}
name='serviceType'
value={'avia'}
/>
</label>
</Grid>
<Grid item xs={4} md={4}>
<label className='checkboxes-grid-label'>
<img src='/static/images/service2.png' alt='' />
<input
className='visually-hidden'
type='radio'
checked={serviceInput.value === 'truck'}
onChange={serviceInput.onChange}
name='serviceType'
value={'truck'}
/>
</label>
</Grid>
{/* <Grid item xs={4} md={4}>
<label className='checkboxes-grid-label'>
<img src='/static/images/service3.png' alt='' />
<input className='visually-hidden' type='radio' name='serviceType' value={'service3'} />
</label>
</Grid> */}
</Grid>
</Box>
<Grid container spacing={1.5} mb={3}>
{/* <Grid item xs={12} md={6}>
<input type='number' placeholder={t('calc_obj.colvo_sht')} className='calc-input' />
</Grid>
<Grid item xs={12} md={6}>
<input type='number' placeholder={t('calc_obj.volume')} className='calc-input' />
</Grid> */}
<Grid item xs={12} md={12}>
<input
value={inputValue.value}
onChange={inputValue.onChange}
type='number'
placeholder={t('calc_obj.weight_of_product')}
className='calc-input'
/>
</Grid>
{/* <Grid item xs={12} md={6}>
<input type='submit' value={t('calc_obj.calc_value')} className='calc-input calc-input--btn' />
</Grid> */}
</Grid>
<Stack direction={'row'} justifyContent={'center'} alignItems={'center'}>
<Typography className='price-value'>
{t('price')} = {result}$
</Typography>
</Stack>
</StyledBox>
);
};
type CalculatorModalProps = {
open: boolean;
onClose: () => void;
};
const CalculatorModal = ({ open, onClose }: CalculatorModalProps) => {
return (
<BaseModal maxWidth='400px' onClose={onClose} open={open}>
<Calculator />
</BaseModal>
);
};
export default Calculator;
export { CalculatorModal };

View File

@@ -0,0 +1 @@
import { default as Calculator, CalculatorModal } from './Calculator';

Some files were not shown because too many files have changed in this diff Show More