init
This commit is contained in:
256
src/app/[locale]/(views)/profile-edit/page.tsx
Normal file
256
src/app/[locale]/(views)/profile-edit/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import IUserForm from './component/IUserForm';
|
||||
|
||||
const Home: React.FC = () => {
|
||||
|
||||
return <IUserForm />;
|
||||
};
|
||||
|
||||
export default Home;
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
72
src/app/[locale]/(views)/profile/(views)/settings/page.tsx
Normal file
72
src/app/[locale]/(views)/profile/(views)/settings/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
378
src/app/[locale]/(views)/profile/(views)/user-data/page.tsx
Normal file
378
src/app/[locale]/(views)/profile/(views)/user-data/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
89
src/app/[locale]/(views)/profile/components/IAboutUser.tsx
Normal file
89
src/app/[locale]/(views)/profile/components/IAboutUser.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
37
src/app/[locale]/(views)/profile/components/ISidebar.tsx
Normal file
37
src/app/[locale]/(views)/profile/components/ISidebar.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
||||
25
src/app/[locale]/(views)/profile/layout.tsx
Normal file
25
src/app/[locale]/(views)/profile/layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
150
src/app/[locale]/(views)/profile/page.tsx
Normal file
150
src/app/[locale]/(views)/profile/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
5
src/app/[locale]/dashboard/customers/page.tsx
Normal file
5
src/app/[locale]/dashboard/customers/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import DashboardClientsPage from '@/routes/private/clients/DashboardClientsPage';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardClientsPage />;
|
||||
}
|
||||
5
src/app/[locale]/dashboard/items/page.tsx
Normal file
5
src/app/[locale]/dashboard/items/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import DashboardGoodsPage from '@/routes/private/items/DashboardItemsPage';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardGoodsPage />;
|
||||
}
|
||||
5
src/app/[locale]/dashboard/layout.tsx
Normal file
5
src/app/[locale]/dashboard/layout.tsx
Normal 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>;
|
||||
}
|
||||
9
src/app/[locale]/dashboard/loading.tsx
Normal file
9
src/app/[locale]/dashboard/loading.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
5
src/app/[locale]/dashboard/main/page.tsx
Normal file
5
src/app/[locale]/dashboard/main/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import DashboardHome from '@/routes/private/dashboard-home';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardHome />;
|
||||
}
|
||||
9
src/app/[locale]/dashboard/packets/create/loading.tsx
Normal file
9
src/app/[locale]/dashboard/packets/create/loading.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
22
src/app/[locale]/dashboard/packets/create/page.tsx
Normal file
22
src/app/[locale]/dashboard/packets/create/page.tsx
Normal 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} />;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import DashboardEditBoxPage from '@/routes/private/boxes-create/DashboardEditBox';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardEditBoxPage />;
|
||||
}
|
||||
5
src/app/[locale]/dashboard/packets/page.tsx
Normal file
5
src/app/[locale]/dashboard/packets/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import DashboardBoxesPage from '@/routes/private/boxes/DashboardBoxesPage';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardBoxesPage />;
|
||||
}
|
||||
5
src/app/[locale]/dashboard/page.tsx
Normal file
5
src/app/[locale]/dashboard/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import DashboardHome from '@/routes/private/dashboard-home';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardHome />;
|
||||
}
|
||||
9
src/app/[locale]/dashboard/parties/create/loading.tsx
Normal file
9
src/app/[locale]/dashboard/parties/create/loading.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
5
src/app/[locale]/dashboard/parties/create/page.tsx
Normal file
5
src/app/[locale]/dashboard/parties/create/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import DashboardCreatePartyPage from '@/routes/private/parties-create/DashboardCreatePartyPage';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardCreatePartyPage />;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import DashboardEditPartyPage from '@/routes/private/parties-create/DashboardEditPartyPage';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardEditPartyPage />;
|
||||
}
|
||||
5
src/app/[locale]/dashboard/parties/page.tsx
Normal file
5
src/app/[locale]/dashboard/parties/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import DashboardPartiesPage from '@/routes/private/parties/DashboardPartiesPage';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardPartiesPage />;
|
||||
}
|
||||
5
src/app/[locale]/dashboard/staffs/page.tsx
Normal file
5
src/app/[locale]/dashboard/staffs/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import DashboardStaffsPage from '@/routes/private/staffs/DashboardStaffsPage';
|
||||
|
||||
export default function Home() {
|
||||
return <DashboardStaffsPage />;
|
||||
}
|
||||
82
src/app/[locale]/globals.css
Normal file
82
src/app/[locale]/globals.css
Normal 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
133
src/app/[locale]/layout.tsx
Normal 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',
|
||||
// },
|
||||
// ];
|
||||
// }
|
||||
95
src/app/[locale]/login/page.tsx
Normal file
95
src/app/[locale]/login/page.tsx
Normal 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;
|
||||
10
src/app/[locale]/news/page.tsx
Normal file
10
src/app/[locale]/news/page.tsx
Normal 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
10
src/app/[locale]/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
14
src/app/[locale]/styles.css
Normal file
14
src/app/[locale]/styles.css
Normal 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;
|
||||
}
|
||||
18
src/clientStore/StoreProvider.tsx
Normal file
18
src/clientStore/StoreProvider.tsx
Normal 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
59
src/clientStore/store.ts
Normal 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,
|
||||
}),
|
||||
}));
|
||||
}
|
||||
20
src/clientStore/useInterval.ts
Normal file
20
src/clientStore/useInterval.ts
Normal 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]);
|
||||
}
|
||||
146
src/components/common/ActionPopMenu/index.tsx
Normal file
146
src/components/common/ActionPopMenu/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
150
src/components/common/AttachInvoiceModal/index.tsx
Normal file
150
src/components/common/AttachInvoiceModal/index.tsx
Normal 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;
|
||||
220
src/components/common/Calculator/Calculator.tsx
Normal file
220
src/components/common/Calculator/Calculator.tsx
Normal 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 };
|
||||
1
src/components/common/Calculator/index.ts
Normal file
1
src/components/common/Calculator/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
import { default as Calculator, CalculatorModal } from './Calculator';
|
||||
268
src/components/common/CheckOrder/CheckOrder.tsx
Normal file
268
src/components/common/CheckOrder/CheckOrder.tsx
Normal file
@@ -0,0 +1,268 @@
|
||||
'use client';
|
||||
|
||||
import CheckOrderResultBox from '@/components/common/CheckOrderResultModal/CheckOrderResultModal';
|
||||
import BaseModal from '@/components/ui-kit/BaseModal';
|
||||
import { Order } from '@/data/order/order.model';
|
||||
import { order_requests } from '@/data/order/order.requests';
|
||||
import useInput from '@/hooks/useInput';
|
||||
import { useMyTranslation } from '@/hooks/useMyTranslation';
|
||||
import { notifyError, notifyUnknownError } from '@/services/notification';
|
||||
import { Box, CircularProgress, Grid, Modal, Stack, SvgIcon, Typography, styled } from '@mui/material';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const StyledBox = styled(Box)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.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;
|
||||
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
|
||||
img {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
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;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.calc-subtitle {
|
||||
color: #1e1e1e;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.delivery_type_label {
|
||||
padding: 5px 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #709ac3;
|
||||
background: #fff;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: #3489e4;
|
||||
|
||||
span {
|
||||
color: #3489e4;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
&:focus-within,
|
||||
&:has(input:checked) {
|
||||
border: 1px solid #2d97ff;
|
||||
box-shadow: 0 0 0 1px #2d97ff;
|
||||
}
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
display: flex;
|
||||
height: 48px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #11579b99;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
padding: 8px 12px;
|
||||
outline: none;
|
||||
border-radius: 0 6px 6px 0;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
box-shadow: 0 0 1px 1px inset #2d97ff;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
border-right: 1px solid #11579b99;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
padding: 7px;
|
||||
border-radius: 6px;
|
||||
border: 1.38px solid rgba(17, 87, 155, 0.2);
|
||||
background: #3487e1;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {};
|
||||
|
||||
const CheckOrder = (props: Props) => {
|
||||
const t = useMyTranslation();
|
||||
const inputValue = useInput('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
const [order, setOrder] = useState<Order | null>(null);
|
||||
|
||||
const onSubmit = async (event: any) => {
|
||||
event.preventDefault();
|
||||
if (!inputValue.value.trim() || loading) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(false);
|
||||
|
||||
const response = await order_requests.check({ trekId: inputValue.value });
|
||||
if (response.data.data[0]) {
|
||||
setOrder(response.data.data[0]);
|
||||
// inputValue.setValue('');
|
||||
} else {
|
||||
setOrder(null);
|
||||
setError(true);
|
||||
}
|
||||
} catch (error) {
|
||||
setError(true);
|
||||
// notifyUnknownError(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledBox component={'form'} onSubmit={onSubmit}>
|
||||
<Typography className='calc-title'>{t('check_order_obj.title')}</Typography>
|
||||
<Typography className='calc-subtitle'>{t('check_order_obj.subtitle')}</Typography>
|
||||
|
||||
<Stack direction={'row'} spacing={1} mb={3}>
|
||||
<Box className='input-wrapper'>
|
||||
<Box className='input-wrapper__icon'>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='25' height='24' viewBox='0 0 25 24' fill='none'>
|
||||
<path
|
||||
d='M2.19727 4H4.19727V20H2.19727V4ZM6.19727 4H7.19727V20H6.19727V4ZM8.19727 4H10.1973V20H8.19727V4ZM11.1973 4H13.1973V20H11.1973V4ZM14.1973 4H16.1973V20H14.1973V4ZM17.1973 4H18.1973V20H17.1973V4ZM19.1973 4H22.1973V20H19.1973V4Z'
|
||||
fill='black'
|
||||
/>
|
||||
</svg>
|
||||
</Box>
|
||||
<input
|
||||
type='text'
|
||||
value={inputValue.value}
|
||||
onChange={inputValue.onChange}
|
||||
placeholder={t('check_order_obj.enter_check_code')}
|
||||
/>
|
||||
</Box>
|
||||
<button className='search-btn' disabled={loading}>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='19' height='18' viewBox='0 0 19 18' fill='none'>
|
||||
<path
|
||||
d='M13.5779 12.4626L16.79 15.6746L15.7293 16.7353L12.5173 13.5233C11.3624 14.4473 9.89769 15 8.30469 15C4.57869 15 1.55469 11.976 1.55469 8.25C1.55469 4.524 4.57869 1.5 8.30469 1.5C12.0307 1.5 15.0547 4.524 15.0547 8.25C15.0547 9.843 14.5019 11.3077 13.5779 12.4626ZM12.0732 11.9061C12.9903 10.9609 13.5547 9.6717 13.5547 8.25C13.5547 5.34938 11.2053 3 8.30469 3C5.40406 3 3.05469 5.34938 3.05469 8.25C3.05469 11.1506 5.40406 13.5 8.30469 13.5C9.72639 13.5 11.0156 12.9356 11.9608 12.0185L12.0732 11.9061Z'
|
||||
fill='white'
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Stack>
|
||||
|
||||
{/* <Typography className='calc-subtitle'>{t('check_order_obj.select_delivery_type')}</Typography> */}
|
||||
<Stack direction={'row'} spacing={1} display={'none'}>
|
||||
<label className='delivery_type_label'>
|
||||
<SvgIcon>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 18 18' fill='none'>
|
||||
<path
|
||||
d='M11.9 2.8353C12.5948 2.27391 13.373 2.23983 14.2233 2.25175C14.6631 2.25791 14.883 2.261 15.0594 2.32941C15.3393 2.43795 15.5621 2.66073 15.6706 2.94061C15.739 3.11701 15.7421 3.3369 15.7483 3.77668C15.7602 4.62695 15.7261 5.40515 15.1647 6.09995C14.692 6.68503 13.8794 6.99076 13.4885 7.63658C13.19 8.12963 13.329 8.64023 13.4573 9.16568L14.4171 13.0997C14.6084 13.8836 14.4629 14.3861 13.9147 14.9343C13.623 15.226 13.3804 15.2112 13.1438 14.8343L10.247 10.2187L8.86358 11.3183C8.36198 11.7171 8.11118 11.9165 7.9794 12.197C7.6707 12.8541 7.86668 13.841 7.87665 14.5535C7.8822 14.9472 7.54193 15.7233 7.06043 15.7495C6.76317 15.7657 6.66212 15.4101 6.56611 15.1919L5.64176 13.0908C5.4207 12.5884 5.41164 12.5793 4.90917 12.3583L2.80811 11.4339C2.58989 11.3379 2.23436 11.2368 2.25053 10.9396C2.27674 10.4581 3.05277 10.1178 3.44652 10.1234C4.15899 10.1333 5.14592 10.3293 5.80301 10.0206C6.08357 9.88883 6.28293 9.63803 6.68166 9.13643L7.78133 7.75298L3.16577 4.85617C2.78884 4.6196 2.77404 4.37698 3.06566 4.08536C3.61388 3.53715 4.11638 3.39161 4.90033 3.58289L8.83433 4.54277C9.35978 4.67099 9.87038 4.81007 10.3634 4.51155C11.0093 4.12058 11.315 3.30806 11.9 2.8353Z'
|
||||
stroke='currentColor'
|
||||
stroke-width='1.5'
|
||||
stroke-linecap='round'
|
||||
stroke-linejoin='round'
|
||||
/>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
<span>{t('check_order_obj.delivery_type_avia')}</span>
|
||||
<input className='visually-hidden' type='radio' name='deliveryType' value={'avia'} />
|
||||
</label>
|
||||
<label className='delivery_type_label'>
|
||||
<SvgIcon>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 18 18' fill='none'>
|
||||
<path
|
||||
d='M14.625 13.125C14.625 14.1605 13.7855 15 12.75 15C11.7145 15 10.875 14.1605 10.875 13.125C10.875 12.0895 11.7145 11.25 12.75 11.25C13.7855 11.25 14.625 12.0895 14.625 13.125Z'
|
||||
stroke='currentColor'
|
||||
stroke-width='1.5'
|
||||
/>
|
||||
<path
|
||||
d='M7.125 13.125C7.125 14.1605 6.28553 15 5.25 15C4.21447 15 3.375 14.1605 3.375 13.125C3.375 12.0895 4.21447 11.25 5.25 11.25C6.28553 11.25 7.125 12.0895 7.125 13.125Z'
|
||||
stroke='currentColor'
|
||||
stroke-width='1.5'
|
||||
/>
|
||||
<path
|
||||
d='M10.875 13.125H7.125M14.625 13.125H15.1974C15.3623 13.125 15.4448 13.125 15.5141 13.1164C16.0252 13.0527 16.4277 12.6502 16.4914 12.1391C16.5 12.0698 16.5 11.9873 16.5 11.8224V9.75C16.5 7.05761 14.3174 4.875 11.625 4.875M1.5 3H9C10.0606 3 10.591 3 10.9205 3.32951C11.25 3.65901 11.25 4.18934 11.25 5.25V11.625M1.5 9.5625V11.25C1.5 11.9509 1.5 12.3014 1.65072 12.5625C1.74946 12.7335 1.89148 12.8755 2.0625 12.9742C2.32356 13.125 2.67403 13.125 3.375 13.125'
|
||||
stroke='currentColor'
|
||||
stroke-width='1.5'
|
||||
stroke-linecap='round'
|
||||
stroke-linejoin='round'
|
||||
/>
|
||||
<path
|
||||
d='M1.5 5.25H6M1.5 7.5H4.5'
|
||||
stroke='currentColor'
|
||||
stroke-width='1.5'
|
||||
stroke-linecap='round'
|
||||
stroke-linejoin='round'
|
||||
/>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
<span>{t('check_order_obj.delivery_type_car')}</span>
|
||||
<input className='visually-hidden' type='radio' name='deliveryType' value={'car'} />
|
||||
</label>
|
||||
</Stack>
|
||||
|
||||
{loading ? <CircularProgress /> : error ? <Box>{t('not_found')}</Box> : order ? <CheckOrderResultBox order={order} /> : ''}
|
||||
</StyledBox>
|
||||
);
|
||||
};
|
||||
|
||||
type CheckOrderModalProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const CheckOrderModal = ({ open, onClose }: CheckOrderModalProps) => {
|
||||
return (
|
||||
<BaseModal open={open} onClose={onClose} maxWidth='400px'>
|
||||
<CheckOrder />
|
||||
</BaseModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default CheckOrder;
|
||||
export { CheckOrderModal };
|
||||
1
src/components/common/CheckOrder/index.ts
Normal file
1
src/components/common/CheckOrder/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default, CheckOrderModal } from './CheckOrder';
|
||||
@@ -0,0 +1,243 @@
|
||||
'use client';
|
||||
|
||||
import BaseModal from '@/components/ui-kit/BaseModal';
|
||||
import { Order } from '@/data/order/order.model';
|
||||
import { PartyStatus } from '@/data/party/party.model';
|
||||
import { useMyTranslation } from '@/hooks/useMyTranslation';
|
||||
import { getBoxStatusStyles } from '@/theme/getStatusBoxStyles';
|
||||
import { Box, Grid, Modal, Stack, SvgIcon, 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-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;
|
||||
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
|
||||
img {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
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;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.calc-subtitle {
|
||||
color: #1e1e1e;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.delivery_type_label {
|
||||
padding: 5px 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #709ac3;
|
||||
background: #fff;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: #3489e4;
|
||||
|
||||
span {
|
||||
color: #3489e4;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
&:focus-within,
|
||||
&:has(input:checked) {
|
||||
border: 1px solid #2d97ff;
|
||||
box-shadow: 0 0 0 1px #2d97ff;
|
||||
}
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
display: flex;
|
||||
height: 48px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #11579b99;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
padding: 8px 12px;
|
||||
outline: none;
|
||||
border-radius: 0 6px 6px 0;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
box-shadow: 0 0 1px 1px inset #2d97ff;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
border-right: 1px solid #11579b99;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
padding: 7px;
|
||||
border-radius: 6px;
|
||||
border: 1.38px solid rgba(17, 87, 155, 0.2);
|
||||
background: #3487e1;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
order: Order;
|
||||
};
|
||||
|
||||
const CheckOrderResultBox = ({ order }: Props) => {
|
||||
const t = useMyTranslation();
|
||||
|
||||
const calcData = (event: any) => {
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledBox component={'form'} onSubmit={calcData}>
|
||||
<Typography variant='h5' fontWeight={600} marginRight={'auto'}>
|
||||
{t('product_name')}: {order.name}
|
||||
</Typography>
|
||||
<Grid container spacing={2} mb={2}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography>{t('cargo_id')}</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
border: '1px solid #D8D8D8',
|
||||
padding: '12px',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
>
|
||||
{order.cargoId}
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography>{t('party_name')}</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
border: '1px solid #D8D8D8',
|
||||
padding: '12px',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
>
|
||||
{order.partyName}
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography>{t('track_id')}</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
border: '1px solid #D8D8D8',
|
||||
padding: '12px',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
>
|
||||
{order.trekId}
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography>{t('quantity')}</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
border: '1px solid #D8D8D8',
|
||||
padding: '12px',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
>
|
||||
{order.amount}
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography>{t('weight')}</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
width: '100%',
|
||||
border: '1px solid #D8D8D8',
|
||||
padding: '12px',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
>
|
||||
{order.weight}
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Stack spacing={1} alignSelf={'start'}>
|
||||
{order.dates.map(({ date, status }, index) => {
|
||||
return (
|
||||
<Stack direction={'row'} alignItems={'center'} spacing={2} key={status + index}>
|
||||
<span>{date}</span>:<Typography sx={{ ...getBoxStatusStyles(status as PartyStatus) }}>{t(status)}</Typography>
|
||||
</Stack>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</StyledBox>
|
||||
);
|
||||
};
|
||||
|
||||
type CheckOrderModalProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
result: Order;
|
||||
};
|
||||
|
||||
const CheckOrderResultModal = ({ open, onClose, result }: CheckOrderModalProps) => {
|
||||
return (
|
||||
<BaseModal open={open} onClose={onClose} maxWidth='600px'>
|
||||
<CheckOrderResultBox order={result} />
|
||||
</BaseModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default CheckOrderResultBox;
|
||||
export { CheckOrderResultModal };
|
||||
1
src/components/common/CheckOrderResultModal/index.ts
Normal file
1
src/components/common/CheckOrderResultModal/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default, CheckOrderResultModal } from './CheckOrderResultModal';
|
||||
12
src/components/common/Container/index.tsx
Normal file
12
src/components/common/Container/index.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React, { Attributes, HTMLAttributes } from 'react';
|
||||
import { Container as MuiContainer } from '@mui/material';
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode;
|
||||
} & HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
const Container = (props: Props) => {
|
||||
return <MuiContainer {...props}>{props.children}</MuiContainer>;
|
||||
};
|
||||
|
||||
export default Container;
|
||||
89
src/components/common/Icons/index.tsx
Normal file
89
src/components/common/Icons/index.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
export function IconExit() {
|
||||
return (
|
||||
<svg viewBox='0 0 600 600' width='19' height='19' xmlns='http://www.w3.org/2000/svg' style={{ fill: 'currentColor' }}>
|
||||
<path d='M 130 0 C 58.672245 0 0 58.672245 0 130 L 0 470 C 0 541.32776 58.672245 600 130 600 L 301.57812 600 C 367.83331 600 423.13643 549.36696 430.67188 485 L 349.43555 485 C 343.32179 505.66026 324.7036 520 301.57812 520 L 130 520 C 101.60826 520 80 498.39174 80 470 L 80 130 C 80 101.60826 101.60826 80 130 80 L 301.57812 80 C 324.7036 80 343.32179 94.339739 349.43555 115 L 430.67188 115 C 423.13642 50.633038 367.83331 0 301.57812 0 L 130 0 z' />
|
||||
<path d='m 476.86328,179.99911 a 40,40 0 0 0 -28.28516,11.71484 40,40 0 0 0 0,56.57032 l 11.71485,11.71484 H 163.72656 a 40,40 0 0 0 -40,40 40,40 0 0 0 40,40 h 296.56641 l -11.71485,11.71484 a 40,40 0 0 0 0,56.57032 40,40 0 0 0 56.57032,0 l 72.79101,-72.79102 A 40,40 0 0 0 600,299.99911 40,40 0 0 0 577.5293,264.09481 l -72.38086,-72.38086 a 40,40 0 0 0 -28.28516,-11.71484 z' />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconProducts({ color = '#333' }: { color?: string }) {
|
||||
return (
|
||||
<svg width='22' height='22' viewBox='0 0 24 24' id='meteor-icon-kit__solid-products' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M10 7H21C22.6569 7 24 8.34315 24 10V21C24 22.6569 22.6569 24 21 24H10C8.34315 24 7 22.6569 7 21V10C7 8.34315 8.34315 7 10 7ZM17 5H10C7.23858 5 5 7.23858 5 10V17H3C1.34315 17 0 15.6569 0 14V3C0 1.34315 1.34315 0 3 0H14C15.6569 0 17 1.34315 17 3V5Z'
|
||||
// fill='#758CA3'
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconPage({ color = '#333' }: { color?: string }) {
|
||||
return (
|
||||
<svg
|
||||
fill={color}
|
||||
version='1.1'
|
||||
id='Capa_1'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
xmlnsXlink='http://www.w3.org/1999/xlink'
|
||||
width='22'
|
||||
height='22'
|
||||
viewBox='0 0 49.167 49.167'
|
||||
xmlSpace='preserve'
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
d='M31.667,13.167c-2.209,0-4-1.791-4-4V0H14.833c-4.418,0-8,3.582-8,8v33.167c0,4.418,3.582,8,8,8h19.5c4.419,0,8-3.582,8-8
|
||||
v-28H31.667z M24.583,32.042H14.75c-0.829,0-1.5-0.672-1.5-1.5s0.671-1.5,1.5-1.5h9.833c0.828,0,1.5,0.672,1.5,1.5
|
||||
S25.412,32.042,24.583,32.042z M34.125,26.167H14.75c-0.829,0-1.5-0.671-1.5-1.5s0.671-1.5,1.5-1.5h19.375
|
||||
c0.828,0,1.5,0.671,1.5,1.5S34.954,26.167,34.125,26.167z M34.125,20.292H14.75c-0.829,0-1.5-0.671-1.5-1.5s0.671-1.5,1.5-1.5
|
||||
h19.375c0.828,0,1.5,0.671,1.5,1.5S34.954,20.292,34.125,20.292z M30.459,7.776V2.847c0-1.215,0.751-2.251,1.813-2.678l9.77,7.815
|
||||
c-0.107,1.498-1.346,2.683-2.87,2.683h-5.821C31.753,10.667,30.459,9.373,30.459,7.776z'
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconSettings({ color = '#333' }: { color?: string }) {
|
||||
return (
|
||||
<svg width='22' height='22' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M14.2788 2.15224C13.9085 2 13.439 2 12.5 2C11.561 2 11.0915 2 10.7212 2.15224C10.2274 2.35523 9.83509 2.74458 9.63056 3.23463C9.53719 3.45834 9.50065 3.7185 9.48635 4.09799C9.46534 4.65568 9.17716 5.17189 8.69017 5.45093C8.20318 5.72996 7.60864 5.71954 7.11149 5.45876C6.77318 5.2813 6.52789 5.18262 6.28599 5.15102C5.75609 5.08178 5.22018 5.22429 4.79616 5.5472C4.47814 5.78938 4.24339 6.1929 3.7739 6.99993C3.30441 7.80697 3.06967 8.21048 3.01735 8.60491C2.94758 9.1308 3.09118 9.66266 3.41655 10.0835C3.56506 10.2756 3.77377 10.437 4.0977 10.639C4.57391 10.936 4.88032 11.4419 4.88029 12C4.88026 12.5581 4.57386 13.0639 4.0977 13.3608C3.77372 13.5629 3.56497 13.7244 3.41645 13.9165C3.09108 14.3373 2.94749 14.8691 3.01725 15.395C3.06957 15.7894 3.30432 16.193 3.7738 17C4.24329 17.807 4.47804 18.2106 4.79606 18.4527C5.22008 18.7756 5.75599 18.9181 6.28589 18.8489C6.52778 18.8173 6.77305 18.7186 7.11133 18.5412C7.60852 18.2804 8.2031 18.27 8.69012 18.549C9.17714 18.8281 9.46533 19.3443 9.48635 19.9021C9.50065 20.2815 9.53719 20.5417 9.63056 20.7654C9.83509 21.2554 10.2274 21.6448 10.7212 21.8478C11.0915 22 11.561 22 12.5 22C13.439 22 13.9085 22 14.2788 21.8478C14.7726 21.6448 15.1649 21.2554 15.3694 20.7654C15.4628 20.5417 15.4994 20.2815 15.5137 19.902C15.5347 19.3443 15.8228 18.8281 16.3098 18.549C16.7968 18.2699 17.3914 18.2804 17.8886 18.5412C18.2269 18.7186 18.4721 18.8172 18.714 18.8488C19.2439 18.9181 19.7798 18.7756 20.2038 18.4527C20.5219 18.2105 20.7566 17.807 21.2261 16.9999C21.6956 16.1929 21.9303 15.7894 21.9827 15.395C22.0524 14.8691 21.9088 14.3372 21.5835 13.9164C21.4349 13.7243 21.2262 13.5628 20.9022 13.3608C20.4261 13.0639 20.1197 12.558 20.1197 11.9999C20.1197 11.4418 20.4261 10.9361 20.9022 10.6392C21.2263 10.4371 21.435 10.2757 21.5836 10.0835C21.9089 9.66273 22.0525 9.13087 21.9828 8.60497C21.9304 8.21055 21.6957 7.80703 21.2262 7C20.7567 6.19297 20.522 5.78945 20.2039 5.54727C19.7799 5.22436 19.244 5.08185 18.7141 5.15109C18.4722 5.18269 18.2269 5.28136 17.8887 5.4588C17.3915 5.71959 16.7969 5.73002 16.3099 5.45096C15.8229 5.17191 15.5347 4.65566 15.5136 4.09794C15.4993 3.71848 15.4628 3.45833 15.3694 3.23463C15.1649 2.74458 14.7726 2.35523 14.2788 2.15224ZM12.5 15C14.1695 15 15.5228 13.6569 15.5228 12C15.5228 10.3431 14.1695 9 12.5 9C10.8305 9 9.47716 10.3431 9.47716 12C9.47716 13.6569 10.8305 15 12.5 15Z'
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconCard({ color = '#333' }: { color?: string }) {
|
||||
return (
|
||||
<svg width='22' height='22' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M16 2H0V14H16V2ZM5 10.5C6.38071 10.5 7.5 9.38071 7.5 8C7.5 6.61929 6.38071 5.5 5 5.5C3.61929 5.5 2.5 6.61929 2.5 8C2.5 9.38071 3.61929 10.5 5 10.5ZM10 5H14V7H10V5ZM14 9H10V11H14V9Z'
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function IconCopy() {
|
||||
return (
|
||||
<svg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path
|
||||
opacity='0.6'
|
||||
fillRule='evenodd'
|
||||
clipRule='evenodd'
|
||||
d='M15 1.25H10.9436C9.10583 1.24998 7.65019 1.24997 6.51098 1.40314C5.33856 1.56076 4.38961 1.89288 3.64124 2.64124C2.89288 3.38961 2.56076 4.33856 2.40314 5.51098C2.24997 6.65019 2.24998 8.10582 2.25 9.94357V16C2.25 17.8722 3.62205 19.424 5.41551 19.7047C5.55348 20.4687 5.81753 21.1208 6.34835 21.6517C6.95027 22.2536 7.70814 22.5125 8.60825 22.6335C9.47522 22.75 10.5775 22.75 11.9451 22.75H15.0549C16.4225 22.75 17.5248 22.75 18.3918 22.6335C19.2919 22.5125 20.0497 22.2536 20.6517 21.6517C21.2536 21.0497 21.5125 20.2919 21.6335 19.3918C21.75 18.5248 21.75 17.4225 21.75 16.0549V10.9451C21.75 9.57754 21.75 8.47522 21.6335 7.60825C21.5125 6.70814 21.2536 5.95027 20.6517 5.34835C20.1208 4.81753 19.4687 4.55348 18.7047 4.41551C18.424 2.62205 16.8722 1.25 15 1.25ZM17.1293 4.27117C16.8265 3.38623 15.9876 2.75 15 2.75H11C9.09318 2.75 7.73851 2.75159 6.71085 2.88976C5.70476 3.02502 5.12511 3.27869 4.7019 3.7019C4.27869 4.12511 4.02502 4.70476 3.88976 5.71085C3.75159 6.73851 3.75 8.09318 3.75 10V16C3.75 16.9876 4.38624 17.8265 5.27117 18.1293C5.24998 17.5194 5.24999 16.8297 5.25 16.0549V10.9451C5.24998 9.57754 5.24996 8.47522 5.36652 7.60825C5.48754 6.70814 5.74643 5.95027 6.34835 5.34835C6.95027 4.74643 7.70814 4.48754 8.60825 4.36652C9.47522 4.24996 10.5775 4.24998 11.9451 4.25H15.0549C15.8297 4.24999 16.5194 4.24998 17.1293 4.27117ZM7.40901 6.40901C7.68577 6.13225 8.07435 5.9518 8.80812 5.85315C9.56347 5.75159 10.5646 5.75 12 5.75H15C16.4354 5.75 17.4365 5.75159 18.1919 5.85315C18.9257 5.9518 19.3142 6.13225 19.591 6.40901C19.8678 6.68577 20.0482 7.07435 20.1469 7.80812C20.2484 8.56347 20.25 9.56458 20.25 11V16C20.25 17.4354 20.2484 18.4365 20.1469 19.1919C20.0482 19.9257 19.8678 20.3142 19.591 20.591C19.3142 20.8678 18.9257 21.0482 18.1919 21.1469C17.4365 21.2484 16.4354 21.25 15 21.25H12C10.5646 21.25 9.56347 21.2484 8.80812 21.1469C8.07435 21.0482 7.68577 20.8678 7.40901 20.591C7.13225 20.3142 6.9518 19.9257 6.85315 19.1919C6.75159 18.4365 6.75 17.4354 6.75 16V11C6.75 9.56458 6.75159 8.56347 6.85315 7.80812C6.9518 7.07435 7.13225 6.68577 7.40901 6.40901Z'
|
||||
fill='#000'
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
164
src/components/common/LanguageSelect/index.tsx
Normal file
164
src/components/common/LanguageSelect/index.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
import ReactSelect from 'react-select';
|
||||
import { Icon, Stack } from '@mui/material';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React from 'react';
|
||||
|
||||
type Props = {};
|
||||
|
||||
const LanguageSelect = (props: Props) => {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const changeLanguage = (lng: string) => {
|
||||
router.push(`/${lng}/${pathname.substring(3)}`, { scroll: false });
|
||||
};
|
||||
|
||||
const options = [
|
||||
{
|
||||
label: (
|
||||
<Stack direction={'row'} alignItems={'center'}>
|
||||
<Icon
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justiftContent: 'center',
|
||||
borderRadius: '50%',
|
||||
img: {
|
||||
objectPosition: 'center',
|
||||
objectFit: 'contain',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<img src={getLanguageImage('uz')} alt='' />
|
||||
</Icon>
|
||||
<span>UZ</span>
|
||||
</Stack>
|
||||
),
|
||||
value: 'uz',
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<Stack direction={'row'} alignItems={'center'}>
|
||||
<Icon
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justiftContent: 'center',
|
||||
borderRadius: '50%',
|
||||
img: {
|
||||
objectPosition: 'center',
|
||||
objectFit: 'contain',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<img src={getLanguageImage('ru')} alt='' />
|
||||
</Icon>
|
||||
<span>RU</span>
|
||||
</Stack>
|
||||
),
|
||||
value: 'ru',
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<Stack direction={'row'} alignItems={'center'}>
|
||||
<Icon
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justiftContent: 'center',
|
||||
borderRadius: '50%',
|
||||
img: {
|
||||
objectPosition: 'center',
|
||||
objectFit: 'contain',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<img src={getLanguageImage('en')} alt='' />
|
||||
</Icon>
|
||||
<span>EN</span>
|
||||
</Stack>
|
||||
),
|
||||
value: 'en',
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<Stack direction={'row'} alignItems={'center'}>
|
||||
<Icon
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justiftContent: 'center',
|
||||
borderRadius: '50%',
|
||||
img: {
|
||||
objectPosition: 'center',
|
||||
objectFit: 'contain',
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<img src={getLanguageImage('cn')} alt='' />
|
||||
</Icon>
|
||||
<span>中國人</span>
|
||||
</Stack>
|
||||
),
|
||||
value: 'cn',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ReactSelect
|
||||
options={options}
|
||||
defaultValue={options[0]}
|
||||
styles={{
|
||||
control(base, props) {
|
||||
return {
|
||||
...base,
|
||||
border: props.isFocused ? '2px solid #3489E4' : '2px solid #D8D8D8',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0',
|
||||
};
|
||||
},
|
||||
valueContainer(base, props) {
|
||||
return {
|
||||
...base,
|
||||
// border: '1px solid #D8D8D8',
|
||||
};
|
||||
},
|
||||
indicatorSeparator(base, props) {
|
||||
return {
|
||||
display: 'none',
|
||||
};
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguageSelect;
|
||||
|
||||
function getLanguageImage(lang: string) {
|
||||
switch (lang) {
|
||||
case 'uz': {
|
||||
return '/static/icons/uz-flag.png';
|
||||
}
|
||||
case 'en': {
|
||||
return '/static/icons/en-flag.png';
|
||||
}
|
||||
case 'ru': {
|
||||
return '/static/icons/ru-flag.webp';
|
||||
}
|
||||
case 'cn': {
|
||||
return '/static/icons/cn-flag.png';
|
||||
}
|
||||
default: {
|
||||
return '/static/icons/uz-flag.png';
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/components/common/Loader/index.tsx
Normal file
18
src/components/common/Loader/index.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { CircularProgress, Stack } from '@mui/material';
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
size?: number | string;
|
||||
p?: number;
|
||||
color?: string;
|
||||
};
|
||||
|
||||
const Loader = ({ size, p, color }: Props) => {
|
||||
return (
|
||||
<Stack direction={'row'} alignItems={'center'} justifyContent={'center'} p={p}>
|
||||
<CircularProgress size={size} {...(color ? { sx: { color } } : {})} />
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loader;
|
||||
67
src/components/common/LogoSvg/index.tsx
Normal file
67
src/components/common/LogoSvg/index.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import React from 'react';
|
||||
|
||||
type Props = {};
|
||||
|
||||
const LogoSvg = (props: Props) => {
|
||||
return (
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width='139'
|
||||
height='32'
|
||||
viewBox='0 0 139 32'
|
||||
fill='none'
|
||||
style={{ display: 'block' }}
|
||||
{...props}
|
||||
>
|
||||
<g clip-path='url(#clip0_258_353)'>
|
||||
<path
|
||||
d='M64.2949 6.9154L63.4504 11.5143C62.8406 11.3084 62.2539 11.1433 61.6904 11.0207C61.1268 10.8982 60.5881 10.8063 60.0741 10.7484C59.5585 10.6905 59.0726 10.6599 58.6149 10.6599C57.6596 10.6599 56.7474 10.8199 55.8732 11.1399C55.0006 11.4598 54.2206 11.9262 53.5331 12.5406C52.8456 13.1551 52.3134 13.8818 51.9366 14.7226C51.5598 15.5634 51.3715 16.474 51.3715 17.4578C51.3715 18.4416 51.5813 19.3232 52.0011 20.1028C52.4209 20.884 53.0059 21.4933 53.7545 21.9308C54.5031 22.3682 55.3311 22.5861 56.2384 22.5861C58.5669 22.5861 60.9715 21.6823 63.4521 19.8747L62.5497 25.5204C62.082 25.7161 61.5532 25.9289 60.9616 26.1535C60.3699 26.3799 59.8692 26.5535 59.4593 26.676C59.0495 26.7986 58.5306 26.9126 57.9059 27.0148C57.2812 27.1186 56.6383 27.1696 55.9806 27.1696C54.6552 27.1696 53.4025 26.979 52.2242 26.5943C51.0459 26.2114 49.9849 25.6174 49.0396 24.8106C48.0943 24.0056 47.3655 23.0201 46.8499 21.8559C46.3343 20.6917 46.0765 19.3675 46.0765 17.8833C46.0765 16.6749 46.2748 15.4137 46.6698 14.1031C47.0664 12.7908 47.734 11.533 48.6744 10.3297C49.6131 9.12634 50.9236 8.13576 52.6026 7.35963C54.2817 6.5835 56.3342 6.19543 58.757 6.19543C59.7403 6.19543 60.6079 6.2516 61.3615 6.36394C62.1151 6.47627 63.0934 6.66179 64.2949 6.9171V6.9154Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
<path
|
||||
d='M82.5976 12.8929C82.5976 14.0844 82.3629 15.1771 81.8936 16.1711C81.4242 17.1651 80.6806 17.9718 79.6592 18.5914C78.6396 19.2109 77.3588 19.5207 75.8202 19.5207H71.8986L70.9285 26.4718H66.0516L68.8247 6.96985H75.9293C78.2611 6.96985 79.9551 7.52812 81.0111 8.64465C82.0671 9.76119 82.5959 11.1773 82.5959 12.8946L82.5976 12.8929ZM77.5836 13.1602C77.5836 12.5219 77.4398 11.95 77.1539 11.4428C76.8663 10.9356 76.4317 10.5373 75.85 10.2463C75.2666 9.95522 74.5709 9.81055 73.7594 9.81055H73.3083L72.325 16.6766H73.6768C74.9146 16.6766 75.8764 16.3889 76.559 15.8119C77.2415 15.2349 77.5836 14.3516 77.5836 13.1602Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
<path
|
||||
d='M104.472 15.6094C104.472 17.194 104.219 18.6629 103.713 20.0126C103.207 21.364 102.472 22.5435 101.507 23.5511C100.542 24.5587 99.3898 25.3399 98.0512 25.8931C96.7126 26.4463 95.2137 26.7237 93.5561 26.7237C92.0539 26.7237 90.5748 26.4275 89.1221 25.8369C87.6695 25.2463 86.4697 24.2813 85.5227 22.9452C84.5758 21.6091 84.1015 19.9139 84.1015 17.8595C84.1015 16.0775 84.4023 14.4826 85.0038 13.0751C85.6054 11.6675 86.4284 10.4914 87.4761 9.54334C88.5239 8.5953 89.7072 7.88555 91.0276 7.41239C92.348 6.93922 93.7511 6.70264 95.2352 6.70264C96.9836 6.70264 98.5619 7.06177 99.9682 7.77832C101.375 8.49658 102.477 9.52972 103.275 10.8811C104.072 12.2326 104.47 13.8069 104.47 15.6094H104.472ZM99.403 15.9464C99.403 14.9711 99.2163 14.1167 98.8428 13.3865C98.4693 12.6547 97.952 12.0879 97.2927 11.6845C96.6316 11.2811 95.8698 11.0803 95.0038 11.0803C93.7924 11.0803 92.743 11.4105 91.8539 12.0726C90.9665 12.733 90.2938 13.5687 89.8394 14.5763C89.3833 15.5839 89.1568 16.6 89.1568 17.6229C89.1568 18.9369 89.5551 20.0517 90.3517 20.9708C91.1482 21.8899 92.2439 22.3495 93.6371 22.3495C94.5196 22.3495 95.3178 22.1759 96.0284 21.8286C96.739 21.4814 97.3505 21.0015 97.8661 20.387C98.3801 19.7726 98.7651 19.0833 99.0196 18.319C99.2741 17.5548 99.4014 16.7651 99.4014 15.9481L99.403 15.9464Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
<path
|
||||
d='M118.927 8.11022L118.312 11.979C118.139 11.8377 117.825 11.6437 117.37 11.3952C116.914 11.1467 116.399 10.9254 115.82 10.7331C115.242 10.5407 114.657 10.4454 114.065 10.4454C113.473 10.4454 112.968 10.619 112.631 10.9662C112.293 11.3135 112.125 11.7168 112.125 12.1764C112.125 12.5798 112.239 12.944 112.467 13.2674C112.695 13.5908 113.282 14.1525 114.229 14.949C115.632 16.103 116.597 17.1089 117.126 17.9667C117.654 18.8245 117.917 19.7981 117.917 20.8857C117.917 21.7776 117.717 22.6677 117.316 23.5596C116.914 24.4515 116.23 25.2021 115.26 25.8114C114.29 26.4207 113.004 26.7254 111.401 26.7254C110.208 26.7254 109.074 26.5535 108 26.2114C106.926 25.8693 106.047 25.4778 105.364 25.037L105.951 21.0831C107.909 22.3495 109.502 22.9826 110.732 22.9826C111.505 22.9826 112.054 22.7631 112.378 22.3222C112.702 21.8814 112.862 21.4031 112.862 20.8874C112.862 20.3155 112.687 19.8151 112.336 19.3896C111.986 18.9624 111.155 18.1352 109.843 16.9063C109.059 16.1745 108.458 15.526 108.04 14.9575C107.62 14.3907 107.354 13.8937 107.24 13.4665C107.126 13.0393 107.07 12.5542 107.07 12.0096C107.07 11.3815 107.189 10.7552 107.425 10.1305C107.661 9.50759 108.033 8.93401 108.539 8.41318C109.044 7.89236 109.691 7.47706 110.479 7.16729C111.267 6.85752 112.189 6.70264 113.245 6.70264C115.167 6.70264 117.061 7.1724 118.929 8.11022H118.927Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
<path
|
||||
d='M138.286 6.96985L137.725 10.9373H131.606L129.338 26.4701H124.529L126.757 10.9373H120.664L121.251 6.96985H138.286Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
<path
|
||||
d='M16.2419 10.4522C18.4349 11.8275 20.7271 13.0376 22.9911 14.2903C23.4357 14.5354 23.9513 14.6767 24.4504 14.7856C25.5709 15.029 26.4484 14.5354 26.926 13.4563C27.8994 11.2692 29.5124 10.2241 31.4938 10.6105C31.7896 10.6667 32.0937 10.7569 32.4094 10.8811C32.5565 10.9373 32.7069 11.0037 32.8605 11.0786C33.1696 11.2283 33.477 11.3764 33.786 11.5194C33.9579 11.5994 34.1066 11.3696 33.9711 11.2368C33.196 10.4862 32.2755 9.28461 31.4112 7.56726C31.4079 7.56385 31.4079 7.55534 31.4013 7.55024C30.7799 6.15627 29.9387 4.9291 28.8728 3.87043C27.635 2.63816 26.103 1.68502 24.2818 1.00931C22.4573 0.337003 20.3503 0 17.9556 0C17.5656 0 17.1839 0.0102122 16.8038 0.0289346C19.091 5.00229 21.4773 9.528 24.8883 13.4001C24.9875 13.5125 24.8999 13.6912 24.7545 13.6708C24.2636 13.5993 23.7365 13.618 23.2903 13.4325C20.694 12.3415 18.4696 10.6224 16.4633 8.66677C15.8138 8.03361 15.5279 6.85921 15.3627 5.88054C15.0255 3.88745 15.6453 2.05266 16.5245 0.0493591C14.3794 0.190628 12.4012 0.64337 10.585 1.4212C8.97698 2.11053 7.53755 2.97856 6.27826 4.03383C6.57903 4.16318 6.86989 4.29764 7.13927 4.47295C10.1801 6.45753 13.168 8.5255 16.2419 10.4539V10.4522Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
<path
|
||||
d='M37.3507 13.2691C35.1825 13.3781 33.6455 14.1525 32.5779 15.6724C32.1648 16.2596 31.8244 16.9574 31.5434 17.7693C31.1716 18.8518 30.7105 19.9258 30.1387 20.9079C29.3603 22.2491 28.2101 23.1358 26.7244 23.6516C24.0934 24.5621 21.6376 25.7723 19.3603 27.2803C17.3706 28.5892 15.5197 30.1278 13.8059 31.8894C13.7778 31.9183 13.7481 31.9439 13.7167 31.9728C14.0241 31.9864 14.3381 31.9949 14.6554 31.9949L14.6521 31.9983C14.738 31.9983 14.8223 31.9983 14.9049 31.9949C14.9892 31.9949 15.0718 31.9949 15.1561 31.9898C15.328 31.9864 15.4949 31.9796 15.6651 31.9728C15.8469 31.9626 16.0237 31.9558 16.1972 31.9405C18.3209 31.7941 20.2792 31.3397 22.0673 30.5754C23.2837 30.0546 24.4041 29.4317 25.4238 28.7032C25.4486 28.6862 25.4717 28.6692 25.4965 28.6487C29.8693 25.6106 32.3615 21.1768 33.0589 18.302C33.2638 17.4715 33.5381 16.7889 33.8521 16.229C33.8554 16.2222 33.8587 16.2119 33.8637 16.2051C34.2339 15.5533 34.6602 15.075 35.0949 14.721C35.0982 14.7159 35.1048 14.7124 35.1114 14.7073C35.9592 14.0912 36.9904 13.8308 37.939 13.6657C38.0564 13.6487 38.1687 13.6283 38.2811 13.6095C38.8513 13.5261 39.3768 13.4632 39.7949 13.3644C40.1849 13.2759 40.4774 13.1551 40.6344 12.9594C39.9916 13.0972 37.7936 13.2436 37.349 13.264L37.3507 13.2691Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
<path
|
||||
d='M19.5455 22.135C17.7425 21.3929 15.7973 20.5981 13.8439 19.7964C13.5531 19.6773 13.6357 19.2313 13.9497 19.2313H15.5131C15.6519 19.2313 15.6783 19.0288 15.5445 18.9914C14.0092 18.5505 12.4574 18.1744 10.9552 17.6433C10.2611 17.3965 9.50583 16.9898 9.05962 16.4179C6.39065 13.0359 5.32141 9.11443 5.36603 4.85083C5.21565 4.9955 5.07187 5.13847 4.92809 5.28996C3.36637 6.92051 2.15831 8.84381 1.294 11.0514C0.429679 13.2589 0 15.6264 0 18.1488C0 20.1862 0.320607 22.0465 0.958516 23.7383C1.59973 25.4319 2.54007 26.8956 3.77622 28.1262C5.01568 29.3602 6.546 30.3133 8.37049 30.9856C9.82974 31.5235 11.4592 31.8468 13.2589 31.9524C13.3101 31.823 13.3647 31.6954 13.4374 31.5762C14.9776 29.0147 16.5013 26.4429 18.0862 23.9068C18.5357 23.1852 19.1687 22.5844 19.5438 22.135H19.5455Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
<path
|
||||
d='M32.4953 6.03711H32.7531L32.9762 6.38943H33.2225L32.9713 5.99286C33.1134 5.93159 33.1977 5.80734 33.1977 5.63373C33.1977 5.39034 33.0175 5.23035 32.7234 5.23035H32.2557V6.38943H32.4986V6.03711H32.4953ZM32.4953 5.43119H32.7068C32.8638 5.43119 32.958 5.50437 32.958 5.63373C32.958 5.76308 32.8638 5.83968 32.7068 5.83968H32.4953V5.43119Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
<path
|
||||
d='M32.6721 6.84894C33.2406 6.84894 33.6835 6.38939 33.6835 5.80389C33.6835 5.21839 33.2522 4.77075 32.6788 4.77075C32.1053 4.77075 31.6674 5.2303 31.6674 5.80899C31.6674 6.38769 32.1036 6.84724 32.6721 6.84724V6.84894ZM32.6771 4.93415C33.163 4.93415 33.5166 5.3103 33.5166 5.80559C33.5166 6.30088 33.1514 6.68895 32.6705 6.68895C32.1896 6.68895 31.8277 6.30429 31.8277 5.8124C31.8277 5.32051 32.1846 4.93585 32.6755 4.93585L32.6771 4.93415Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id='clip0_258_353'>
|
||||
<rect width='138.286' height='32' fill='currentColor' />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogoSvg;
|
||||
78
src/components/common/MComponents/MCodeInput.tsx
Normal file
78
src/components/common/MComponents/MCodeInput.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef, createRef } from 'react';
|
||||
import { Box, TextField } from '@mui/material';
|
||||
|
||||
interface CodeInputProps {
|
||||
onCodeChange?: (code: string) => void;
|
||||
isError: boolean;
|
||||
clearError?: () => void;
|
||||
}
|
||||
|
||||
export default function MCodeInput({ onCodeChange, isError, clearError }: CodeInputProps) {
|
||||
const [codes, setCodes] = useState(['', '', '', '']);
|
||||
|
||||
const inputRefs = useRef([
|
||||
createRef<HTMLInputElement>(),
|
||||
createRef<HTMLInputElement>(),
|
||||
createRef<HTMLInputElement>(),
|
||||
createRef<HTMLInputElement>(),
|
||||
]);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, index: number) => {
|
||||
const { value } = e.target;
|
||||
const digit = value.replace(/\D/g, '').slice(-1);
|
||||
|
||||
const newCodes = [...codes];
|
||||
newCodes[index] = digit;
|
||||
setCodes(newCodes);
|
||||
|
||||
if (digit && index < 3) inputRefs.current[index + 1].current?.focus();
|
||||
if (onCodeChange) onCodeChange(newCodes.join(''));
|
||||
if (clearError) clearError();
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement | HTMLDivElement>, index: number) => {
|
||||
if (e.key === 'Backspace' && !codes[index]) {
|
||||
if (index > 0) {
|
||||
inputRefs.current[index - 1].current?.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
{codes.map((code, idx) => (
|
||||
<TextField
|
||||
key={idx}
|
||||
error={isError}
|
||||
inputRef={inputRefs.current[idx]}
|
||||
value={code}
|
||||
onChange={e => handleChange(e, idx)}
|
||||
onKeyDown={e => handleKeyDown(e, idx)}
|
||||
inputProps={{
|
||||
maxLength: 1,
|
||||
style: {
|
||||
textAlign: 'center',
|
||||
fontSize: '1.5rem',
|
||||
width: '3rem',
|
||||
},
|
||||
}}
|
||||
sx={{
|
||||
'& .MuiOutlinedInput-root': {
|
||||
backgroundColor: '#f2f2f2',
|
||||
borderRadius: 4,
|
||||
'& fieldset': {
|
||||
borderColor: 'transparent',
|
||||
transition: 'border-color 0.3s ease',
|
||||
},
|
||||
'&.Mui-focused fieldset': {
|
||||
border: '1px solid #1976D2',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
58
src/components/common/MComponents/MPhoneInput.tsx
Normal file
58
src/components/common/MComponents/MPhoneInput.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { TextField, InputAdornment } from '@mui/material';
|
||||
|
||||
function formatPhoneNumber(phoneNumber: string): string {
|
||||
const digits = phoneNumber.replace(/\D/g, '');
|
||||
|
||||
const truncated = digits.slice(0, 9);
|
||||
|
||||
let formatted = '';
|
||||
|
||||
if (truncated.length > 0) {
|
||||
formatted = truncated.slice(0, 2);
|
||||
}
|
||||
if (truncated.length > 2) {
|
||||
formatted += ' ' + truncated.slice(2, 5);
|
||||
}
|
||||
if (truncated.length > 5) {
|
||||
formatted += ' ' + truncated.slice(5, 7);
|
||||
}
|
||||
if (truncated.length > 7) {
|
||||
formatted += ' ' + truncated.slice(7, 9);
|
||||
}
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
||||
interface IProps {
|
||||
value?: string;
|
||||
setValue?: (value: string) => void;
|
||||
element_id?: string;
|
||||
isError: boolean;
|
||||
}
|
||||
|
||||
export default function PhoneInput({ value, setValue, isError, element_id }: IProps) {
|
||||
const [phoneNumber, setPhoneNumber] = useState('');
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (setValue) setValue(formatPhoneNumber(e.target.value));
|
||||
else setPhoneNumber(formatPhoneNumber(e.target.value));
|
||||
};
|
||||
|
||||
return (
|
||||
<TextField
|
||||
id={element_id}
|
||||
error={isError}
|
||||
label='Telefon raqam'
|
||||
value={value || phoneNumber}
|
||||
onChange={handleChange}
|
||||
fullWidth={true}
|
||||
placeholder='99 123 45 67'
|
||||
InputProps={{
|
||||
startAdornment: <InputAdornment position='start'>+998</InputAdornment>,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
369
src/components/common/MyTable/MuiTable.tsx
Normal file
369
src/components/common/MyTable/MuiTable.tsx
Normal file
@@ -0,0 +1,369 @@
|
||||
import * as React from 'react';
|
||||
import { alpha } from '@mui/material/styles';
|
||||
import Box from '@mui/material/Box';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableContainer from '@mui/material/TableContainer';
|
||||
import TableHead from '@mui/material/TableHead';
|
||||
import TablePagination from '@mui/material/TablePagination';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import TableSortLabel from '@mui/material/TableSortLabel';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||
import { visuallyHidden } from '@mui/utils';
|
||||
|
||||
interface Data {
|
||||
id: number;
|
||||
calories: number;
|
||||
carbs: number;
|
||||
fat: number;
|
||||
name: string;
|
||||
protein: number;
|
||||
}
|
||||
|
||||
function createData(id: number, name: string, calories: number, fat: number, carbs: number, protein: number): Data {
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
calories,
|
||||
fat,
|
||||
carbs,
|
||||
protein,
|
||||
};
|
||||
}
|
||||
|
||||
const rows = [
|
||||
createData(1, 'Cupcake', 305, 3.7, 67, 4.3),
|
||||
createData(2, 'Donut', 452, 25.0, 51, 4.9),
|
||||
createData(3, 'Eclair', 262, 16.0, 24, 6.0),
|
||||
createData(4, 'Frozen yoghurt', 159, 6.0, 24, 4.0),
|
||||
createData(5, 'Gingerbread', 356, 16.0, 49, 3.9),
|
||||
createData(6, 'Honeycomb', 408, 3.2, 87, 6.5),
|
||||
createData(7, 'Ice cream sandwich', 237, 9.0, 37, 4.3),
|
||||
createData(8, 'Jelly Bean', 375, 0.0, 94, 0.0),
|
||||
createData(9, 'KitKat', 518, 26.0, 65, 7.0),
|
||||
createData(10, 'Lollipop', 392, 0.2, 98, 0.0),
|
||||
createData(11, 'Marshmallow', 318, 0, 81, 2.0),
|
||||
createData(12, 'Nougat', 360, 19.0, 9, 37.0),
|
||||
createData(13, 'Oreo', 437, 18.0, 63, 4.0),
|
||||
];
|
||||
|
||||
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
|
||||
if (b[orderBy] < a[orderBy]) {
|
||||
return -1;
|
||||
}
|
||||
if (b[orderBy] > a[orderBy]) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
type Order = 'asc' | 'desc';
|
||||
|
||||
function getComparator<Key extends keyof any>(
|
||||
order: Order,
|
||||
orderBy: Key
|
||||
): (a: { [key in Key]: number | string }, b: { [key in Key]: number | string }) => number {
|
||||
return order === 'desc' ? (a, b) => descendingComparator(a, b, orderBy) : (a, b) => -descendingComparator(a, b, orderBy);
|
||||
}
|
||||
|
||||
// Since 2020 all major browsers ensure sort stability with Array.prototype.sort().
|
||||
// stableSort() brings sort stability to non-modern browsers (notably IE11). If you
|
||||
// only support modern browsers you can replace stableSort(exampleArray, exampleComparator)
|
||||
// with exampleArray.slice().sort(exampleComparator)
|
||||
function stableSort<T>(array: readonly T[], comparator: (a: T, b: T) => number) {
|
||||
const stabilizedThis = array.map((el, index) => [el, index] as [T, number]);
|
||||
stabilizedThis.sort((a, b) => {
|
||||
const order = comparator(a[0], b[0]);
|
||||
if (order !== 0) {
|
||||
return order;
|
||||
}
|
||||
return a[1] - b[1];
|
||||
});
|
||||
return stabilizedThis.map(el => el[0]);
|
||||
}
|
||||
|
||||
interface HeadCell {
|
||||
disablePadding: boolean;
|
||||
id: keyof Data;
|
||||
label: string;
|
||||
numeric: boolean;
|
||||
}
|
||||
|
||||
const headCells: readonly HeadCell[] = [
|
||||
{
|
||||
id: 'name',
|
||||
numeric: false,
|
||||
disablePadding: true,
|
||||
label: 'Dessert (100g serving)',
|
||||
},
|
||||
{
|
||||
id: 'calories',
|
||||
numeric: true,
|
||||
disablePadding: false,
|
||||
label: 'Calories',
|
||||
},
|
||||
{
|
||||
id: 'fat',
|
||||
numeric: true,
|
||||
disablePadding: false,
|
||||
label: 'Fat (g)',
|
||||
},
|
||||
{
|
||||
id: 'carbs',
|
||||
numeric: true,
|
||||
disablePadding: false,
|
||||
label: 'Carbs (g)',
|
||||
},
|
||||
{
|
||||
id: 'protein',
|
||||
numeric: true,
|
||||
disablePadding: false,
|
||||
label: 'Protein (g)',
|
||||
},
|
||||
];
|
||||
|
||||
interface EnhancedTableProps {
|
||||
numSelected: number;
|
||||
onRequestSort: (event: React.MouseEvent<unknown>, property: keyof Data) => void;
|
||||
onSelectAllClick: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
order: Order;
|
||||
orderBy: string;
|
||||
rowCount: number;
|
||||
}
|
||||
|
||||
function EnhancedTableHead(props: EnhancedTableProps) {
|
||||
const { onSelectAllClick, order, orderBy, numSelected, rowCount, onRequestSort } = props;
|
||||
const createSortHandler = (property: keyof Data) => (event: React.MouseEvent<unknown>) => {
|
||||
onRequestSort(event, property);
|
||||
};
|
||||
|
||||
return (
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell padding='checkbox'>
|
||||
<Checkbox
|
||||
color='primary'
|
||||
indeterminate={numSelected > 0 && numSelected < rowCount}
|
||||
checked={rowCount > 0 && numSelected === rowCount}
|
||||
onChange={onSelectAllClick}
|
||||
inputProps={{
|
||||
'aria-label': 'select all desserts',
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
{headCells.map(headCell => (
|
||||
<TableCell
|
||||
key={headCell.id}
|
||||
align={headCell.numeric ? 'right' : 'left'}
|
||||
padding={headCell.disablePadding ? 'none' : 'normal'}
|
||||
sortDirection={orderBy === headCell.id ? order : false}
|
||||
>
|
||||
<TableSortLabel
|
||||
active={orderBy === headCell.id}
|
||||
direction={orderBy === headCell.id ? order : 'asc'}
|
||||
onClick={createSortHandler(headCell.id)}
|
||||
>
|
||||
{headCell.label}
|
||||
{orderBy === headCell.id ? (
|
||||
<Box component='span' sx={visuallyHidden}>
|
||||
{order === 'desc' ? 'sorted descending' : 'sorted ascending'}
|
||||
</Box>
|
||||
) : null}
|
||||
</TableSortLabel>
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
);
|
||||
}
|
||||
|
||||
interface EnhancedTableToolbarProps {
|
||||
numSelected: number;
|
||||
}
|
||||
|
||||
function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
|
||||
const { numSelected } = props;
|
||||
|
||||
return (
|
||||
<Toolbar
|
||||
sx={{
|
||||
pl: { sm: 2 },
|
||||
pr: { xs: 1, sm: 1 },
|
||||
...(numSelected > 0 && {
|
||||
bgcolor: theme => alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity),
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{numSelected > 0 ? (
|
||||
<Typography sx={{ flex: '1 1 100%' }} color='inherit' variant='subtitle1' component='div'>
|
||||
{numSelected} selected
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography sx={{ flex: '1 1 100%' }} variant='h6' id='tableTitle' component='div'>
|
||||
Nutrition
|
||||
</Typography>
|
||||
)}
|
||||
{numSelected > 0 ? (
|
||||
<Tooltip title='Delete'>
|
||||
<IconButton>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip title='Filter list'>
|
||||
<IconButton>
|
||||
<FilterListIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Toolbar>
|
||||
);
|
||||
}
|
||||
|
||||
export default function EnhancedTable() {
|
||||
const [order, setOrder] = React.useState<Order>('asc');
|
||||
const [orderBy, setOrderBy] = React.useState<keyof Data>('calories');
|
||||
const [selected, setSelected] = React.useState<readonly number[]>([]);
|
||||
const [page, setPage] = React.useState(0);
|
||||
const [dense, setDense] = React.useState(false);
|
||||
const [rowsPerPage, setRowsPerPage] = React.useState(5);
|
||||
|
||||
const handleRequestSort = (event: React.MouseEvent<unknown>, property: keyof Data) => {
|
||||
const isAsc = orderBy === property && order === 'asc';
|
||||
setOrder(isAsc ? 'desc' : 'asc');
|
||||
setOrderBy(property);
|
||||
};
|
||||
|
||||
const handleSelectAllClick = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.checked) {
|
||||
const newSelected = rows.map(n => n.id);
|
||||
setSelected(newSelected);
|
||||
return;
|
||||
}
|
||||
setSelected([]);
|
||||
};
|
||||
|
||||
const handleClick = (event: React.MouseEvent<unknown>, id: number) => {
|
||||
const selectedIndex = selected.indexOf(id);
|
||||
let newSelected: readonly number[] = [];
|
||||
|
||||
if (selectedIndex === -1) {
|
||||
newSelected = newSelected.concat(selected, id);
|
||||
} else if (selectedIndex === 0) {
|
||||
newSelected = newSelected.concat(selected.slice(1));
|
||||
} else if (selectedIndex === selected.length - 1) {
|
||||
newSelected = newSelected.concat(selected.slice(0, -1));
|
||||
} else if (selectedIndex > 0) {
|
||||
newSelected = newSelected.concat(selected.slice(0, selectedIndex), selected.slice(selectedIndex + 1));
|
||||
}
|
||||
setSelected(newSelected);
|
||||
};
|
||||
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setRowsPerPage(parseInt(event.target.value, 10));
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
const handleChangeDense = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDense(event.target.checked);
|
||||
};
|
||||
|
||||
const isSelected = (id: number) => selected.indexOf(id) !== -1;
|
||||
|
||||
// Avoid a layout jump when reaching the last page with empty rows.
|
||||
const emptyRows = page > 0 ? Math.max(0, (1 + page) * rowsPerPage - rows.length) : 0;
|
||||
|
||||
const visibleRows = React.useMemo(
|
||||
() => stableSort(rows, getComparator(order, orderBy)).slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage),
|
||||
[order, orderBy, page, rowsPerPage]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<Paper sx={{ width: '100%', mb: 2 }}>
|
||||
<EnhancedTableToolbar numSelected={selected.length} />
|
||||
<TableContainer>
|
||||
<Table sx={{ minWidth: 750 }} aria-labelledby='tableTitle' size={dense ? 'small' : 'medium'}>
|
||||
<EnhancedTableHead
|
||||
numSelected={selected.length}
|
||||
order={order}
|
||||
orderBy={orderBy}
|
||||
onSelectAllClick={handleSelectAllClick}
|
||||
onRequestSort={handleRequestSort}
|
||||
rowCount={rows.length}
|
||||
/>
|
||||
<TableBody>
|
||||
{visibleRows.map((row, index) => {
|
||||
const isItemSelected = isSelected(row.id);
|
||||
const labelId = `enhanced-table-checkbox-${index}`;
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
hover
|
||||
onClick={event => handleClick(event, row.id)}
|
||||
role='checkbox'
|
||||
aria-checked={isItemSelected}
|
||||
tabIndex={-1}
|
||||
key={row.id}
|
||||
selected={isItemSelected}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
>
|
||||
<TableCell padding='checkbox'>
|
||||
<Checkbox
|
||||
color='primary'
|
||||
checked={isItemSelected}
|
||||
inputProps={{
|
||||
'aria-labelledby': labelId,
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell component='th' id={labelId} scope='row' padding='none'>
|
||||
{row.name}
|
||||
</TableCell>
|
||||
<TableCell align='right'>{row.calories}</TableCell>
|
||||
<TableCell align='right'>{row.fat}</TableCell>
|
||||
<TableCell align='right'>{row.carbs}</TableCell>
|
||||
<TableCell align='right'>{row.protein}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
{emptyRows > 0 && (
|
||||
<TableRow
|
||||
style={{
|
||||
height: (dense ? 33 : 53) * emptyRows,
|
||||
}}
|
||||
>
|
||||
<TableCell colSpan={6} />
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25]}
|
||||
component='div'
|
||||
count={rows.length}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
onPageChange={handleChangePage}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||
/>
|
||||
</Paper>
|
||||
<FormControlLabel control={<Switch checked={dense} onChange={handleChangeDense} />} label='Dense padding' />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
159
src/components/common/MyTable/MyTable.tsx
Normal file
159
src/components/common/MyTable/MyTable.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import Loader from '@/components/common/Loader';
|
||||
import { Scrollbar } from '@/components/common/Scrollbar';
|
||||
import BaseButton from '@/components/ui-kit/BaseButton';
|
||||
import BaseIconButton from '@/components/ui-kit/BaseIconButton';
|
||||
import { FilterList, Search } from '@mui/icons-material';
|
||||
import {
|
||||
Box,
|
||||
IconButton,
|
||||
Stack,
|
||||
SxProps,
|
||||
Table,
|
||||
TableBody,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Theme,
|
||||
Tooltip,
|
||||
Typography,
|
||||
styled,
|
||||
TableCell,
|
||||
} from '@mui/material';
|
||||
import React from 'react';
|
||||
|
||||
export interface ColumnData<Data, DataKey = keyof Data> {
|
||||
dataKey?: DataKey;
|
||||
label: string;
|
||||
width: string | number;
|
||||
numeric?: boolean;
|
||||
getSxStyles?: (data: Data) => SxProps<Theme>;
|
||||
renderCell?: (data: Data, rowIndex: number) => React.ReactNode;
|
||||
renderHeaderCell?: (rowIndex: number) => React.ReactNode;
|
||||
}
|
||||
|
||||
const StyledTable = styled(Table)`
|
||||
td,
|
||||
th {
|
||||
height: auto;
|
||||
padding: 12px 24px;
|
||||
border-color: #ebeff6;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
tr {
|
||||
&:last-child {
|
||||
td {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thead {
|
||||
th {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
color: #8c959e;
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
td {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
`;
|
||||
const StyledTableRow = styled(TableRow)``;
|
||||
const StyledTableCell = styled(TableCell)`
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
type Props<Data> = {
|
||||
columns: ColumnData<Data & { id: number | string }>[];
|
||||
data: Data[];
|
||||
loading: boolean;
|
||||
onClickRow?: (data: Data) => void;
|
||||
};
|
||||
|
||||
const MyTable = <Data extends { id: number | string }>(props: Props<Data>) => {
|
||||
const { columns, data, loading, onClickRow } = props;
|
||||
|
||||
const isEmpty = !data?.length && !loading;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Scrollbar>
|
||||
<StyledTable>
|
||||
<TableHead>
|
||||
<StyledTableRow>
|
||||
{columns.map((column, index) => (
|
||||
<StyledTableCell
|
||||
// @ts-expect-error
|
||||
key={column.dataKey + index}
|
||||
variant='head'
|
||||
align={column.numeric || false ? 'right' : 'left'}
|
||||
sx={{
|
||||
backgroundColor: 'background.paper',
|
||||
width: column.width,
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{column.renderHeaderCell ? column.renderHeaderCell(index) : column.label}
|
||||
</StyledTableCell>
|
||||
))}
|
||||
</StyledTableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isEmpty ? (
|
||||
'Empty'
|
||||
) : loading ? (
|
||||
<StyledTableCell colSpan={columns.length}>
|
||||
<Loader p={4} size={96} />
|
||||
</StyledTableCell>
|
||||
) : (
|
||||
data.map((row, rowIndex) => {
|
||||
return (
|
||||
<StyledTableRow
|
||||
key={row.id}
|
||||
sx={{
|
||||
...(onClickRow
|
||||
? {
|
||||
cursor: 'pointer',
|
||||
'&:hover': {
|
||||
backgroundColor: '#f1f1f1',
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
}}
|
||||
onClick={() => {
|
||||
onClickRow?.(row);
|
||||
}}
|
||||
>
|
||||
{columns.map((column, index) => (
|
||||
<StyledTableCell
|
||||
// @ts-expect-error
|
||||
key={column.dataKey + index}
|
||||
align={column.numeric || false ? 'right' : 'left'}
|
||||
sx={{
|
||||
...column.getSxStyles?.(row),
|
||||
width: column.width,
|
||||
}}
|
||||
>
|
||||
{/* @ts-expect-error */}
|
||||
{column.renderCell ? column.renderCell(row, rowIndex) : row[column.dataKey]}
|
||||
</StyledTableCell>
|
||||
))}
|
||||
</StyledTableRow>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</TableBody>
|
||||
</StyledTable>
|
||||
</Scrollbar>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyTable;
|
||||
1
src/components/common/MyTable/index.ts
Normal file
1
src/components/common/MyTable/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as MyTable, type ColumnData } from './MyTable';
|
||||
9
src/components/common/NextLink/NextLink.style.ts
Normal file
9
src/components/common/NextLink/NextLink.style.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { styled } from '@mui/material';
|
||||
import Link from 'next/link';
|
||||
|
||||
const StyledNextLink = styled(Link)`
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
`;
|
||||
|
||||
export { StyledNextLink };
|
||||
30
src/components/common/NextLink/NextLink.tsx
Normal file
30
src/components/common/NextLink/NextLink.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React, { ForwardedRef, forwardRef } from 'react';
|
||||
import { StyledNextLink } from '@/components/common/NextLink/NextLink.style';
|
||||
import { useLocale } from 'next-intl';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
interface IMyLink extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
|
||||
children?: React.ReactNode;
|
||||
href: string;
|
||||
className?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const NextLink = ({ href, className, children, ...props }: IMyLink, ref: ForwardedRef<HTMLAnchorElement>) => {
|
||||
const pathname = usePathname();
|
||||
const locale = useLocale();
|
||||
|
||||
return (
|
||||
<StyledNextLink
|
||||
href={`/${locale}${href}`}
|
||||
className={className + (pathname.includes(href) ? ' active-navlink' : '')}
|
||||
{...props}
|
||||
prefetch={false}
|
||||
ref={ref}
|
||||
>
|
||||
{children}
|
||||
</StyledNextLink>
|
||||
);
|
||||
};
|
||||
|
||||
export default forwardRef(NextLink);
|
||||
1
src/components/common/NextLink/index.ts
Normal file
1
src/components/common/NextLink/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './NextLink';
|
||||
18
src/components/common/QueryContainer/index.tsx
Normal file
18
src/components/common/QueryContainer/index.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
'use client';
|
||||
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
retry: false,
|
||||
retryOnMount: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default function QueryContainer({ children }: { children: ReactNode }) {
|
||||
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
|
||||
}
|
||||
9
src/components/common/Scrollbar/index.tsx
Normal file
9
src/components/common/Scrollbar/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import SimpleBar from 'simplebar-react';
|
||||
import { styled } from '@mui/material/styles';
|
||||
|
||||
export const Scrollbar = styled(SimpleBar)`
|
||||
border-right: 1px solid #555;
|
||||
::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
}
|
||||
`;
|
||||
151
src/components/common/StatusChangePopup/index.tsx
Normal file
151
src/components/common/StatusChangePopup/index.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
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 { Box, CircularProgress, IconButton, PopoverOrigin, SvgIcon, Typography } from '@mui/material';
|
||||
import { ArrowDropDown, MoreVert } from '@mui/icons-material';
|
||||
import { getBoxStatusStyles } from '@/theme/getStatusBoxStyles';
|
||||
import { BoxStatus, IBox } from '@/data/box/box.model';
|
||||
import { PartyStatus } from '@/data/party/party.model';
|
||||
import BaseButton from '@/components/ui-kit/BaseButton';
|
||||
|
||||
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 StatusChangePopupProps = {
|
||||
mainIcon?: React.ReactNode;
|
||||
buttons: {
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
dontCloseOnClick?: boolean;
|
||||
}[];
|
||||
loading?: boolean;
|
||||
placement?: PlacementType;
|
||||
anchor: {
|
||||
text: React.ReactNode;
|
||||
status: BoxStatus | PartyStatus;
|
||||
};
|
||||
};
|
||||
|
||||
export default function StatusChangePopup({ buttons, mainIcon, placement, anchor, loading }: StatusChangePopupProps) {
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<BaseButton
|
||||
id='demo-customized-button'
|
||||
aria-controls={open ? 'demo-customized-menu' : undefined}
|
||||
aria-haspopup='true'
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
onClick={handleClick}
|
||||
sx={{ ...getBoxStatusStyles(anchor.status), alignItems: 'center', whiteSpace: 'nowrap' }}
|
||||
endIcon={<ArrowDropDown />}
|
||||
loading={loading}
|
||||
fullWidth
|
||||
>
|
||||
{anchor.text}
|
||||
</BaseButton>
|
||||
<StyledMenu
|
||||
id='demo-customized-menu'
|
||||
MenuListProps={{
|
||||
'aria-labelledby': 'demo-customized-button',
|
||||
}}
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
placement={{
|
||||
anchorOrigin: placement?.anchorOrigin ?? {
|
||||
vertical: 'bottom',
|
||||
horizontal: 'center',
|
||||
},
|
||||
transformOrigin: placement?.transformOrigin ?? {
|
||||
vertical: 'top',
|
||||
horizontal: 'center',
|
||||
},
|
||||
}}
|
||||
sx={{
|
||||
'.MuiMenu-list': {
|
||||
padding: '4px',
|
||||
borderRadius: '8px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{buttons.map((btn, index) => {
|
||||
return (
|
||||
<MenuItem
|
||||
onClick={event => {
|
||||
btn.onClick();
|
||||
if (!btn.dontCloseOnClick) handleClose();
|
||||
}}
|
||||
disableRipple
|
||||
key={index}
|
||||
sx={{
|
||||
padding: '8px 12px',
|
||||
gap: '8px',
|
||||
}}
|
||||
>
|
||||
<SvgIcon
|
||||
sx={{
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
{btn.icon}
|
||||
</SvgIcon>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: '14px',
|
||||
fontWeight: 500,
|
||||
lineHeight: '16px',
|
||||
letterSpacing: '-0.4px',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{btn.label}
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</StyledMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
190
src/components/common/VerifyPhoneModal/VerifyPhoneModal.tsx
Normal file
190
src/components/common/VerifyPhoneModal/VerifyPhoneModal.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Button, Box, Modal, Typography } from '@mui/material';
|
||||
import MCodeInput from '@/components/common/MComponents/MCodeInput';
|
||||
import PhoneInput from '@/components/common/MComponents/MPhoneInput';
|
||||
import axios from 'axios';
|
||||
import { BASE_URL } from '@/helpers/constants';
|
||||
import { useMyNavigation } from '@/hooks/useMyNavigation';
|
||||
import toast from 'react-hot-toast';
|
||||
import authRequest from '@/helpers/authRequest';
|
||||
|
||||
interface IProps {
|
||||
onClose: () => void;
|
||||
openRegister: () => void;
|
||||
open: boolean;
|
||||
changePhone: (value: string) => void;
|
||||
phoneNumber?: string;
|
||||
}
|
||||
|
||||
interface IData {
|
||||
message: string;
|
||||
data: null | {
|
||||
username: string;
|
||||
accessToken: string;
|
||||
roles: string[];
|
||||
};
|
||||
}
|
||||
|
||||
const style = {
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: { xs: '90%', sm: 400 },
|
||||
bgcolor: 'background.paper',
|
||||
borderRadius: 2,
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
};
|
||||
|
||||
export const VerifyPhoneModal = ({ onClose, open, openRegister, changePhone, phoneNumber }: IProps) => {
|
||||
const navigate = useMyNavigation();
|
||||
const [phoneValue, setPhoneValue] = useState('');
|
||||
const [step, setStep] = useState<1 | 2>(1);
|
||||
const [verificationCode, setVerificationCode] = useState('');
|
||||
const [inputErrors, setInputErrors] = useState({ phone: false, code: false });
|
||||
const [isExist, setIsExist] = useState<boolean>(false);
|
||||
const [tryNumber, setTryNumber] = useState(0);
|
||||
|
||||
const clearCodesError = () => {
|
||||
setInputErrors(prev => ({ ...prev, code: false }));
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setStep(1);
|
||||
setVerificationCode('');
|
||||
onClose();
|
||||
};
|
||||
|
||||
const changePhoneValue = (phone_value: string) => {
|
||||
changePhone('+998' + phone_value?.replaceAll(' ', ''));
|
||||
setPhoneValue(phone_value);
|
||||
setInputErrors(prev => ({ ...prev, phone: false }));
|
||||
};
|
||||
|
||||
const handleSendCode = () => {
|
||||
const phone_value = phoneValue.replaceAll(' ', '');
|
||||
if (phone_value.length !== 9) {
|
||||
setInputErrors(prev => ({ ...prev, phone: true }));
|
||||
return;
|
||||
}
|
||||
toast
|
||||
.promise(axios.get(BASE_URL + '/api/v1/profile/check-phone?phone=%2B998' + phone_value), {
|
||||
loading: 'Nomer tekshirilmoqda',
|
||||
success: 'Tekshirildi',
|
||||
error: 'Xatolik yuz berdi',
|
||||
})
|
||||
.then(async (res: any) => {
|
||||
const is_exist: boolean = res?.data.data?.exists;
|
||||
setIsExist(is_exist);
|
||||
if (is_exist) {
|
||||
setStep(2);
|
||||
try {
|
||||
await authRequest.post('/profile/send-otp', { phone: phoneNumber });
|
||||
toast.success("Tasdiqlash ko'di yuborildi");
|
||||
} catch (error: any) {
|
||||
toast.error(error?.message || "Tasdiqlash ko'di yuborilmadi");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
openRegister();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
setStep(1);
|
||||
setVerificationCode('');
|
||||
};
|
||||
|
||||
const handleVerifyCode = () => {
|
||||
if (verificationCode?.length !== 4) {
|
||||
setInputErrors(prev => ({ ...prev, code: true }));
|
||||
} else {
|
||||
if (isExist) {
|
||||
axios
|
||||
.post(BASE_URL + '/api/v1/profile/sign-in', {
|
||||
phone: '+998' + phoneValue.replaceAll(' ', ''),
|
||||
code: verificationCode,
|
||||
})
|
||||
.then(res => {
|
||||
const resData: IData = res.data;
|
||||
const data = resData.data;
|
||||
if (data) {
|
||||
localStorage.setItem('token', data.accessToken);
|
||||
localStorage.setItem('username', data.username);
|
||||
localStorage.setItem('roles', data.roles?.[0]);
|
||||
navigate.push('/profile');
|
||||
onClose();
|
||||
setVerificationCode('');
|
||||
setStep(1);
|
||||
} else {
|
||||
toast.error("Tasdiqlash ko'di xato");
|
||||
|
||||
if (tryNumber > 3) {
|
||||
onClose();
|
||||
setVerificationCode('');
|
||||
setPhoneValue('');
|
||||
setStep(1);
|
||||
}
|
||||
setTryNumber(prev => prev + 1);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
openRegister();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<Box sx={style}>
|
||||
<Typography variant='h6' align={'center'} fontSize={'1.5rem'} fontWeight={'bolder'} component='h2' mb={2}>
|
||||
{`Ro'yxatdan o'tish / Kirish`}
|
||||
</Typography>
|
||||
|
||||
{step === 1 && (
|
||||
<>
|
||||
<Typography variant='body1' mb={2}>
|
||||
{`Tasdiqlash kodi jo'natishimiz uchun telefon raqamingizni kiriting`}
|
||||
</Typography>
|
||||
|
||||
<PhoneInput isError={inputErrors.phone} value={phoneValue} setValue={changePhoneValue} />
|
||||
|
||||
<Box sx={{ mt: 3 }} display='flex' justifyContent='flex-end'>
|
||||
<Button variant='outlined' onClick={handleClose} sx={{ mr: 2 }}>
|
||||
Bekor qilish
|
||||
</Button>
|
||||
|
||||
<Button variant='contained' color='primary' onClick={handleSendCode}>
|
||||
Tasdiqlash
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{step === 2 && (
|
||||
<>
|
||||
<Typography variant='body1' mb={2}>
|
||||
Iltimos, SMS orqali yuborilgan 4 xonali kodni kiriting:
|
||||
</Typography>
|
||||
|
||||
<MCodeInput clearError={clearCodesError} isError={inputErrors.code} onCodeChange={setVerificationCode} />
|
||||
|
||||
<Box sx={{ mt: 3 }} display='flex' justifyContent={'end'} gap={2}>
|
||||
<Button variant='outlined' onClick={handleBack}>
|
||||
Orqaga
|
||||
</Button>
|
||||
|
||||
<Button variant='contained' color='primary' onClick={handleVerifyCode}>
|
||||
Tasdiqlash
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
1
src/components/common/VerifyPhoneModal/index.ts
Normal file
1
src/components/common/VerifyPhoneModal/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { VerifyPhoneModal } from './VerifyPhoneModal';
|
||||
15
src/components/icons/index.tsx
Normal file
15
src/components/icons/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
export const ImageIcon = ({ width = '30' }: { width?: string }) => (
|
||||
<svg width={width} height={width} viewBox='0 0 32 32' version='1.1' xmlns='http://www.w3.org/2000/svg'>
|
||||
<title>image-picture</title>
|
||||
<desc>Created with Sketch Beta.</desc>
|
||||
<defs></defs>
|
||||
<g id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'>
|
||||
<g id='Icon-Set' transform='translate(-360.000000, -99.000000)' fill='#555'>
|
||||
<path
|
||||
d='M368,109 C366.896,109 366,108.104 366,107 C366,105.896 366.896,105 368,105 C369.104,105 370,105.896 370,107 C370,108.104 369.104,109 368,109 L368,109 Z M368,103 C365.791,103 364,104.791 364,107 C364,109.209 365.791,111 368,111 C370.209,111 372,109.209 372,107 C372,104.791 370.209,103 368,103 L368,103 Z M390,116.128 L384,110 L374.059,120.111 L370,116 L362,123.337 L362,103 C362,101.896 362.896,101 364,101 L388,101 C389.104,101 390,101.896 390,103 L390,116.128 L390,116.128 Z M390,127 C390,128.104 389.104,129 388,129 L382.832,129 L375.464,121.535 L384,112.999 L390,118.999 L390,127 L390,127 Z M364,129 C362.896,129 362,128.104 362,127 L362,126.061 L369.945,118.945 L380.001,129 L364,129 L364,129 Z M388,99 L364,99 C361.791,99 360,100.791 360,103 L360,127 C360,129.209 361.791,131 364,131 L388,131 C390.209,131 392,129.209 392,127 L392,103 C392,100.791 390.209,99 388,99 L388,99 Z'
|
||||
id='image-picture'
|
||||
></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
71
src/components/layout/dashboard-layout/DashboardLayout.tsx
Normal file
71
src/components/layout/dashboard-layout/DashboardLayout.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
'use client';
|
||||
|
||||
import Loader from '@/components/common/Loader';
|
||||
import DashboardHeader from '@/components/layout/dashboard-layout/Header/Header';
|
||||
import Sidebar from '@/components/layout/dashboard-layout/Sidebar';
|
||||
import { useAuthContext } from '@/context/auth-context';
|
||||
import { pageLinks } from '@/helpers/constants';
|
||||
import { useMyNavigation } from '@/hooks/useMyNavigation';
|
||||
import { useMyTranslation } from '@/hooks/useMyTranslation';
|
||||
import { Box, styled } from '@mui/material';
|
||||
import { useLocale } from 'next-intl';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const StyledLayout = styled(Box)`
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
'sidebar header'
|
||||
'sidebar main';
|
||||
grid-template-columns: 250px 1fr;
|
||||
grid-template-rows: 80px calc(100vh - 80px);
|
||||
font-family: 'SF Pro Display', sans-serif;
|
||||
|
||||
.layout-header {
|
||||
grid-area: header;
|
||||
height: 100%;
|
||||
}
|
||||
.layout-sidebar {
|
||||
grid-area: sidebar;
|
||||
}
|
||||
.layout-main {
|
||||
grid-area: main;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: #edf1f7;
|
||||
padding: 40px 40px 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
const DashboardLayout = (props: Props) => {
|
||||
const { isAuth, isLoading } = useAuthContext();
|
||||
const navigation = useMyNavigation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAuth && !isLoading) {
|
||||
navigation.push(pageLinks.login);
|
||||
}
|
||||
}, [isAuth, isLoading]);
|
||||
|
||||
if (isLoading) return <Loader p={8} size={96} />;
|
||||
|
||||
if (!isAuth && !isLoading) return null;
|
||||
|
||||
return (
|
||||
<StyledLayout className='dashboard-layout'>
|
||||
<header className='layout-header'>
|
||||
<DashboardHeader />
|
||||
</header>
|
||||
<aside className='layout-sidebar'>
|
||||
<Sidebar />
|
||||
</aside>
|
||||
<main className='layout-main'>{props.children}</main>
|
||||
</StyledLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardLayout;
|
||||
100
src/components/layout/dashboard-layout/Header/Header.tsx
Normal file
100
src/components/layout/dashboard-layout/Header/Header.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { Settings } from '@mui/icons-material';
|
||||
import { Box, Icon, IconButton, Stack, Typography, styled } from '@mui/material';
|
||||
import { useLocale } from 'next-intl';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React from 'react';
|
||||
|
||||
type Props = {};
|
||||
|
||||
const StyledBox = styled(Box)`
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
background: #fff;
|
||||
box-shadow: 12px 12px 24px 0px rgba(0, 0, 0, 0.04);
|
||||
padding: 27px 40px;
|
||||
|
||||
.user-name {
|
||||
color: #000;
|
||||
font-size: 24px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 32px;
|
||||
letter-spacing: 1.44px;
|
||||
}
|
||||
|
||||
.lng-img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.lng-select {
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
color: #07275c;
|
||||
}
|
||||
`;
|
||||
|
||||
const DashboardHeader = (props: Props) => {
|
||||
const language = useLocale();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
const changeLanguage = (lng: string) => {
|
||||
router.push(`/${lng}/${pathname.substring(3)}`, { scroll: false });
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledBox>
|
||||
{/* <Typography className='user-name'>Salom, Qurbonmurod</Typography> */}
|
||||
|
||||
<Stack direction={'row'} spacing={1.5}>
|
||||
<Stack direction={'row'} justifyContent={'center'} alignItems={'center'} spacing={1}>
|
||||
<img className='lng-img' src={getLanguageImage(language)} alt='' />
|
||||
|
||||
<select
|
||||
className='lng-select'
|
||||
value={language}
|
||||
onChange={event => {
|
||||
changeLanguage(event.target.value);
|
||||
}}
|
||||
>
|
||||
<option value='uz'>UZ</option>
|
||||
<option value='ru'>RU</option>
|
||||
<option value='en'>EN</option>
|
||||
<option value='cn'>中國人</option>
|
||||
</select>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</StyledBox>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardHeader;
|
||||
|
||||
function getLanguageImage(lang: string) {
|
||||
switch (lang) {
|
||||
case 'uz': {
|
||||
return '/static/icons/uz-flag.png';
|
||||
}
|
||||
case 'en': {
|
||||
return '/static/icons/en-flag.png';
|
||||
}
|
||||
case 'ru': {
|
||||
return '/static/icons/ru-flag.webp';
|
||||
}
|
||||
case 'cn': {
|
||||
return '/static/icons/cn-flag.png';
|
||||
}
|
||||
default: {
|
||||
return '/static/icons/uz-flag.png';
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/components/layout/dashboard-layout/Header/index.ts
Normal file
1
src/components/layout/dashboard-layout/Header/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Header';
|
||||
171
src/components/layout/dashboard-layout/Sidebar/Sidebar.tsx
Normal file
171
src/components/layout/dashboard-layout/Sidebar/Sidebar.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import LogoSvg from '@/components/common/LogoSvg';
|
||||
import NextLink from '@/components/common/NextLink';
|
||||
import { routes } from '@/components/layout/dashboard-layout/routes';
|
||||
import { useAuthContext } from '@/context/auth-context';
|
||||
import { pageLinks } from '@/helpers/constants';
|
||||
import { useMyTranslation } from '@/hooks/useMyTranslation';
|
||||
import { Box, IconButton, Skeleton, Stack, Tooltip, Typography, styled } from '@mui/material';
|
||||
import React from 'react';
|
||||
|
||||
const StyledBox = styled(Box)`
|
||||
height: 100%;
|
||||
background: #3489e4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 20px;
|
||||
|
||||
.logo {
|
||||
padding: 23px 40px;
|
||||
|
||||
h2 {
|
||||
color: #fff;
|
||||
font-size: 32px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 36px;
|
||||
letter-spacing: 1.92px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav {
|
||||
padding: 60px 0 20px;
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 12px 38px;
|
||||
color: #fff;
|
||||
white-space: nowrap;
|
||||
transition:
|
||||
font-weight 0.3s ease,
|
||||
background-color 0.12s ease,
|
||||
color 0.12s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #ebf3fc;
|
||||
color: #3489e4;
|
||||
}
|
||||
|
||||
&.active-navlink {
|
||||
background-color: #ebf3fc;
|
||||
color: #3489e4;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-nav {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: auto;
|
||||
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.bottom-nav-img {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.bottom-nav-text {
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 20px; /* 111.111% */
|
||||
letter-spacing: 1.44px;
|
||||
}
|
||||
|
||||
.bottom-nav-btn {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {};
|
||||
|
||||
const Sidebar = (props: Props) => {
|
||||
const t = useMyTranslation();
|
||||
const { user, isLoading, logout } = useAuthContext();
|
||||
|
||||
const onLogout = () => {
|
||||
if (window.confirm('Do you really want to log out?')) {
|
||||
logout();
|
||||
}
|
||||
};
|
||||
|
||||
const access_routes = routes.filter(route => {
|
||||
const haveAccess = route.roles.includes(user?.role!);
|
||||
return haveAccess;
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledBox>
|
||||
<Box className='logo'>
|
||||
<Typography component='h2'>
|
||||
<NextLink href={pageLinks.home}>
|
||||
<LogoSvg />
|
||||
</NextLink>
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Stack className='nav' spacing={0} component='ul'>
|
||||
{/* {isLoading
|
||||
? [1, 2, 3].map((_, index) => (
|
||||
<NextLink className='nav-item' href={'#'} key={index}>
|
||||
<Skeleton
|
||||
sx={{
|
||||
backgroundColor: '#fff',
|
||||
width: 'calc(100% + 40px)',
|
||||
height: '50px',
|
||||
marginLeft: '-20px',
|
||||
marginRight: '-20px',
|
||||
}}
|
||||
/>
|
||||
</NextLink>
|
||||
))
|
||||
: []} */}
|
||||
{access_routes.map((route, index) => {
|
||||
return (
|
||||
<NextLink className='nav-item' href={route.path} key={index}>
|
||||
{route.icon}
|
||||
<span>{t(route.title)}</span>
|
||||
</NextLink>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
|
||||
<Stack className='bottom-nav'>
|
||||
<Tooltip title={t('log_out')}>
|
||||
<IconButton className='bottom-nav-btn' onClick={onLogout}>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='22' height='20' viewBox='0 0 22 20' fill='none'>
|
||||
<path
|
||||
d='M10 20C4.47715 20 0 15.5228 0 10C0 4.47715 4.47715 0 10 0C13.2713 0 16.1757 1.57078 18.0002 3.99923L15.2909 3.99931C13.8807 2.75499 12.0285 2 10 2C5.58172 2 2 5.58172 2 10C2 14.4183 5.58172 18 10 18C12.029 18 13.8816 17.2446 15.2919 15.9998H18.0009C16.1765 18.4288 13.2717 20 10 20ZM17 14V11H9V9H17V6L22 10L17 14Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
</svg>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</StyledBox>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
1
src/components/layout/dashboard-layout/Sidebar/index.ts
Normal file
1
src/components/layout/dashboard-layout/Sidebar/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Sidebar';
|
||||
1
src/components/layout/dashboard-layout/index.ts
Normal file
1
src/components/layout/dashboard-layout/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './DashboardLayout';
|
||||
101
src/components/layout/dashboard-layout/routes.tsx
Normal file
101
src/components/layout/dashboard-layout/routes.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import { UserRoleEnum } from '@/data/user/user.model';
|
||||
import { pageLinks } from '@/helpers/constants';
|
||||
import { SvgIcon } from '@mui/material';
|
||||
|
||||
export const routes = [
|
||||
{
|
||||
title: 'dashboard',
|
||||
path: pageLinks.dashboard.main.index,
|
||||
icon: (
|
||||
<SvgIcon fontSize='small'>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none'>
|
||||
<path
|
||||
d='M20 20.0001C20 20.5524 19.5523 21.0001 19 21.0001H5C4.44772 21.0001 4 20.5524 4 20.0001V11.0001H1L11.3273 1.61162C11.7087 1.26488 12.2913 1.26488 12.6727 1.61162L23 11.0001H20V20.0001ZM8 15.0001V17.0001H16V15.0001H8Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
),
|
||||
roles: [UserRoleEnum.ADMIN, UserRoleEnum.CHINA_WORKER],
|
||||
},
|
||||
{
|
||||
title: 'parties',
|
||||
path: pageLinks.dashboard.parties.index,
|
||||
icon: (
|
||||
<SvgIcon fontSize='small'>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none'>
|
||||
<path
|
||||
d='M5.75 3C5.57531 3.00009 5.4037 3.04594 5.25223 3.13298C5.10077 3.22002 4.97475 3.34521 4.88672 3.49609L3.13672 6.49609C3.04733 6.64899 3.00015 6.82289 3 7V19C3 20.0931 3.90694 21 5 21H19C20.0931 21 21 20.0931 21 19V7C20.9999 6.82289 20.9527 6.64899 20.8633 6.49609L19.1133 3.49609C19.0252 3.34521 18.8992 3.22002 18.7478 3.13298C18.5963 3.04594 18.4247 3.00009 18.25 3H5.75ZM6.32422 5H17.6758L18.8418 7H5.1582L6.32422 5ZM10 9H14C14.552 9 15 9.448 15 10C15 10.552 14.552 11 14 11H10C9.448 11 9 10.552 9 10C9 9.448 9.448 9 10 9Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
),
|
||||
roles: [UserRoleEnum.ADMIN, UserRoleEnum.CHINA_WORKER],
|
||||
},
|
||||
{
|
||||
title: 'packet',
|
||||
path: pageLinks.dashboard.boxes.index,
|
||||
icon: (
|
||||
<SvgIcon fontSize='small'>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none'>
|
||||
<path
|
||||
d='M17.578 4.432L15.578 3.382C13.822 2.461 12.944 2 12 2C11.056 2 10.178 2.46 8.422 3.382L8.101 3.551L17.024 8.65L21.04 6.64C20.394 5.908 19.352 5.361 17.578 4.43M21.748 7.964L17.75 9.964V13C17.75 13.1989 17.671 13.3897 17.5303 13.5303C17.3897 13.671 17.1989 13.75 17 13.75C16.8011 13.75 16.6103 13.671 16.4697 13.5303C16.329 13.3897 16.25 13.1989 16.25 13V10.714L12.75 12.464V21.904C13.468 21.725 14.285 21.297 15.578 20.618L17.578 19.568C19.729 18.439 20.805 17.875 21.403 16.86C22 15.846 22 14.583 22 12.06V11.943C22 10.05 22 8.866 21.748 7.964ZM11.25 21.904V12.464L2.252 7.964C2 8.866 2 10.05 2 11.941V12.058C2 14.583 2 15.846 2.597 16.86C3.195 17.875 4.271 18.44 6.422 19.569L8.422 20.618C9.715 21.297 10.532 21.725 11.25 21.904ZM2.96 6.641L12 11.161L15.411 9.456L6.525 4.378L6.422 4.432C4.649 5.362 3.606 5.909 2.96 6.642'
|
||||
fill='currentColor'
|
||||
/>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
),
|
||||
roles: [UserRoleEnum.ADMIN, UserRoleEnum.CHINA_WORKER],
|
||||
},
|
||||
{
|
||||
title: 'products',
|
||||
path: pageLinks.dashboard.items.index,
|
||||
icon: (
|
||||
<SvgIcon fontSize='small'>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='18' height='20' viewBox='0 0 18 20' fill='none'>
|
||||
<path
|
||||
d='M16.6667 7.77778H17.7778V20H0V7.77778H1.11111V5.55556C1.11111 2.48889 3.6 5.05038e-06 6.66667 5.05038e-06C7.45555 5.05038e-06 8.21111 0.166672 8.88889 0.466672C9.589 0.157923 10.3459 -0.00103288 11.1111 5.05038e-06C14.1778 5.05038e-06 16.6667 2.48889 16.6667 5.55556V7.77778ZM3.33333 5.55556V7.77778H5.55555V5.55556C5.55555 4.3 5.98889 3.15556 6.68889 2.22223H6.66667C4.83333 2.22223 3.33333 3.72223 3.33333 5.55556ZM14.4444 7.77778V5.55556C14.4444 3.72223 12.9444 2.22223 11.1111 2.22223H11.0889C11.8195 3.18031 12.2174 4.35069 12.2222 5.55556V7.77778H14.4444ZM8.88889 3.08889C8.21111 3.7 7.77778 4.57778 7.77778 5.55556V7.77778H10V5.55556C10 4.57778 9.56666 3.7 8.88889 3.08889Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
),
|
||||
roles: [UserRoleEnum.ADMIN, UserRoleEnum.CHINA_WORKER, UserRoleEnum.UZB_WORKER],
|
||||
},
|
||||
{
|
||||
title: 'clients',
|
||||
path: pageLinks.dashboard.customers.index,
|
||||
icon: (
|
||||
<SvgIcon fontSize='small'>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none'>
|
||||
<path
|
||||
d='M16 17.0001V19.0001H2V17.0001C2 17.0001 2 13.0001 9 13.0001C16 13.0001 16 17.0001 16 17.0001ZM12.5 7.50005C12.5 6.80782 12.2947 6.13113 11.9101 5.55556C11.5256 4.97998 10.9789 4.53138 10.3394 4.26647C9.69985 4.00157 8.99612 3.93226 8.31718 4.0673C7.63825 4.20235 7.01461 4.5357 6.52513 5.02518C6.03564 5.51466 5.7023 6.1383 5.56725 6.81724C5.4322 7.49617 5.50152 8.1999 5.76642 8.83944C6.03133 9.47899 6.47993 10.0256 7.0555 10.4102C7.63108 10.7948 8.30777 11.0001 9 11.0001C9.92826 11.0001 10.8185 10.6313 11.4749 9.97493C12.1313 9.31855 12.5 8.42831 12.5 7.50005ZM15.94 13.0001C16.5547 13.4758 17.0578 14.0805 17.4137 14.7716C17.7696 15.4626 17.9697 16.2233 18 17.0001V19.0001H22V17.0001C22 17.0001 22 13.3701 15.94 13.0001ZM15 4.00005C14.3117 3.99622 13.6385 4.20201 13.07 4.59005C13.6774 5.43879 14.0041 6.45634 14.0041 7.50005C14.0041 8.54377 13.6774 9.56132 13.07 10.4101C13.6385 10.7981 14.3117 11.0039 15 11.0001C15.9283 11.0001 16.8185 10.6313 17.4749 9.97493C18.1313 9.31855 18.5 8.42831 18.5 7.50005C18.5 6.57179 18.1313 5.68156 17.4749 5.02518C16.8185 4.3688 15.9283 4.00005 15 4.00005Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
),
|
||||
roles: [UserRoleEnum.ADMIN, UserRoleEnum.UZB_WORKER],
|
||||
},
|
||||
{
|
||||
title: 'staffs',
|
||||
path: pageLinks.dashboard.staffs.index,
|
||||
icon: (
|
||||
<SvgIcon fontSize='small'>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none'>
|
||||
<path
|
||||
d='M11.24 10.7934C13.6185 10.7934 15.5467 8.86523 15.5467 6.48672C15.5467 4.10821 13.6185 2.18005 11.24 2.18005C8.86151 2.18005 6.93335 4.10821 6.93335 6.48672C6.93335 8.86523 8.86151 10.7934 11.24 10.7934Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
<path d='M14 18.6666H18.6667V19.6H14V18.6666Z' fill='currentColor' />
|
||||
<path
|
||||
d='M9.99998 20V22C9.99998 22.1768 10.0702 22.3464 10.1952 22.4714C10.3203 22.5965 10.4898 22.6667 10.6666 22.6667H22C22.1768 22.6667 22.3464 22.5965 22.4714 22.4714C22.5964 22.3464 22.6666 22.1768 22.6666 22V15.3334C22.6666 15.1566 22.5964 14.987 22.4714 14.862C22.3464 14.7369 22.1768 14.6667 22 14.6667H17.3333V13.6867C17.3333 13.5099 17.2631 13.3403 17.1381 13.2153C17.013 13.0903 16.8435 13.02 16.6666 13.02C16.4898 13.02 16.3203 13.0903 16.1952 13.2153C16.0702 13.3403 16 13.5099 16 13.6867V14.6667H14.6666V12.28C13.5338 12.095 12.3879 12.0014 11.24 12C8.70851 11.9893 6.20541 12.5331 3.90665 13.5934C3.5283 13.7719 3.20912 14.0552 2.98696 14.4097C2.76481 14.7642 2.64901 15.175 2.65331 15.5934V20H9.99998ZM21.3333 21.3334H11.3333V16H16V16.28C16 16.4568 16.0702 16.6264 16.1952 16.7514C16.3203 16.8765 16.4898 16.9467 16.6666 16.9467C16.8435 16.9467 17.013 16.8765 17.1381 16.7514C17.2631 16.6264 17.3333 16.4568 17.3333 16.28V16H21.3333V21.3334Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
),
|
||||
roles: [UserRoleEnum.ADMIN],
|
||||
},
|
||||
];
|
||||
22
src/components/layout/landing-layout/Layout.tsx
Normal file
22
src/components/layout/landing-layout/Layout.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
'use client';
|
||||
|
||||
import Footer from '@/components/layout/landing-layout/footer';
|
||||
import Header from '@/components/layout/landing-layout/header';
|
||||
import { Box } from '@mui/material';
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const Layout = (props: Props) => {
|
||||
return (
|
||||
<Box sx={{}}>
|
||||
<Header />
|
||||
<main className='main-content'>{props.children}</main>
|
||||
<Footer />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
219
src/components/layout/landing-layout/footer/Footer.tsx
Normal file
219
src/components/layout/landing-layout/footer/Footer.tsx
Normal file
@@ -0,0 +1,219 @@
|
||||
import Container from '@/components/common/Container';
|
||||
import NextLink from '@/components/common/NextLink';
|
||||
import { pageLinks } from '@/helpers/constants';
|
||||
import { useMyTranslation } from '@/hooks/useMyTranslation';
|
||||
import { Grid, Stack, Typography, css, styled } from '@mui/material';
|
||||
import React from 'react';
|
||||
|
||||
const StyledFooter = styled('footer')`
|
||||
padding-top: 90px;
|
||||
padding-bottom: 15px;
|
||||
|
||||
.grid-container {
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
.logo-link {
|
||||
color: #0076be;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
color: #07275c;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.col-title {
|
||||
color: #07275c;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.col-link {
|
||||
color: #0076be;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.social-link-wrapper {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex-shrink: 0;
|
||||
background-color: #0076be;
|
||||
border-radius: 50%;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
filter: drop-shadow(2px 2px 2px rgba(0, 0, 0, 0.1));
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-text {
|
||||
color: #07275c;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.join-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
.join-input {
|
||||
border-radius: 23px;
|
||||
background: #dbe8f1;
|
||||
padding: 12px 16px;
|
||||
color: #222;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.join-icon {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: #1f6bbd;
|
||||
}
|
||||
|
||||
${({ theme }) => css`
|
||||
${theme.breakpoints.down('md')} {
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
type Props = {};
|
||||
|
||||
const Footer = (props: Props) => {
|
||||
const t = useMyTranslation();
|
||||
|
||||
return (
|
||||
<StyledFooter>
|
||||
<Container>
|
||||
<Grid container className='grid-container' spacing={4}>
|
||||
<Grid item xs={12} md={3}>
|
||||
<NextLink className='logo-link' href={pageLinks.home}>
|
||||
CPost
|
||||
</NextLink>
|
||||
<Typography className='info-text'>{t('footer_site_info_text')}</Typography>
|
||||
<Stack direction={'row'} spacing={1.5}>
|
||||
<NextLink className='social-link-wrapper' href='#'>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 14 14' fill='none'>
|
||||
<path
|
||||
d='M12.9573 3.29936C12.512 3.49635 12.0397 3.62574 11.5562 3.68323C12.0656 3.37827 12.4468 2.89852 12.6288 2.33336C12.1501 2.61777 11.6261 2.81795 11.0797 2.92519C10.6185 2.43312 9.97384 2.15427 9.29939 2.15503C7.95235 2.15503 6.85983 3.24719 6.85983 4.59482C6.85983 4.786 6.88176 4.97141 6.92277 5.1503C4.89444 5.04819 3.09754 4.07753 1.89396 2.60132C1.67726 2.97364 1.56337 3.39685 1.56394 3.82764C1.56394 4.67401 1.99477 5.42106 2.64921 5.85842C2.2618 5.84617 1.88293 5.74154 1.54419 5.55317C1.54382 5.56342 1.54382 5.57367 1.54382 5.58336C1.54382 6.76573 2.38478 7.75203 3.50116 7.97597C3.29164 8.03267 3.07552 8.06132 2.85846 8.06114C2.70082 8.06114 2.54783 8.04662 2.39913 8.01809C2.70977 8.98712 3.61017 9.69261 4.6781 9.71197C3.81391 10.3902 2.74674 10.7582 1.64817 10.7566C1.45374 10.7568 1.25947 10.7453 1.06641 10.7223C2.18169 11.4393 3.47989 11.8198 4.80575 11.8184C9.29344 11.8184 11.7472 8.10104 11.7472 4.87731C11.7472 4.77184 11.7446 4.666 11.7399 4.56165C12.2175 4.21659 12.6298 3.78914 12.9573 3.29936Z'
|
||||
fill='white'
|
||||
/>
|
||||
</svg>
|
||||
</NextLink>
|
||||
<NextLink className='social-link-wrapper' href='#'>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 14 14' fill='none'>
|
||||
<path
|
||||
d='M6.83335 5.08333C5.86653 5.08333 5.08333 5.86681 5.08333 6.83333C5.08333 7.80009 5.86682 8.58333 6.83335 8.58333C7.8001 8.58333 8.58335 7.79986 8.58335 6.83333C8.58335 5.86658 7.79987 5.08333 6.83335 5.08333ZM6.83335 3.91667C8.44358 3.91667 9.75001 5.22164 9.75001 6.83333C9.75001 8.44363 8.44504 9.75 6.83335 9.75C5.22306 9.75 3.91667 8.44503 3.91667 6.83333C3.91667 5.22306 5.22164 3.91667 6.83335 3.91667ZM10.625 3.77034C10.625 4.1729 10.2979 4.49952 9.89584 4.49952C9.49329 4.49952 9.16668 4.1724 9.16668 3.77034C9.16668 3.36828 9.49375 3.04167 9.89584 3.04167C10.2974 3.04116 10.625 3.36828 10.625 3.77034ZM6.83335 2.16667C5.38989 2.16667 5.15462 2.17049 4.48325 2.20038C4.02585 2.22186 3.71924 2.28337 3.43436 2.39397C3.18119 2.49216 2.99856 2.60941 2.80398 2.80399C2.60865 2.99932 2.49162 3.18145 2.39383 3.43473C2.28297 3.72027 2.22147 4.0264 2.20038 4.48317C2.1702 5.1272 2.16667 5.35228 2.16667 6.83333C2.16667 8.27679 2.17049 8.51205 2.20038 9.18335C2.22186 9.64057 2.28346 9.94763 2.39379 10.2318C2.49224 10.4854 2.60972 10.6684 2.80336 10.8621C2.99946 11.0579 3.18214 11.1753 3.43302 11.2722C3.72137 11.3836 4.02779 11.4452 4.48316 11.4663C5.1272 11.4964 5.35228 11.5 6.83335 11.5C8.2768 11.5 8.51206 11.4962 9.18336 11.4663C9.63959 11.4449 9.94689 11.3831 10.2318 11.2729C10.4847 11.1747 10.6683 11.0568 10.8621 10.8633C11.0582 10.6669 11.1754 10.4847 11.2723 10.2332C11.3835 9.94588 11.4452 9.63905 11.4663 9.18353C11.4965 8.53947 11.5 8.31436 11.5 6.83333C11.5 5.38989 11.4962 5.15462 11.4663 4.4833C11.4448 4.02695 11.3831 3.7192 11.2727 3.43436C11.1747 3.18189 11.0571 2.9987 10.8627 2.80399C10.667 2.60834 10.4854 2.49154 10.2319 2.39383C9.9466 2.28307 9.63999 2.22148 9.18348 2.20039C8.53948 2.1702 8.31437 2.16667 6.83335 2.16667ZM6.83335 1C8.41803 1 8.61589 1.00583 9.23814 1.035C9.85886 1.06368 10.2823 1.16188 10.6542 1.30625C11.0387 1.45452 11.3634 1.65479 11.6876 1.97903C12.0114 2.30327 12.2117 2.62896 12.3604 3.0125C12.5043 3.38389 12.6025 3.80778 12.6317 4.42854C12.6594 5.05077 12.6667 5.24861 12.6667 6.83333C12.6667 8.41808 12.6608 8.61588 12.6317 9.23812C12.603 9.85891 12.5043 10.2823 12.3604 10.6542C12.2121 11.0387 12.0114 11.3634 11.6876 11.6877C11.3634 12.0114 11.0372 12.2117 10.6542 12.3604C10.2823 12.5043 9.85886 12.6025 9.23814 12.6317C8.61589 12.6594 8.41803 12.6667 6.83335 12.6667C5.24861 12.6667 5.05076 12.6608 4.42854 12.6317C3.80778 12.603 3.38486 12.5043 3.0125 12.3604C2.62847 12.2121 2.30326 12.0114 1.97903 11.6877C1.65479 11.3634 1.455 11.0372 1.30625 10.6542C1.16188 10.2823 1.06417 9.85891 1.035 9.23812C1.00729 8.61588 1 8.41808 1 6.83333C1 5.24861 1.00583 5.05077 1.035 4.42854C1.06368 3.80729 1.16188 3.38438 1.30625 3.0125C1.45451 2.62847 1.65479 2.30327 1.97903 1.97903C2.30326 1.65479 2.62896 1.455 3.0125 1.30625C3.38438 1.16188 3.80729 1.06417 4.42854 1.035C5.05076 1.00729 5.24861 1 6.83335 1Z'
|
||||
fill='white'
|
||||
/>
|
||||
</svg>
|
||||
</NextLink>
|
||||
<NextLink className='social-link-wrapper' href='#'>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 14 14' fill='none'>
|
||||
<path
|
||||
d='M4.22214 3.11156C4.22194 3.56393 3.94749 3.97101 3.52821 4.14084C3.10893 4.31068 2.62855 4.20935 2.31358 3.88464C1.99861 3.55993 1.91196 3.07668 2.09448 2.66277C2.277 2.24886 2.69225 1.98693 3.14442 2.0005C3.74498 2.01853 4.22241 2.51072 4.22214 3.11156ZM4.25547 5.0448H2.03336V12H4.25547V5.0448ZM7.76642 5.0448H5.55541V12H7.7442V8.35018C7.7442 6.31694 10.3941 6.12806 10.3941 8.35018V12H12.5884V7.59466C12.5884 4.16706 8.66638 4.29484 7.7442 5.97807L7.76642 5.0448Z'
|
||||
fill='white'
|
||||
/>
|
||||
</svg>
|
||||
</NextLink>
|
||||
<NextLink className='social-link-wrapper' href='#'>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 14 14' fill='none'>
|
||||
<path
|
||||
d='M8.16683 7.87508H9.62516L10.2085 5.54175H8.16683V4.37508C8.16683 3.77461 8.16683 3.20841 9.3335 3.20841H10.2085V1.24847C10.0185 1.22324 9.30025 1.16675 8.54185 1.16675C6.9584 1.16675 5.8335 2.13325 5.8335 3.90825V5.54175H4.0835V7.87508H5.8335V12.8334H8.16683V7.87508Z'
|
||||
fill='white'
|
||||
/>
|
||||
</svg>
|
||||
</NextLink>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={3}>
|
||||
<Typography className='col-title'>{t('pages')}</Typography>
|
||||
|
||||
<Stack spacing={1.5}>
|
||||
<NextLink className='col-link' href={pageLinks.home}>
|
||||
{t('main_page')}
|
||||
</NextLink>
|
||||
<NextLink className='col-link' href={pageLinks.home}>
|
||||
{t('services')}
|
||||
</NextLink>
|
||||
{/* <NextLink className='col-link' href={pageLinks.home}>
|
||||
{t('news')}
|
||||
</NextLink> */}
|
||||
<NextLink className='col-link' href={pageLinks.home}>
|
||||
{t('about_us')}
|
||||
</NextLink>
|
||||
<NextLink className='col-link' href={pageLinks.home}>
|
||||
{t('contacts')}
|
||||
</NextLink>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={3}>
|
||||
<Typography className='col-title'>{t('info')}</Typography>
|
||||
<Stack spacing={1.5}>
|
||||
<NextLink className='col-link' href={pageLinks.home}>
|
||||
{t('privacy_policy')}
|
||||
</NextLink>
|
||||
<NextLink className='col-link' href={pageLinks.home}>
|
||||
{t('terms_of_use')}
|
||||
</NextLink>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={3}>
|
||||
<Typography className='col-title'>{t('join_tg')}</Typography>
|
||||
<Typography className='col-link' mb={2.5}>
|
||||
{t('sign_up_to_tg')}
|
||||
</Typography>
|
||||
<div className='join-wrapper'>
|
||||
<input className='join-input' type='text' placeholder={t('your_email')} />
|
||||
<div className='join-icon'>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='21' height='20' viewBox='0 0 21 20' fill='none'>
|
||||
<path
|
||||
d='M1.64978 7.76304C1.20682 7.61789 1.20332 7.3837 1.65889 7.23442L17.8383 1.93269C18.2862 1.78589 18.5432 2.0324 18.4177 2.4643L13.795 18.3695C13.667 18.8099 13.4087 18.8252 13.2194 18.4065L10.1725 11.6669L15.2587 5.00023L8.47707 10.0003L1.64978 7.76304Z'
|
||||
fill='white'
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Typography className='bottom-text'>2024 CPost. {t('all_rights_reserved')}</Typography>
|
||||
</Container>
|
||||
</StyledFooter>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
1
src/components/layout/landing-layout/footer/index.ts
Normal file
1
src/components/layout/landing-layout/footer/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Footer';
|
||||
519
src/components/layout/landing-layout/header/Header.tsx
Normal file
519
src/components/layout/landing-layout/header/Header.tsx
Normal file
@@ -0,0 +1,519 @@
|
||||
import { CalculatorModal } from '@/components/common/Calculator/Calculator';
|
||||
import { CheckOrderModal } from '@/components/common/CheckOrder';
|
||||
import Container from '@/components/common/Container';
|
||||
import NextLink from '@/components/common/NextLink';
|
||||
import { pageLinks } from '@/helpers/constants';
|
||||
import { useMyTranslation } from '@/hooks/useMyTranslation';
|
||||
import { Box, Drawer, IconButton, Stack, Typography, css, styled, Button } from '@mui/material';
|
||||
import { useLocale } from 'next-intl';
|
||||
import { Close, Menu } from '@mui/icons-material';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useState } from 'react';
|
||||
import LogoSvg from '@/components/common/LogoSvg';
|
||||
import { VerifyPhoneModal } from '@/components/common/VerifyPhoneModal';
|
||||
import { IRegisterModal } from '@/components/layout/landing-layout/header/components/IRegisterModal';
|
||||
import ILoginBtn from '@/components/layout/landing-layout/header/components/ILoginBtn';
|
||||
import IDollarYuan from '@/components/layout/landing-layout/header/components/IDollarYuan';
|
||||
|
||||
const StyledHeader = styled('header')`
|
||||
z-index: 999;
|
||||
position: sticky;
|
||||
left: 0;
|
||||
top: -43px;
|
||||
|
||||
.top-header {
|
||||
background-color: #07275c;
|
||||
padding: 8px 0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.top-header-link {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: 0.14px;
|
||||
}
|
||||
|
||||
.social-link {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
// ---------------------------------
|
||||
|
||||
.main-header {
|
||||
background: #e5eef6;
|
||||
padding: 16px 0;
|
||||
|
||||
.logo-link {
|
||||
color: #003a77;
|
||||
font-size: 28px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 36px; /* 128.571% */
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: #07275c;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 20px; /* 125% */
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.lng-img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.lng-select {
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
color: #07275c;
|
||||
}
|
||||
|
||||
.calc-btn {
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
border-radius: 4px;
|
||||
border: 2px solid #0076be;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.check-btn {
|
||||
display: flex;
|
||||
padding: 13px 12px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
border-radius: 4px;
|
||||
background: #0076be;
|
||||
color: #fff;
|
||||
transition: opacity 0.3s ease;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${({ theme }) => css`
|
||||
${theme.breakpoints.down('md')} {
|
||||
top: 0;
|
||||
|
||||
.main-header {
|
||||
padding: 10px 0;
|
||||
}
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const StyledDrawer = styled(Drawer)`
|
||||
.logo-link {
|
||||
color: #003a77;
|
||||
font-size: 28px;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 36px; /* 128.571% */
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: #07275c;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 20px; /* 125% */
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.lng-img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.lng-select {
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
color: #07275c;
|
||||
}
|
||||
|
||||
.calc-btn {
|
||||
display: flex;
|
||||
padding: 10px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
border-radius: 4px;
|
||||
border: 2px solid #0076be;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.check-btn {
|
||||
display: flex;
|
||||
padding: 13px 12px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
border-radius: 4px;
|
||||
background: #0076be;
|
||||
color: #fff;
|
||||
transition: opacity 0.3s ease;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {};
|
||||
|
||||
const Header = (props: Props) => {
|
||||
const t = useMyTranslation();
|
||||
const language = useLocale();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const [isOpenMenu, setIsOpenMenu] = useState(false);
|
||||
const [modal, setModal] = useState<'calculator' | 'check_order' | 'verify_phone' | 'register' | null>(null);
|
||||
const [phoneNumber, setPhoneNumber] = useState('');
|
||||
|
||||
const openCalculatorModal = () => {
|
||||
setModal('calculator');
|
||||
};
|
||||
|
||||
const changePhone = (value: string) => {
|
||||
setPhoneNumber(value);
|
||||
};
|
||||
|
||||
const openCheckOrderModal = () => {
|
||||
setModal('check_order');
|
||||
};
|
||||
|
||||
const openVerifyPhoneModal = () => {
|
||||
setModal('verify_phone');
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setModal(null);
|
||||
};
|
||||
|
||||
const changeLanguage = (lng: string) => {
|
||||
router.push(`/${lng}/${pathname.substring(3)}`, { scroll: false });
|
||||
};
|
||||
|
||||
const openRegister = () => setModal('register');
|
||||
|
||||
return (
|
||||
<StyledHeader>
|
||||
<Box
|
||||
className='top-header'
|
||||
display={{
|
||||
xs: 'none',
|
||||
md: 'block',
|
||||
}}
|
||||
>
|
||||
<Container>
|
||||
<Stack direction={'row'} justifyContent={'space-between'}>
|
||||
<Stack direction={'row'} spacing={1.5}>
|
||||
<Stack direction={'row'} alignItems={'center'} spacing={1.5}>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='none'>
|
||||
<path
|
||||
d='M17.5 13.6833V16.6301C17.5 17.0676 17.1617 17.4306 16.7254 17.4614C16.3609 17.4872 16.0636 17.5 15.8333 17.5C8.4695 17.5 2.5 11.5305 2.5 4.16667C2.5 3.93642 2.51288 3.63906 2.53863 3.27458C2.56948 2.83823 2.93245 2.5 3.36988 2.5H6.31675C6.53065 2.5 6.7098 2.66202 6.73127 2.87483C6.75056 3.06589 6.76848 3.21928 6.78506 3.33502C6.95362 4.51227 7.29794 5.6328 7.79058 6.66919C7.86966 6.83554 7.81809 7.03466 7.66821 7.14172L5.86962 8.4265C6.9646 10.9843 9.01575 13.0354 11.5735 14.1304L12.8559 12.3349C12.9643 12.1832 13.1658 12.1311 13.3342 12.211C14.3705 12.7032 15.4909 13.0472 16.668 13.2153C16.783 13.2318 16.9354 13.2496 17.1252 13.2687C17.338 13.2902 17.5 13.4694 17.5 13.6833Z'
|
||||
fill='white'
|
||||
/>
|
||||
</svg>
|
||||
<NextLink className='top-header-link' href='#'>
|
||||
+998 (99) 110 22 22
|
||||
</NextLink>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='2' height='20' viewBox='0 0 2 20' fill='none'>
|
||||
<path d='M1 0L0.999999 20' stroke='white' stroke-width='2' />
|
||||
</svg>
|
||||
<NextLink className='top-header-link' href='#'>
|
||||
+86 (150) 2452 0047
|
||||
</NextLink>
|
||||
</Stack>
|
||||
<Stack direction={'row'} alignItems={'center'} spacing={1.5}>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='none'>
|
||||
<path
|
||||
d='M2.49984 2.5H17.4998C17.9601 2.5 18.3332 2.8731 18.3332 3.33333V16.6667C18.3332 17.1269 17.9601 17.5 17.4998 17.5H2.49984C2.0396 17.5 1.6665 17.1269 1.6665 16.6667V3.33333C1.6665 2.8731 2.0396 2.5 2.49984 2.5ZM16.6665 6.0316L10.0597 11.9483L3.33317 6.01328V15.8333H16.6665V6.0316ZM3.75939 4.16667L10.0514 9.71833L16.2507 4.16667H3.75939Z'
|
||||
fill='white'
|
||||
/>
|
||||
</svg>
|
||||
<NextLink className='top-header-link' href='#'>
|
||||
info@cpost.uz
|
||||
</NextLink>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Stack direction={'row'} spacing={2} display={'flex'} alignItems={'center'}>
|
||||
<IDollarYuan />
|
||||
{/*<Typography>1 USD = 7.1 Y</Typography>*/}
|
||||
{/*<NextLink className='social-link'>*/}
|
||||
{/* <svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='none'>*/}
|
||||
{/* <path*/}
|
||||
{/* d='M6.66536 2C4.0927 2 2 4.09464 2 6.66797V13.3346C2 15.9073 4.09464 18 6.66797 18H13.3346C15.9073 18 18 15.9054 18 13.332V6.66536C18 4.0927 15.9054 2 13.332 2H6.66536ZM14.6667 4.66667C15.0347 4.66667 15.3333 4.96533 15.3333 5.33333C15.3333 5.70133 15.0347 6 14.6667 6C14.2987 6 14 5.70133 14 5.33333C14 4.96533 14.2987 4.66667 14.6667 4.66667ZM10 6C12.206 6 14 7.794 14 10C14 12.206 12.206 14 10 14C7.794 14 6 12.206 6 10C6 7.794 7.794 6 10 6ZM10 7.33333C9.29276 7.33333 8.61448 7.61428 8.11438 8.11438C7.61428 8.61448 7.33333 9.29276 7.33333 10C7.33333 10.7072 7.61428 11.3855 8.11438 11.8856C8.61448 12.3857 9.29276 12.6667 10 12.6667C10.7072 12.6667 11.3855 12.3857 11.8856 11.8856C12.3857 11.3855 12.6667 10.7072 12.6667 10C12.6667 9.29276 12.3857 8.61448 11.8856 8.11438C11.3855 7.61428 10.7072 7.33333 10 7.33333Z'*/}
|
||||
{/* fill='white'*/}
|
||||
{/* />*/}
|
||||
{/* </svg>*/}
|
||||
{/*</NextLink>*/}
|
||||
{/*<NextLink className='social-link'>*/}
|
||||
{/* <svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='none'>*/}
|
||||
{/* <path*/}
|
||||
{/* d='M6.66536 2C4.0927 2 2 4.09464 2 6.66797V13.3346C2 15.9073 4.09464 18 6.66797 18H13.3346C15.9073 18 18 15.9054 18 13.332V6.66536C18 4.0927 15.9054 2 13.332 2H6.66536ZM14.6667 4.66667C15.0347 4.66667 15.3333 4.96533 15.3333 5.33333C15.3333 5.70133 15.0347 6 14.6667 6C14.2987 6 14 5.70133 14 5.33333C14 4.96533 14.2987 4.66667 14.6667 4.66667ZM10 6C12.206 6 14 7.794 14 10C14 12.206 12.206 14 10 14C7.794 14 6 12.206 6 10C6 7.794 7.794 6 10 6ZM10 7.33333C9.29276 7.33333 8.61448 7.61428 8.11438 8.11438C7.61428 8.61448 7.33333 9.29276 7.33333 10C7.33333 10.7072 7.61428 11.3855 8.11438 11.8856C8.61448 12.3857 9.29276 12.6667 10 12.6667C10.7072 12.6667 11.3855 12.3857 11.8856 11.8856C12.3857 11.3855 12.6667 10.7072 12.6667 10C12.6667 9.29276 12.3857 8.61448 11.8856 8.11438C11.3855 7.61428 10.7072 7.33333 10 7.33333Z'*/}
|
||||
{/* fill='white'*/}
|
||||
{/* />*/}
|
||||
{/* </svg>*/}
|
||||
{/*</NextLink>*/}
|
||||
{/*<NextLink className='social-link'>*/}
|
||||
{/* <svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='none'>*/}
|
||||
{/* <path*/}
|
||||
{/* d='M2.42918 9.84011C7.66585 7.53636 14.4425 4.72761 15.3779 4.33886C17.8317 3.32136 18.5846 3.51636 18.2092 5.76969C17.9396 7.38928 17.1617 12.7514 16.5417 16.0889C16.1738 18.068 15.3483 18.3026 14.0504 17.4464C13.4263 17.0343 10.2758 14.9509 9.5921 14.4618C8.96793 14.0159 8.1071 13.4797 9.18668 12.4234C9.57085 12.0472 12.0892 9.64261 14.0513 7.77094C14.3083 7.52511 13.9854 7.12136 13.6888 7.31844C11.0442 9.07219 7.37751 11.5064 6.91085 11.8234C6.20585 12.3022 5.52876 12.5218 4.31335 12.1726C3.39501 11.9089 2.49793 11.5943 2.14876 11.4743C0.804182 11.0126 1.12335 10.4147 2.42918 9.84011Z'*/}
|
||||
{/* fill='white'*/}
|
||||
{/* />*/}
|
||||
{/* </svg>*/}
|
||||
{/*</NextLink>*/}
|
||||
<ILoginBtn openVerifyPhoneModal={openVerifyPhoneModal} />
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Container>
|
||||
</Box>
|
||||
<div className='main-header'>
|
||||
<Container>
|
||||
<Stack direction={'row'} justifyContent={'space-between'} alignItems={'center'} spacing={7.5}>
|
||||
<NextLink className='logo-link' href={pageLinks.home}>
|
||||
<LogoSvg />
|
||||
</NextLink>
|
||||
<Stack direction={'row'} alignItems={'center'} spacing={2} display={{ xs: 'none', md: 'flex' }}>
|
||||
<NextLink className='nav-link' href={pageLinks.home}>
|
||||
{t('home')}
|
||||
</NextLink>
|
||||
<NextLink className='nav-link' href={pageLinks.home + '#about'}>
|
||||
{t('about_us')}
|
||||
</NextLink>
|
||||
<NextLink className='nav-link' href={pageLinks.home + '#services'}>
|
||||
{t('services')}
|
||||
</NextLink>
|
||||
{/* <NextLink className='nav-link' href={pageLinks.news}>
|
||||
{t('news')}
|
||||
</NextLink> */}
|
||||
<NextLink className='nav-link' href={pageLinks.home + '#tariffs'}>
|
||||
{t('tariffs')}
|
||||
</NextLink>
|
||||
<NextLink className='nav-link' href={pageLinks.home + '#contacts'}>
|
||||
{t('contacts')}
|
||||
</NextLink>
|
||||
</Stack>
|
||||
<Stack direction={'row'} alignItems={'center'} spacing={2}>
|
||||
<Stack direction={'row'} alignItems={'center'} spacing={2} display={{ xs: 'none', sm: 'flex' }}>
|
||||
<img className='lng-img' src={getLanguageImage(language)} alt='' />
|
||||
|
||||
<select
|
||||
className='lng-select'
|
||||
value={language}
|
||||
onChange={event => {
|
||||
changeLanguage(event.target.value);
|
||||
}}
|
||||
>
|
||||
<option value='uz'>UZ</option>
|
||||
<option value='ru'>RU</option>
|
||||
<option value='en'>EN</option>
|
||||
<option value='cn'>中國人</option>
|
||||
</select>
|
||||
|
||||
<button className='calc-btn' onClick={openCalculatorModal}>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='none'>
|
||||
<path
|
||||
d='M2 0H18C18.5523 0 19 0.44772 19 1V19C19 19.5523 18.5523 20 18 20H2C1.44772 20 1 19.5523 1 19V1C1 0.44772 1.44772 0 2 0ZM3 2V18H17V2H3ZM5 4H15V8H5V4ZM5 10H7V12H5V10ZM5 14H7V16H5V14ZM9 10H11V12H9V10ZM9 14H11V16H9V14ZM13 10H15V16H13V10Z'
|
||||
fill='#0076BE'
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button className='check-btn' onClick={openCheckOrderModal}>
|
||||
{t('check_order')}
|
||||
</button>
|
||||
</Stack>
|
||||
|
||||
<IconButton
|
||||
sx={{
|
||||
display: { xs: 'flex', md: 'none' },
|
||||
}}
|
||||
onClick={() => setIsOpenMenu(p => !p)}
|
||||
>
|
||||
<Menu />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Container>
|
||||
</div>
|
||||
|
||||
{burgerMenu()}
|
||||
<CalculatorModal onClose={closeModal} open={modal === 'calculator'} />
|
||||
<CheckOrderModal onClose={closeModal} open={modal === 'check_order'} />
|
||||
<VerifyPhoneModal changePhone={changePhone} openRegister={openRegister} onClose={closeModal} phoneNumber={phoneNumber} open={modal === 'verify_phone'} />
|
||||
<IRegisterModal phoneNumber={phoneNumber} onClose={closeModal} open={modal === 'register'} />
|
||||
</StyledHeader>
|
||||
);
|
||||
|
||||
function burgerMenu() {
|
||||
return (
|
||||
<StyledDrawer
|
||||
open={isOpenMenu}
|
||||
onClose={() => setIsOpenMenu(false)}
|
||||
anchor='right'
|
||||
sx={{
|
||||
'.MuiPaper-root': {
|
||||
padding: '10px 12px',
|
||||
width: '100%',
|
||||
maxWidth: '400px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2}>
|
||||
<Stack direction={'row'} alignItems={'center'} justifyContent={'space-between'}>
|
||||
<NextLink className='logo-link' href={pageLinks.home}>
|
||||
<LogoSvg />
|
||||
</NextLink>
|
||||
<IconButton onClick={() => setIsOpenMenu(false)}>
|
||||
<Close />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
|
||||
<NextLink className='nav-link' href={pageLinks.home} onClick={() => setIsOpenMenu(false)}>
|
||||
{t('home')}
|
||||
</NextLink>
|
||||
<NextLink className='nav-link' href={pageLinks.home + '#about'} onClick={() => setIsOpenMenu(false)}>
|
||||
{t('about_us')}
|
||||
</NextLink>
|
||||
<NextLink className='nav-link' href={pageLinks.home + '#services'} onClick={() => setIsOpenMenu(false)}>
|
||||
{t('services')}
|
||||
</NextLink>
|
||||
{/* <NextLink className='nav-link' href={pageLinks.news} onClick={() => setIsOpenMenu(false)}>
|
||||
{t('news')}
|
||||
</NextLink> */}
|
||||
<NextLink className='nav-link' href={pageLinks.home + '#tariffs'} onClick={() => setIsOpenMenu(false)}>
|
||||
{t('tariffs')}
|
||||
</NextLink>
|
||||
<NextLink className='nav-link' href={pageLinks.home + '#contacts'} onClick={() => setIsOpenMenu(false)}>
|
||||
{t('contacts')}
|
||||
</NextLink>
|
||||
|
||||
<Stack spacing={2} display={{ xs: 'flex' }}>
|
||||
<Stack spacing={2} display={{ sm: 'none' }}>
|
||||
<button className='calc-btn' onClick={openCalculatorModal}>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='none'>
|
||||
<path
|
||||
d='M2 0H18C18.5523 0 19 0.44772 19 1V19C19 19.5523 18.5523 20 18 20H2C1.44772 20 1 19.5523 1 19V1C1 0.44772 1.44772 0 2 0ZM3 2V18H17V2H3ZM5 4H15V8H5V4ZM5 10H7V12H5V10ZM5 14H7V16H5V14ZM9 10H11V12H9V10ZM9 14H11V16H9V14ZM13 10H15V16H13V10Z'
|
||||
fill='#0076BE'
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button className='check-btn' onClick={openCheckOrderModal}>
|
||||
{t('check_order')}
|
||||
</button>
|
||||
</Stack>
|
||||
|
||||
<button className='check-btn' onClick={openVerifyPhoneModal}>
|
||||
<svg width='18' height='18' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path
|
||||
d='M8 7C9.65685 7 11 5.65685 11 4C11 2.34315 9.65685 1 8 1C6.34315 1 5 2.34315 5 4C5 5.65685 6.34315 7 8 7Z'
|
||||
fill='#fff'
|
||||
/>
|
||||
<path d='M14 12C14 10.3431 12.6569 9 11 9H5C3.34315 9 2 10.3431 2 12V15H14V12Z' fill='#fff' />
|
||||
</svg>
|
||||
<span>Hisobga Kirish</span>
|
||||
</button>
|
||||
|
||||
<Stack display={{ sm: 'none' }} direction={'row'} justifyContent={'flex-start'} alignItems={'center'}>
|
||||
<img className='lng-img' src={getLanguageImage(language)} alt='' />
|
||||
|
||||
<select
|
||||
className='lng-select'
|
||||
value={language}
|
||||
onChange={event => {
|
||||
changeLanguage(event.target.value);
|
||||
}}
|
||||
>
|
||||
<option value='uz'>UZ</option>
|
||||
<option value='ru'>RU</option>
|
||||
<option value='en'>EN</option>
|
||||
<option value='cn'>中國人</option>
|
||||
</select>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</StyledDrawer>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Header;
|
||||
|
||||
function getLanguageImage(lang: string) {
|
||||
switch (lang) {
|
||||
case 'uz': {
|
||||
return '/static/icons/uz-flag.png';
|
||||
}
|
||||
case 'en': {
|
||||
return '/static/icons/en-flag.png';
|
||||
}
|
||||
case 'ru': {
|
||||
return '/static/icons/ru-flag.webp';
|
||||
}
|
||||
case 'cn': {
|
||||
return '/static/icons/cn-flag.png';
|
||||
}
|
||||
default: {
|
||||
return '/static/icons/uz-flag.png';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
'use client';
|
||||
|
||||
import { Typography } from '@mui/material';
|
||||
import { useEffect, useState } from 'react';
|
||||
import axios from 'axios';
|
||||
|
||||
export default function IDollarYuan() {
|
||||
const [yuan, setYuan] = useState<string | undefined>('');
|
||||
|
||||
useEffect(() => {
|
||||
axios.get('https://api.exchangerate-api.com/v4/latest/USD').then(res => {
|
||||
setYuan(res.data?.rates?.CNY);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (!yuan) return null;
|
||||
|
||||
return <Typography>1 USD = {yuan} Y</Typography>;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '@mui/material';
|
||||
import React from 'react';
|
||||
import { useMyNavigation } from '@/hooks/useMyNavigation';
|
||||
|
||||
interface IProps {
|
||||
openVerifyPhoneModal: () => void;
|
||||
}
|
||||
|
||||
export default function ILoginBtn({ openVerifyPhoneModal }: IProps) {
|
||||
const token = typeof window !== "undefined" ? localStorage.getItem('token') : "";
|
||||
const { replace } = useMyNavigation();
|
||||
|
||||
const handleProfile = () => {
|
||||
if (token) {
|
||||
replace('/profile');
|
||||
} else {
|
||||
openVerifyPhoneModal();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={handleProfile}
|
||||
sx={{
|
||||
backgroundColor: 'transparent',
|
||||
padding: 0,
|
||||
textTransform: 'capitalize',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 0.5,
|
||||
}}
|
||||
>
|
||||
<svg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<path
|
||||
d='M8 7C9.65685 7 11 5.65685 11 4C11 2.34315 9.65685 1 8 1C6.34315 1 5 2.34315 5 4C5 5.65685 6.34315 7 8 7Z'
|
||||
fill='#fff'
|
||||
/>
|
||||
<path d='M14 12C14 10.3431 12.6569 9 11 9H5C3.34315 9 2 10.3431 2 12V15H14V12Z' fill='#fff' />
|
||||
</svg>
|
||||
<span>{token ? 'Profil' : 'Kirish'}</span>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Box, TextField } from '@mui/material';
|
||||
import { IRegisterProps } from '@/components/layout/landing-layout/header/components/IRegisterModal';
|
||||
|
||||
|
||||
export const IRegisterFirstStep = ({ formDataChanging, formData, dataErrors }: IRegisterProps) => {
|
||||
return (
|
||||
<Box display={'flex'} flexDirection={'column'} gap={3}>
|
||||
<TextField
|
||||
error={dataErrors.first_name}
|
||||
label='Ism'
|
||||
variant='outlined'
|
||||
fullWidth
|
||||
value={formData.first_name}
|
||||
onChange={e => formDataChanging({ key: 'first_name', value: e.target.value })}
|
||||
/>
|
||||
<TextField
|
||||
error={dataErrors.last_name}
|
||||
label='Familiya'
|
||||
variant='outlined'
|
||||
fullWidth
|
||||
value={formData.last_name}
|
||||
onChange={e => formDataChanging({ key: 'last_name', value: e.target.value })}
|
||||
/>
|
||||
<TextField
|
||||
error={dataErrors.birth_date}
|
||||
label="Tug'ilgan sana"
|
||||
type='date'
|
||||
fullWidth
|
||||
InputLabelProps={{ shrink: true }}
|
||||
value={formData.birth_date}
|
||||
onChange={e => formDataChanging({ key: 'birth_date', value: e.target.value })}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Box, Button, TextField, Typography } from '@mui/material';
|
||||
import { IRegisterProps } from '@/components/layout/landing-layout/header/components/IRegisterModal';
|
||||
import MCodeInput from '@/components/common/MComponents/MCodeInput';
|
||||
import React from 'react';
|
||||
|
||||
export function IRegisterFourthStep({ formDataChanging, formData, dataErrors }: IRegisterProps) {
|
||||
return (
|
||||
<Box display={'flex'} flexDirection={'column'} gap={3}>
|
||||
{/*<Typography variant='body1' mb={2}>*/}
|
||||
{/* Iltimos, SMS orqali yuborilgan 4 xonali kodni kiriting:*/}
|
||||
{/*</Typography>*/}
|
||||
|
||||
{/*<MCodeInput clearError={clearCodesError} isError={inputErrors.code} onCodeChange={setVerificationCode} />*/}
|
||||
|
||||
{/*<Box sx={{ mt: 3 }} display='flex' justifyContent={'end'} gap={2}>*/}
|
||||
{/* <Button variant='outlined' onClick={handleBack}>*/}
|
||||
{/* Orqaga*/}
|
||||
{/* </Button>*/}
|
||||
|
||||
{/* <Button variant='contained' color='primary' onClick={handleVerifyCode}>*/}
|
||||
{/* Tasdiqlash*/}
|
||||
{/* </Button>*/}
|
||||
{/*</Box>*/}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
'use client';
|
||||
|
||||
import { Box, Button, Modal, Typography } from '@mui/material';
|
||||
import React, { useState } from 'react';
|
||||
import { IRegisterFirstStep } from '@/components/layout/landing-layout/header/components/IRegisterFirstStep';
|
||||
import { IRegisterSecondStep } from '@/components/layout/landing-layout/header/components/IRegisterSecondStep';
|
||||
import { IRegisterThirdStep } from '@/components/layout/landing-layout/header/components/IRegisterThirdStep';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import toast from 'react-hot-toast';
|
||||
import axios from 'axios';
|
||||
import { BASE_URL } from '@/helpers/constants';
|
||||
import { toBase64 } from '@/helpers/toBase64';
|
||||
import MCodeInput from '@/components/common/MComponents/MCodeInput';
|
||||
import myAxios from '@/helpers/myAxios';
|
||||
import authRequest from '@/helpers/authRequest';
|
||||
|
||||
// import { IRegisterFourthStep } from '@/components/layout/landing-layout/header/components/IRegisterFourthStep';
|
||||
|
||||
interface IProps {
|
||||
onClose: () => void;
|
||||
open: boolean;
|
||||
phoneNumber: string;
|
||||
}
|
||||
|
||||
export interface IRegisterData {
|
||||
passport_serie: string;
|
||||
passport_pnfl: string;
|
||||
passport_front: File | null;
|
||||
passport_back: File | null;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
birth_date: string;
|
||||
address: string;
|
||||
filial: string;
|
||||
password: string;
|
||||
password_confirmation: string;
|
||||
}
|
||||
|
||||
export interface IErrorsData {
|
||||
passport_serie: boolean;
|
||||
passport_pnfl: boolean;
|
||||
passport_front: boolean;
|
||||
passport_back: boolean;
|
||||
first_name: boolean;
|
||||
last_name: boolean;
|
||||
birth_date: boolean;
|
||||
address: boolean;
|
||||
filial: boolean;
|
||||
password: boolean;
|
||||
password_confirmation: boolean;
|
||||
}
|
||||
|
||||
export interface IRegisterProps {
|
||||
formData: IRegisterData;
|
||||
formDataChanging: ({ key, value }: { key: string; value: string | number | File | null }) => void;
|
||||
dataErrors: IErrorsData;
|
||||
}
|
||||
|
||||
const style = {
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: { xs: '90%', sm: 400 },
|
||||
bgcolor: 'background.paper',
|
||||
borderRadius: 2,
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
};
|
||||
|
||||
const initialValues: IRegisterData = {
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
birth_date: '',
|
||||
passport_pnfl: '',
|
||||
passport_serie: '',
|
||||
passport_back: null,
|
||||
passport_front: null,
|
||||
address: '',
|
||||
filial: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
};
|
||||
|
||||
const initialErrorValues: IErrorsData = {
|
||||
first_name: false,
|
||||
last_name: false,
|
||||
birth_date: false,
|
||||
passport_pnfl: false,
|
||||
passport_serie: false,
|
||||
passport_back: false,
|
||||
passport_front: false,
|
||||
address: false,
|
||||
filial: false,
|
||||
password: false,
|
||||
password_confirmation: false,
|
||||
};
|
||||
|
||||
export function IRegisterModal({ open, onClose, phoneNumber }: IProps) {
|
||||
const pathname = usePathname();
|
||||
const locale = pathname.split('/')[1] || 'uz';
|
||||
const router = useRouter();
|
||||
const [step, setStep] = useState<number>(1);
|
||||
const [formData, setFormData] = useState<IRegisterData>(initialValues);
|
||||
const [dataErrors, setDataErrors] = useState<IErrorsData>(initialErrorValues);
|
||||
const [verifyCode, setVerifyCode] = useState('');
|
||||
|
||||
const formDataChanging = ({ key, value }: { key: string; value: string | number | File | null }): void => {
|
||||
setDataErrors(prev => ({ ...prev, [key]: false }));
|
||||
setFormData({ ...formData, [key]: value });
|
||||
};
|
||||
|
||||
const nextStepHandler = async () => {
|
||||
if (step === 1) {
|
||||
if (!formData.first_name) setDataErrors(prev => ({ ...prev, first_name: true }));
|
||||
if (!formData.last_name) setDataErrors(prev => ({ ...prev, last_name: true }));
|
||||
if (!formData.birth_date) setDataErrors(prev => ({ ...prev, birth_date: true }));
|
||||
|
||||
if (!formData.first_name || !formData.last_name || !formData.birth_date) return;
|
||||
} else if (step === 2) {
|
||||
if (!formData.passport_pnfl) setDataErrors(prev => ({ ...prev, passport_pnfl: true }));
|
||||
if (!formData.passport_serie) setDataErrors(prev => ({ ...prev, passport_serie: true }));
|
||||
if (!formData.passport_front) setDataErrors(prev => ({ ...prev, passport_front: true }));
|
||||
if (!formData.passport_back) setDataErrors(prev => ({ ...prev, passport_back: true }));
|
||||
|
||||
if (!formData.passport_pnfl || !formData.passport_serie || !formData.passport_front || !formData.passport_back) return;
|
||||
} else if (step === 3) {
|
||||
if (!formData.address) setDataErrors(prev => ({ ...prev, address: true }));
|
||||
if (!formData.filial) setDataErrors(prev => ({ ...prev, filial: true }));
|
||||
|
||||
if (!formData.address || !formData.filial) return;
|
||||
|
||||
try {
|
||||
await authRequest.post('/profile/send-otp', { phone: phoneNumber });
|
||||
toast.success("Tasdiqlash ko'di yuborildi");
|
||||
} catch (error: any) {
|
||||
toast.error(error?.message || "Tasdiqlash ko'di yuborilmadi");
|
||||
return;
|
||||
}
|
||||
} else if (step === 4) {
|
||||
if (verifyCode.length !== 4) return;
|
||||
}
|
||||
|
||||
if (step !== 4) return setStep(prev => prev + 1);
|
||||
|
||||
const fData = {
|
||||
name: formData.first_name + ' ' + formData.last_name,
|
||||
phone: phoneNumber,
|
||||
passportSeries: formData.passport_serie,
|
||||
pinfl: formData.passport_pnfl,
|
||||
dateOfBirth: formData.birth_date,
|
||||
address: formData.address,
|
||||
region: formData.filial,
|
||||
passportFront: formData.passport_front ? await toBase64(formData.passport_front) : '',
|
||||
passportBack: formData.passport_back ? await toBase64(formData.passport_back) : '',
|
||||
code: verifyCode,
|
||||
};
|
||||
|
||||
toast
|
||||
.promise(axios.post(BASE_URL + '/api/v1/profile/sign-up', fData), {
|
||||
loading: 'Loading...',
|
||||
success: 'Ma’lumotlaringiz ko‘rib chiqilmoqda. Iltimos, kuting.',
|
||||
error: 'Hatolik yuz berdi',
|
||||
})
|
||||
.then(() => {
|
||||
axios
|
||||
.post(BASE_URL + '/api/v1/profile/sign-in', {
|
||||
phone: phoneNumber,
|
||||
code: '1234',
|
||||
})
|
||||
.then(response => {
|
||||
// router.push(`/${locale}/profile`);
|
||||
setStep(1);
|
||||
setFormData(initialValues);
|
||||
onClose();
|
||||
|
||||
// const data = response.data?.data;
|
||||
// localStorage.setItem('token', data?.accessToken);
|
||||
// localStorage.setItem('username', data?.username);
|
||||
// localStorage.setItem('roles', data?.role?.[0]);
|
||||
})
|
||||
.catch((error: any) => {
|
||||
if(axios.isAxiosError(error) && error.response?.status === 413) {
|
||||
toast.error('Katta hajmdagi fayl yubordingiz. Iltimos, kichikroq hajmli fayl tanlang.');
|
||||
}
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
const decStepHandler = () => {
|
||||
if (step === 1) {
|
||||
onClose();
|
||||
} else {
|
||||
setStep(prev => {
|
||||
if (prev > 1) return prev - 1;
|
||||
return prev;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleVerifyCode = () => {};
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<Box sx={style}>
|
||||
<Typography variant='h6' align={'center'} fontSize={'1.5rem'} fontWeight={'bolder'} component='h2' mb={2}>
|
||||
{`Ro'yxatdan o'tish`}
|
||||
</Typography>
|
||||
|
||||
{step === 1 && <IRegisterFirstStep formDataChanging={formDataChanging} formData={formData} dataErrors={dataErrors} />}
|
||||
{step === 2 && <IRegisterSecondStep formDataChanging={formDataChanging} formData={formData} dataErrors={dataErrors} />}
|
||||
{step === 3 && <IRegisterThirdStep formDataChanging={formDataChanging} formData={formData} dataErrors={dataErrors} />}
|
||||
|
||||
{/*{step === 4 && <IRegisterFourthStep formDataChanging={formDataChanging} formData={formData} dataErrors={dataErrors} />}*/}
|
||||
|
||||
{step === 4 && (
|
||||
<Box display={'flex'} flexDirection={'column'} gap={3}>
|
||||
<Typography variant='body1' mb={2}>
|
||||
Iltimos, SMS orqali yuborilgan 4 xonali kodni kiriting:
|
||||
</Typography>
|
||||
|
||||
{/*<MCodeInput clearError={clearCodesError} isError={inputErrors.code} onCodeChange={value => setVerifyCode(value)} />*/}
|
||||
<MCodeInput isError={false} onCodeChange={value => setVerifyCode(value)} />
|
||||
|
||||
{/*<Box sx={{ mt: 3 }} display='flex' justifyContent={'end'} gap={2}>*/}
|
||||
{/* <Button variant='outlined' onClick={() => setStep(3)}>*/}
|
||||
{/* Orqaga*/}
|
||||
{/* </Button>*/}
|
||||
|
||||
{/* <Button variant='contained' color='primary' onClick={handleVerifyCode}>*/}
|
||||
{/* Tasdiqlash*/}
|
||||
{/* </Button>*/}
|
||||
{/*</Box>*/}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box sx={{ marginTop: 2, display: 'flex', justifyContent: 'end', gap: 2 }}>
|
||||
<Button variant='outlined' onClick={decStepHandler}>
|
||||
{step === 1 ? 'Bekor qilish' : 'Qaytish'}
|
||||
</Button>
|
||||
<Button variant='contained' onClick={nextStepHandler}>
|
||||
{step === 4 ? 'Yuborish' : 'Keyingi'}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
'use client';
|
||||
|
||||
import { IRegisterProps } from '@/components/layout/landing-layout/header/components/IRegisterModal';
|
||||
import { Box, Card, TextField } from '@mui/material';
|
||||
|
||||
export function IRegisterSecondStep({ formData, formDataChanging, dataErrors }: IRegisterProps) {
|
||||
const handleFrontChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (!e.target.files?.[0]) return;
|
||||
formDataChanging({
|
||||
key: 'passport_front',
|
||||
value: e.target.files[0],
|
||||
});
|
||||
};
|
||||
const handleBackChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (!e.target.files?.[0]) return;
|
||||
formDataChanging({
|
||||
key: 'passport_back',
|
||||
value: e.target.files[0],
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Box display='flex' flexDirection='column' gap={2}>
|
||||
<TextField
|
||||
label='Passport Seriya/Raqami'
|
||||
variant='outlined'
|
||||
error={dataErrors.passport_serie}
|
||||
fullWidth
|
||||
value={formData.passport_serie}
|
||||
onChange={e => formDataChanging({ key: 'passport_serie', value: e.target.value })}
|
||||
/>
|
||||
<TextField
|
||||
label='PINFL'
|
||||
variant='outlined'
|
||||
error={dataErrors.passport_pnfl}
|
||||
fullWidth
|
||||
value={formData.passport_pnfl}
|
||||
onChange={e => formDataChanging({ key: 'passport_pnfl', value: e.target.value })}
|
||||
/>
|
||||
<Box>
|
||||
<Box sx={{ marginBottom: 1, fontWeight: 500 }}>Passport rasmi</Box>
|
||||
<Box display='flex' justifyContent='center' gap={3}>
|
||||
<Box>
|
||||
<Card
|
||||
component='label'
|
||||
sx={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
bgcolor: '#f0f0f0',
|
||||
cursor: 'pointer',
|
||||
border: `1px solid ${dataErrors.passport_front ? "red" : "transparent"}`,
|
||||
transition: 'all 0.6 ease-out'
|
||||
}}
|
||||
>
|
||||
{formData.passport_front ? (
|
||||
<Box
|
||||
component='img'
|
||||
src={URL.createObjectURL(formData.passport_front)}
|
||||
sx={{ maxWidth: '100%', maxHeight: '100%' }}
|
||||
/>
|
||||
) : (
|
||||
<Box fontSize={14} color={'#444'}>
|
||||
Oldi rasmi
|
||||
</Box>
|
||||
)}
|
||||
<input type='file' hidden accept='image/*' onChange={handleFrontChange} />
|
||||
</Card>
|
||||
</Box>
|
||||
<Box>
|
||||
<Card
|
||||
component='label'
|
||||
sx={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
bgcolor: '#f0f0f0',
|
||||
cursor: 'pointer',
|
||||
border: `1px solid ${dataErrors.passport_back ? "red" : "transparent"}`,
|
||||
transition: 'all 0.6 ease-out'
|
||||
}}
|
||||
>
|
||||
{formData.passport_back ? (
|
||||
<Box
|
||||
component='img'
|
||||
src={URL.createObjectURL(formData.passport_back)}
|
||||
sx={{ maxWidth: '100%', maxHeight: '100%' }}
|
||||
/>
|
||||
) : (
|
||||
<Box fontSize={14} color={'#444'}>
|
||||
Orqa rasmi
|
||||
</Box>
|
||||
)}
|
||||
<input type='file' hidden accept='image/*' onChange={handleBackChange} />
|
||||
</Card>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Box, TextField, MenuItem } from '@mui/material';
|
||||
import { IRegisterProps } from '@/components/layout/landing-layout/header/components/IRegisterModal';
|
||||
import useMainFetch from '@/hooks/useMainFetch';
|
||||
|
||||
export function IRegisterThirdStep({ formData, formDataChanging, dataErrors }: IRegisterProps) {
|
||||
const { data } = useMainFetch({
|
||||
key: 'branches',
|
||||
endpoint: '/branches',
|
||||
});
|
||||
|
||||
const branchOptions = data?.data?.data?.map((item: { option: string; nameUz: string; nameRu: string; nameEn: string }) => {
|
||||
|
||||
|
||||
return {
|
||||
value: item.option,
|
||||
label: item.nameUz,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<Box component='form' display='flex' flexDirection='column' gap={2}>
|
||||
<TextField
|
||||
label='Manzil'
|
||||
variant='outlined'
|
||||
fullWidth
|
||||
value={formData.address}
|
||||
error={dataErrors.address}
|
||||
onChange={e => formDataChanging({ key: 'address', value: e.target.value })}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label='Qulay filial tanlash'
|
||||
variant='outlined'
|
||||
fullWidth
|
||||
select
|
||||
value={formData.filial}
|
||||
error={dataErrors.filial}
|
||||
onChange={e => formDataChanging({ key: 'filial', value: e.target.value })}
|
||||
>
|
||||
{branchOptions?.map((option: {value: string; label: string}) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
1
src/components/layout/landing-layout/header/index.ts
Normal file
1
src/components/layout/landing-layout/header/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Header';
|
||||
1
src/components/layout/landing-layout/index.ts
Normal file
1
src/components/layout/landing-layout/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Layout';
|
||||
38
src/components/ui-kit/BaseButton/index.tsx
Normal file
38
src/components/ui-kit/BaseButton/index.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { Button as MuiButton, ButtonProps, CircularProgress } from '@mui/material';
|
||||
import NextLink from '@/components/common/NextLink';
|
||||
import { ButtonColorVariant, getButtonStyle } from '@/theme/getButtonStyles';
|
||||
|
||||
type BaseButtonProps = ButtonProps & {
|
||||
loading?: boolean;
|
||||
colorVariant?: ButtonColorVariant;
|
||||
};
|
||||
|
||||
const BaseButton = ({ colorVariant, ...props }: BaseButtonProps) => {
|
||||
return (
|
||||
<MuiButton
|
||||
{...props}
|
||||
LinkComponent={NextLink}
|
||||
disabled={props.disabled || props.loading}
|
||||
endIcon={props.loading ? <CircularProgress size={20} color='inherit' /> : props.endIcon ? props.endIcon : null}
|
||||
{...(colorVariant
|
||||
? {
|
||||
sx: getButtonStyle(colorVariant, false, props.sx),
|
||||
}
|
||||
: {
|
||||
sx: {
|
||||
borderRadius: '8px',
|
||||
padding: '10px 18px',
|
||||
|
||||
fontSize: '18px',
|
||||
fontWeight: 600,
|
||||
lineHeight: '24px',
|
||||
textTransform: 'capitalize',
|
||||
...props.sx,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BaseButton;
|
||||
32
src/components/ui-kit/BaseIconButton/index.tsx
Normal file
32
src/components/ui-kit/BaseIconButton/index.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import { IconButton as MuiIconButton, ButtonProps, CircularProgress } from '@mui/material';
|
||||
import NextLink from '@/components/common/NextLink';
|
||||
import { getButtonStyle, ButtonColorVariant } from '@/theme/getButtonStyles';
|
||||
|
||||
type BaseIconButtonProps = ButtonProps & {
|
||||
loading?: boolean;
|
||||
colorVariant?: ButtonColorVariant;
|
||||
};
|
||||
|
||||
const BaseIconButton = ({ colorVariant, ...props }: BaseIconButtonProps) => {
|
||||
return (
|
||||
<MuiIconButton
|
||||
{...props}
|
||||
LinkComponent={NextLink}
|
||||
disabled={props.disabled || props.loading}
|
||||
{...(colorVariant
|
||||
? {
|
||||
sx: getButtonStyle(colorVariant, true, props.sx),
|
||||
}
|
||||
: {
|
||||
sx: {
|
||||
...props.sx,
|
||||
},
|
||||
})}
|
||||
>
|
||||
{props.loading ? <CircularProgress size={20} color='inherit' /> : props.children}
|
||||
</MuiIconButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default BaseIconButton;
|
||||
62
src/components/ui-kit/BaseInput/index.tsx
Normal file
62
src/components/ui-kit/BaseInput/index.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { TextField, TextFieldProps } from '@mui/material';
|
||||
import React, { ForwardedRef, forwardRef } from 'react';
|
||||
|
||||
type Props = {
|
||||
mainBorderColor?: string;
|
||||
inputFontSize?: string;
|
||||
} & TextFieldProps;
|
||||
|
||||
const BaseInput = ({ mainBorderColor = '#3489E4', inputFontSize = '16px', ...props }: Props, ref: ForwardedRef<HTMLInputElement>) => {
|
||||
return (
|
||||
<TextField
|
||||
variant='outlined'
|
||||
inputRef={ref}
|
||||
{...props}
|
||||
sx={{
|
||||
input: {
|
||||
padding: '8px',
|
||||
gap: '8px',
|
||||
fontSize: inputFontSize,
|
||||
},
|
||||
fieldset: {
|
||||
borderRadius: '8px',
|
||||
border: `2px solid ${mainBorderColor}`,
|
||||
|
||||
fontSize: '16px',
|
||||
fontWeight: 400,
|
||||
lineHeight: '20px',
|
||||
color: '#000',
|
||||
},
|
||||
|
||||
'&:hover': {
|
||||
fieldset: {
|
||||
border: `2px solid ${mainBorderColor} !important`,
|
||||
},
|
||||
},
|
||||
|
||||
'&.Mui-focused': {
|
||||
fieldset: {
|
||||
borderRadius: '8px',
|
||||
// border: `2px solid ${mainBorderColor}`,
|
||||
border: `2px solid #3489E4 !important`,
|
||||
|
||||
fontSize: '16px',
|
||||
fontWeight: 400,
|
||||
lineHeight: '20px',
|
||||
color: '#000',
|
||||
|
||||
'&:hover': {
|
||||
fieldset: {
|
||||
border: `2px solid #3489E4 !important`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
...props.sx,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default forwardRef(BaseInput);
|
||||
49
src/components/ui-kit/BaseModal/index.tsx
Normal file
49
src/components/ui-kit/BaseModal/index.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import BaseIconButton from '@/components/ui-kit/BaseIconButton';
|
||||
import { Close } from '@mui/icons-material';
|
||||
import { Box, Modal, Stack, styled } from '@mui/material';
|
||||
import React from 'react';
|
||||
|
||||
const StyledBox = styled(Box)`
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
overflow: auto;
|
||||
max-height: 90%;
|
||||
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
box-shadow: 4px 4px 4px 0px rgba(17, 87, 155, 0.08);
|
||||
display: flex;
|
||||
padding: 10px 32px 40px;
|
||||
flex-direction: column-reverse;
|
||||
width: 100%;
|
||||
|
||||
.modal-close-btn {
|
||||
margin-right: -20px;
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
onClose: () => void;
|
||||
open: boolean;
|
||||
maxWidth: string;
|
||||
};
|
||||
|
||||
const BaseModal = ({ children, onClose, open, maxWidth }: Props) => {
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<StyledBox sx={{ maxWidth }}>
|
||||
<Box>{children}</Box>
|
||||
<Stack direction={'row'} alignSelf={'flex-end'} justifyContent={'flex-end'} mb={1}>
|
||||
<BaseIconButton className='modal-close-btn' onClick={onClose}>
|
||||
<Close />
|
||||
</BaseIconButton>
|
||||
</Stack>
|
||||
</StyledBox>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default BaseModal;
|
||||
31
src/components/ui-kit/BasePagination/index.tsx
Normal file
31
src/components/ui-kit/BasePagination/index.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import * as React from 'react';
|
||||
import Pagination from '@mui/material/Pagination';
|
||||
import Stack from '@mui/material/Stack';
|
||||
|
||||
interface BasePaginationProps {
|
||||
page: number;
|
||||
totalCount: number;
|
||||
pageSize: number;
|
||||
onChange: (newPage: number) => void;
|
||||
}
|
||||
|
||||
export default function BasePagination({ page, pageSize, totalCount, onChange }: BasePaginationProps) {
|
||||
return (
|
||||
<Stack spacing={2}>
|
||||
<Pagination
|
||||
page={page}
|
||||
count={Math.ceil(totalCount / pageSize)}
|
||||
onChange={(_, newPage) => onChange(newPage)}
|
||||
variant='outlined'
|
||||
shape='rounded'
|
||||
color='primary'
|
||||
sx={{
|
||||
'.Mui-selected': {
|
||||
backgroundColor: theme => theme.palette.primary.main,
|
||||
color: '#fff',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
31
src/components/ui-kit/BaseReactSelect/index.tsx
Normal file
31
src/components/ui-kit/BaseReactSelect/index.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React, { ForwardedRef, forwardRef } from 'react';
|
||||
import ReactSelect, { StylesConfig } from 'react-select';
|
||||
|
||||
export const selectDefaultStyles: StylesConfig = {
|
||||
control(base, props) {
|
||||
return {
|
||||
...base,
|
||||
border: props.isFocused ? '2px solid #3489E4' : '2px solid #D8D8D8',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0',
|
||||
minWidth: 200,
|
||||
};
|
||||
},
|
||||
valueContainer(base, props) {
|
||||
return {
|
||||
...base,
|
||||
// border: '1px solid #D8D8D8',
|
||||
};
|
||||
},
|
||||
indicatorSeparator(base, props) {
|
||||
return {
|
||||
display: 'none',
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const BaseReactSelect = (props: any, ref: ForwardedRef<HTMLSelectElement>) => {
|
||||
return <ReactSelect styles={selectDefaultStyles} {...props} ref={ref} />;
|
||||
};
|
||||
|
||||
export default forwardRef(BaseReactSelect);
|
||||
59
src/components/ui-kit/BaseSelect/index.tsx
Normal file
59
src/components/ui-kit/BaseSelect/index.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Select, SelectProps } from '@mui/material';
|
||||
import React, { ForwardedRef, forwardRef } from 'react';
|
||||
|
||||
type Props = {
|
||||
mainBorderColor?: string;
|
||||
} & SelectProps;
|
||||
|
||||
const BaseSelect = ({ mainBorderColor = '#3489E4', ...props }: Props, ref: ForwardedRef<HTMLSelectElement>) => {
|
||||
return (
|
||||
<Select
|
||||
variant='outlined'
|
||||
inputRef={ref}
|
||||
{...props}
|
||||
sx={{
|
||||
'.MuiSelect-select': {
|
||||
padding: '8px',
|
||||
gap: '8px',
|
||||
},
|
||||
fieldset: {
|
||||
borderRadius: '8px',
|
||||
border: `2px solid ${mainBorderColor}`,
|
||||
|
||||
fontSize: '16px',
|
||||
fontWeight: 400,
|
||||
lineHeight: '20px',
|
||||
color: '#000',
|
||||
},
|
||||
|
||||
'&:hover': {
|
||||
fieldset: {
|
||||
border: `2px solid ${mainBorderColor} !important`,
|
||||
},
|
||||
},
|
||||
|
||||
'.Mui-focused': {
|
||||
fieldset: {
|
||||
borderRadius: '8px',
|
||||
// border: `2px solid ${mainBorderColor}`,
|
||||
border: `2px solid #3489E4 !important`,
|
||||
|
||||
fontSize: '16px',
|
||||
fontWeight: 400,
|
||||
lineHeight: '20px',
|
||||
color: '#000',
|
||||
|
||||
'&:hover': {
|
||||
fieldset: {
|
||||
border: `2px solid #3489E4 !important`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
...props.sx,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default forwardRef(BaseSelect);
|
||||
113
src/context/auth-context.tsx
Normal file
113
src/context/auth-context.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
'use client';
|
||||
|
||||
import { User, UserRoleEnum } from '@/data/user/user.model';
|
||||
import { user_requests } from '@/data/user/user.requests';
|
||||
import { auth_service } from '@/services/auth';
|
||||
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||
|
||||
const initialState = {
|
||||
isAuth: false,
|
||||
isLoading: true,
|
||||
user: null,
|
||||
};
|
||||
|
||||
type AuthContextType = {
|
||||
isAuth: boolean;
|
||||
isLoading: boolean;
|
||||
user: User | null;
|
||||
login: ({ accessToken }: { accessToken: string }) => Promise<void>;
|
||||
logout: () => void;
|
||||
refetchUserData: () => void;
|
||||
isAdmin: boolean;
|
||||
isAdminOrUzbek: boolean;
|
||||
};
|
||||
|
||||
export const AuthContext = createContext<AuthContextType>(null!);
|
||||
|
||||
export const AuthContextProvider = (props: { children: React.ReactNode }) => {
|
||||
const [state, setState] = useState<{ isAuth: boolean; isLoading: boolean; user: User | null }>(() => {
|
||||
if (auth_service.getToken()) {
|
||||
return {
|
||||
...initialState,
|
||||
isAuth: true,
|
||||
};
|
||||
} else {
|
||||
return initialState;
|
||||
}
|
||||
});
|
||||
const initialize = async () => {
|
||||
const _token = auth_service.getToken();
|
||||
|
||||
if (_token) {
|
||||
try {
|
||||
const response = await user_requests.getMe();
|
||||
setState({
|
||||
isAuth: true,
|
||||
isLoading: false,
|
||||
user: response.data.data,
|
||||
// || {
|
||||
// active: true,
|
||||
// id: 9999,
|
||||
// role: UserRoleEnum.CHINA_WORKER,
|
||||
// username: 'demo_username',
|
||||
// address: 'Demo Address',
|
||||
// fullName: 'FullDemoName',
|
||||
// phone: '99894444444444444',
|
||||
// },
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setState({
|
||||
isAuth: true,
|
||||
isLoading: false,
|
||||
user: null,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setState({
|
||||
isAuth: false,
|
||||
isLoading: false,
|
||||
user: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initialize();
|
||||
}, []);
|
||||
|
||||
const login = async ({ accessToken }: { accessToken: string }) => {
|
||||
auth_service.login(accessToken);
|
||||
setState(prev => ({ ...prev, isLoading: true }));
|
||||
initialize();
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
auth_service.logout();
|
||||
};
|
||||
|
||||
const refetchUserData = useCallback(() => {
|
||||
initialize();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
...state,
|
||||
login,
|
||||
logout,
|
||||
refetchUserData,
|
||||
isAdmin: Boolean(state.user?.role.includes(UserRoleEnum.ADMIN)),
|
||||
isAdminOrUzbek: Boolean(
|
||||
state.user?.role.includes(UserRoleEnum.ADMIN) || state.user?.role.includes(UserRoleEnum.UZB_WORKER)
|
||||
),
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const AuthConsumer = AuthContext.Consumer;
|
||||
|
||||
export const useAuthContext = () => useContext(AuthContext);
|
||||
36
src/context/customerAuthContext.tsx
Normal file
36
src/context/customerAuthContext.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React, { createContext, useContext, useState, ReactNode } from 'react';
|
||||
import { getCookie, deleteCookie } from 'cookies-next';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
interface AuthContextType {
|
||||
token: string | null;
|
||||
setToken: (token: string | null) => void;
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [token, setToken] = useState<string | null>(getCookie('authToken') || null);
|
||||
const router = useRouter();
|
||||
|
||||
const logout = () => {
|
||||
setToken(null);
|
||||
deleteCookie('authToken');
|
||||
router.push('/login');
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ token, setToken, logout }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useAuthCustomer = () => {
|
||||
const context = useContext(AuthContext);
|
||||
if (!context) {
|
||||
throw new Error('useAuth must be used within an AuthProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
101
src/data/box/box.model.ts
Normal file
101
src/data/box/box.model.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
export type BoxStatus = 'READY_TO_INVOICE' | 'READY';
|
||||
export const BoxStatusList: BoxStatus[] = ['READY_TO_INVOICE', 'READY'];
|
||||
|
||||
export interface IBox {
|
||||
packetNetWeight: string;
|
||||
totalNetWeight: string;
|
||||
cargoId: string;
|
||||
passportName: string;
|
||||
id: number;
|
||||
partyName: string;
|
||||
boxType: string;
|
||||
name: string;
|
||||
volume: string;
|
||||
boxWeight: number;
|
||||
brutto: number;
|
||||
hasInvoice: boolean;
|
||||
totalItems: number;
|
||||
status: BoxStatus;
|
||||
totalBrutto: number;
|
||||
}
|
||||
|
||||
export interface IBoxDetail {
|
||||
packet: {
|
||||
id: number;
|
||||
cargoId: string;
|
||||
packetNetWeight: number;
|
||||
passportName: string;
|
||||
totalItems: number;
|
||||
totalNetWeight: number;
|
||||
partyName: string;
|
||||
partyId: string;
|
||||
boxType: string;
|
||||
name: string;
|
||||
volume: string;
|
||||
boxWeight: number;
|
||||
brutto: number;
|
||||
hasInvoice: boolean;
|
||||
status: BoxStatus;
|
||||
};
|
||||
client: {
|
||||
passportId: number;
|
||||
passportName: string;
|
||||
};
|
||||
items: [
|
||||
{
|
||||
id: number;
|
||||
partyName: string;
|
||||
boxName: string;
|
||||
cargoId: string;
|
||||
trekId: string;
|
||||
name: string;
|
||||
nameRu: string;
|
||||
amount: number;
|
||||
weight: number;
|
||||
price: number;
|
||||
totalPrice: number;
|
||||
hasImage: boolean;
|
||||
imageUrl: string | null;
|
||||
packetName: string;
|
||||
status: BoxStatus;
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export type CreateBoxBodyType = {
|
||||
status: BoxStatus;
|
||||
cargoId: string;
|
||||
passportId: string;
|
||||
partyId: string;
|
||||
items: {
|
||||
trekId: string;
|
||||
name: string;
|
||||
amount: number;
|
||||
weight: number;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type UpdateBoxBodyType = {
|
||||
passportId: number;
|
||||
status: BoxStatus;
|
||||
packetId: string;
|
||||
// clientId: number;
|
||||
cargoId: string;
|
||||
|
||||
// type: string;
|
||||
// name: string;
|
||||
// volume: string;
|
||||
// boxWeight: number;
|
||||
// brutto: number;
|
||||
items: {
|
||||
cargoId: string;
|
||||
trekId: string;
|
||||
name: string;
|
||||
amount: number;
|
||||
weight: number;
|
||||
|
||||
id: number;
|
||||
price: number;
|
||||
totalPrice: number;
|
||||
}[];
|
||||
};
|
||||
67
src/data/box/box.requests.ts
Normal file
67
src/data/box/box.requests.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { IBox, CreateBoxBodyType, UpdateBoxBodyType, IBoxDetail, BoxStatus } from '@/data/box/box.model';
|
||||
import { CommonResponseType, PageAble } from '@/helpers/types';
|
||||
import { request } from '@/services/request';
|
||||
import axios from 'axios';
|
||||
|
||||
export const box_requests = {
|
||||
async getAll(params?: {
|
||||
page?: number;
|
||||
sort?: string;
|
||||
direction?: string;
|
||||
cargoId?: string;
|
||||
partyId?: string | number;
|
||||
status?: BoxStatus;
|
||||
}) {
|
||||
return request.get<CommonResponseType<PageAble<IBox>>>('/packets/list', { params });
|
||||
},
|
||||
async changeStatus(params: { packetId: string | number; status: BoxStatus }) {
|
||||
return request.put('/packets/change', undefined, { params });
|
||||
},
|
||||
async create({ partyId, ...body }: CreateBoxBodyType) {
|
||||
return request.post<CommonResponseType>('/packets/create', body, {
|
||||
params: { partyId },
|
||||
});
|
||||
},
|
||||
async update({ packetId, ...body }: UpdateBoxBodyType) {
|
||||
return request.put<CommonResponseType>('/packets/update', body, {
|
||||
params: { packetId },
|
||||
});
|
||||
},
|
||||
async find(params: { packetId?: number | string }) {
|
||||
return request.get<CommonResponseType<IBoxDetail>>('/packets/find', { params });
|
||||
},
|
||||
async delete(params: { packetId: number | string }) {
|
||||
return request.delete<CommonResponseType>('/packets/delete', { params });
|
||||
},
|
||||
async downloadExcel(params: { packetId: number | string }) {
|
||||
return request.get<Blob>('/packets/download', { params, responseType: 'blob' });
|
||||
},
|
||||
async translateWithGoogleApi(params: { text: string }): Promise<string> {
|
||||
const response = await axios.post('https://translation.googleapis.com/language/translate/v2', undefined, {
|
||||
params: {
|
||||
q: params.text,
|
||||
target: 'ru',
|
||||
source: 'zh',
|
||||
key: 'AIzaSyA5uAPZyjF_yo1hYOWWJ2uP7XgcmohZc8o',
|
||||
},
|
||||
});
|
||||
|
||||
return response.data.data.translations?.[0]?.translatedText ?? '';
|
||||
},
|
||||
async translateWithMemoryApi(params: { text: string }): Promise<string> {
|
||||
const response = await axios.get<{
|
||||
responseData: {
|
||||
translatedText: string;
|
||||
match: number;
|
||||
};
|
||||
}>('https://api.mymemory.translated.net/get', {
|
||||
params: {
|
||||
q: params.text,
|
||||
langpair: 'zh|ru',
|
||||
// key: '7a4ac2e07cde1ff1e9de',
|
||||
},
|
||||
});
|
||||
|
||||
return response.data.responseData.translatedText;
|
||||
},
|
||||
};
|
||||
0
src/data/box/box.service.ts
Normal file
0
src/data/box/box.service.ts
Normal file
15
src/data/customers/customer.model.ts
Normal file
15
src/data/customers/customer.model.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export type Customer = {
|
||||
id: number;
|
||||
chatId: string;
|
||||
fullName: string;
|
||||
phone: string;
|
||||
address: string;
|
||||
cargoId: string;
|
||||
|
||||
aviaCargoId: string;
|
||||
autoCargoId: string;
|
||||
|
||||
pinfl: string;
|
||||
passportSeries: string;
|
||||
dateOfBirth: string;
|
||||
};
|
||||
45
src/data/customers/customer.requests.ts
Normal file
45
src/data/customers/customer.requests.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Customer } from '@/data/customers/customer.model';
|
||||
import { CommonResponseType, PageAble } from '@/helpers/types';
|
||||
import { request } from '@/services/request';
|
||||
import omit from 'lodash.omit';
|
||||
|
||||
export const customer_requests = {
|
||||
async getAll(params?: {
|
||||
cargoId?: string;
|
||||
aviaCargoId?: string;
|
||||
autoCargoId?: string;
|
||||
page?: number;
|
||||
sort?: string;
|
||||
direction?: string;
|
||||
}) {
|
||||
return request.get<CommonResponseType<PageAble<Customer>>>('/clients/list', { params });
|
||||
},
|
||||
|
||||
async notAttached(params: { clientName?: string; partyId: number; page: number }) {
|
||||
return request.get<CommonResponseType<Customer[]>>('/clients/notAttached', { params });
|
||||
},
|
||||
|
||||
async edit(body: {
|
||||
clientId: number;
|
||||
|
||||
fullName: string;
|
||||
phone: string;
|
||||
address: string;
|
||||
pinfl: string;
|
||||
passportSeries: string;
|
||||
dateOfBirth: string;
|
||||
}) {
|
||||
const id = body.clientId;
|
||||
const data = omit(body, 'clientId');
|
||||
return request.put<CommonResponseType<Customer[]>>(`/clients/edit/${id}`, data);
|
||||
},
|
||||
async create(body: { fullName: string; phone: string; address: string; pinfl: string; passport: string; dateOfBirth: string }) {
|
||||
return request.post<CommonResponseType<Customer[]>>(`/clients/create`, body);
|
||||
},
|
||||
async delete(params: { clientId: number | string }) {
|
||||
return request.delete<CommonResponseType>(`/clients/delete/${params.clientId}`);
|
||||
},
|
||||
async downloadClientsExcel() {
|
||||
return request.get<Blob>('/clients/download', { responseType: 'blob' });
|
||||
},
|
||||
};
|
||||
4
src/data/invoice/invoice.model.ts
Normal file
4
src/data/invoice/invoice.model.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export type CreateInvoiceParams = {
|
||||
packetId: number;
|
||||
clientId: number;
|
||||
};
|
||||
9
src/data/invoice/invoice.requests.ts
Normal file
9
src/data/invoice/invoice.requests.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { CreateInvoiceParams } from '@/data/invoice/invoice.model';
|
||||
import { CommonResponseType } from '@/helpers/types';
|
||||
import { request } from '@/services/request';
|
||||
|
||||
export const invoice_requests = {
|
||||
async create(params: CreateInvoiceParams) {
|
||||
return request.post<CommonResponseType>('/invoice/create', undefined, { params });
|
||||
},
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user