account/sender UI
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import React from 'react'
|
||||
import MultiSelect from '@/components/ui/Selector'
|
||||
|
||||
const DelivelerPage = () => {
|
||||
return (
|
||||
|
||||
@@ -1,16 +1,288 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import MultiSelect from '@/components/ui/Selector'
|
||||
import TextInput from '@/components/ui/TextInput'
|
||||
import PhoneInput from '@/components/ui/PhoneInput'
|
||||
import Button from '@/components/ui/Button'
|
||||
import TextAreaInput from '@/components/ui/TextAreaInput'
|
||||
import CheckboxInput from '@/components/ui/CheckboxInput'
|
||||
import { useForm } from '@/app/hooks/useForm'
|
||||
import showToast from '@/components/ui/Toast'
|
||||
import { SenderPageProps, SelectOption } from '@/app/types'
|
||||
import {
|
||||
cargo_types,
|
||||
cargo_type_translations,
|
||||
transport_types,
|
||||
transport_translations,
|
||||
} from '@/app/constants'
|
||||
|
||||
const formatDateToHTML = (date: Date) => {
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}T${hours}:${minutes}`
|
||||
}
|
||||
|
||||
const validationRules = {
|
||||
transport: { required: true },
|
||||
country_from: { required: true, minLength: 2 },
|
||||
city_from: { required: true, minLength: 2 },
|
||||
country_to: { required: true, minLength: 2 },
|
||||
city_to: { required: true, minLength: 2 },
|
||||
cargo_type: { required: true },
|
||||
departure: {
|
||||
required: true,
|
||||
pattern: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/,
|
||||
},
|
||||
arrival: {
|
||||
required: true,
|
||||
pattern: /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/,
|
||||
},
|
||||
contact_number: {
|
||||
required: true,
|
||||
minLength: 11,
|
||||
pattern: /^\+?[0-9]{11,}$/,
|
||||
},
|
||||
comment: { required: false, minLength: 10 },
|
||||
email_notification: { required: false },
|
||||
}
|
||||
|
||||
const SenderPage = () => {
|
||||
const today = formatDateToHTML(new Date())
|
||||
|
||||
const initialValues: SenderPageProps = {
|
||||
transport: '',
|
||||
country_from: '',
|
||||
city_from: '',
|
||||
country_to: '',
|
||||
city_to: '',
|
||||
cargo_type: '',
|
||||
departure: '',
|
||||
arrival: '',
|
||||
contact_number: '',
|
||||
comment: '',
|
||||
email_notification: false,
|
||||
}
|
||||
|
||||
const cargoOptions: SelectOption[] = cargo_types.map((type, index) => ({
|
||||
id: index + 1,
|
||||
label: cargo_type_translations[type],
|
||||
value: type,
|
||||
}))
|
||||
|
||||
const transportOptions: SelectOption[] = transport_types.map((type, index) => ({
|
||||
id: index + 1,
|
||||
label: transport_translations[type],
|
||||
value: type,
|
||||
}))
|
||||
|
||||
const { values, handleChange, handleSubmit } = useForm<SenderPageProps>(
|
||||
initialValues,
|
||||
validationRules,
|
||||
async values => {
|
||||
try {
|
||||
// await addNewSpecialist(values, selectedImage || undefined)
|
||||
showToast({
|
||||
type: 'success',
|
||||
message: 'Маршрут успешно создан!',
|
||||
})
|
||||
} catch {
|
||||
showToast({
|
||||
type: 'error',
|
||||
message: 'Упс, что то пошло не так...',
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="overflow-hidden rounded-2xl shadow">
|
||||
<div className="bg-white p-6 sm:p-8">
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-2xl">Отправить посылку</h1>
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div className="overflow-hidden rounded-2xl bg-white shadow">
|
||||
<div className="p-6 sm:p-8">
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold text-gray-900">Отправить посылку</h1>
|
||||
<p className="mt-1 text-sm text-gray-600">
|
||||
Заполните информацию о вашей посылке и маршруте
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* тип груза и транспорта */}
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
<div>
|
||||
<label htmlFor="cargo_type" className="block text-sm font-medium text-gray-700">
|
||||
Тип груза
|
||||
</label>
|
||||
<MultiSelect
|
||||
value={values.cargo_type ? [parseInt(values.cargo_type)] : []}
|
||||
handleChange={e => {
|
||||
handleChange({
|
||||
target: {
|
||||
id: 'cargo_type',
|
||||
value: e.target.value[0]?.toString() || '',
|
||||
},
|
||||
})
|
||||
}}
|
||||
name="cargo_type"
|
||||
options={cargoOptions}
|
||||
className="mt-1"
|
||||
placeholder="Выберите тип груза"
|
||||
noOptionsMessage="Нет доступных типов груза"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="transport" className="block text-sm font-medium text-gray-700">
|
||||
Способ перевозки
|
||||
</label>
|
||||
<MultiSelect
|
||||
value={values.transport ? [parseInt(values.transport)] : []}
|
||||
handleChange={e => {
|
||||
handleChange({
|
||||
target: {
|
||||
id: 'transport',
|
||||
value: e.target.value[0]?.toString() || '',
|
||||
},
|
||||
})
|
||||
}}
|
||||
name="transport"
|
||||
options={transportOptions}
|
||||
className="mt-1"
|
||||
placeholder="Выберите способ перевозки"
|
||||
noOptionsMessage="Нет доступных способов перевозки"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* маршрут */}
|
||||
<div>
|
||||
<h2 className="mb-2 text-xl font-medium text-gray-900">Маршрут</h2>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
<div className="space-y-6">
|
||||
<TextInput
|
||||
name="country_from"
|
||||
value={values.country_from}
|
||||
handleChange={handleChange}
|
||||
label="Страна отправления"
|
||||
placeholder="Введите страну отправления"
|
||||
style="register"
|
||||
/>
|
||||
<TextInput
|
||||
name="country_to"
|
||||
value={values.country_to}
|
||||
handleChange={handleChange}
|
||||
label="Страна назначения"
|
||||
placeholder="Введите страну назначения"
|
||||
style="register"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<TextInput
|
||||
name="city_to"
|
||||
value={values.city_to}
|
||||
handleChange={handleChange}
|
||||
label="Город назначения"
|
||||
placeholder="Введите город назначения"
|
||||
style="register"
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
name="country_to"
|
||||
value={values.country_to}
|
||||
handleChange={handleChange}
|
||||
label="Страна назначения"
|
||||
placeholder="Введите страну назначения"
|
||||
style="register"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* даты */}
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
||||
<div>
|
||||
<label htmlFor="departure" className="block text-sm font-medium text-gray-700">
|
||||
Дата отправления
|
||||
</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
name="departure"
|
||||
id="departure"
|
||||
value={values.departure}
|
||||
onChange={handleChange}
|
||||
min={today}
|
||||
className="mt-1 block w-full rounded-xl border border-gray-300 px-3 py-2 focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="arrival" className="block text-sm font-medium text-gray-700">
|
||||
Дата прибытия
|
||||
</label>
|
||||
<input
|
||||
type="datetime-local"
|
||||
name="arrival"
|
||||
id="arrival"
|
||||
value={values.arrival}
|
||||
onChange={handleChange}
|
||||
min={values.departure || today}
|
||||
className="mt-1 block w-full rounded-xl border border-gray-300 px-3 py-2 focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* контактная информация */}
|
||||
<h2 className="mb-2 text-xl font-medium text-gray-900">Контактная информация</h2>
|
||||
<div className="space-y-6">
|
||||
<PhoneInput
|
||||
value={values.contact_number}
|
||||
handleChange={handleChange}
|
||||
label="Контактный телефон"
|
||||
operatorsInfo={false}
|
||||
/>
|
||||
|
||||
<TextAreaInput
|
||||
value={values.comment}
|
||||
handleChange={handleChange}
|
||||
label="Комментарий"
|
||||
placeholder="Дополнительная информация о грузе или пожелания"
|
||||
name="comment"
|
||||
/>
|
||||
|
||||
<CheckboxInput
|
||||
name="email_notification"
|
||||
label="Уведомления"
|
||||
checked={values.email_notification}
|
||||
handleChange={handleChange}
|
||||
enabledText="Хочу получать уведомления по email"
|
||||
disabledText="Не получать уведомления"
|
||||
info="Вы будете получать уведомления о важных событиях на указанный email адрес"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* кнопки действий */}
|
||||
<div className="flex justify-end space-x-4">
|
||||
<Button
|
||||
type="button"
|
||||
text="Отмена"
|
||||
className="border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none"
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
text="Создать маршрут"
|
||||
className="bg-orange hover:bg-orange/80 focus:ring-red w-1/3 border border-gray-300 py-2 text-sm font-medium text-white shadow-sm focus:ring-2 focus:ring-offset-2 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,7 @@
|
||||
import React from 'react'
|
||||
import { cookies } from 'next/headers'
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
interface Route {
|
||||
id: string
|
||||
from_city_name: string
|
||||
to_city_name: string
|
||||
from_country_name: string
|
||||
to_country_name: string
|
||||
formatted_departure: string
|
||||
formatted_arrival: string
|
||||
formatted_cargo_type: string
|
||||
formatted_transport: string
|
||||
comment?: string
|
||||
owner_type: string
|
||||
}
|
||||
import { Route } from '@/app/types'
|
||||
|
||||
async function getRoutes() {
|
||||
const cookieStore = await cookies()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import Image, { StaticImageData } from 'next/image'
|
||||
import Image from 'next/image'
|
||||
import Button from '@/components/ui/Button'
|
||||
import { SearchCardProps } from '@/app/types/index'
|
||||
|
||||
@@ -41,10 +41,10 @@ const SearchCard = ({
|
||||
<>
|
||||
{/* десктоп */}
|
||||
<div className="hidden sm:block">
|
||||
<div className="bg-white rounded-xl shadow-lg p-6 w-full my-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="my-4 w-full rounded-xl bg-white p-6 shadow-lg">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-5">
|
||||
<div className="w-16 h-16 bg-gray-200 rounded-full flex items-center justify-center">
|
||||
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-gray-200">
|
||||
<Image
|
||||
src={userImg}
|
||||
alt={username}
|
||||
@@ -56,35 +56,28 @@ const SearchCard = ({
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="text-base font-semibold">{username}</div>
|
||||
<div className="text-gray-500">|</div>
|
||||
<div
|
||||
className={`text-base font-semibold ${getUserRequestStyles()}`}
|
||||
>
|
||||
<div className={`text-base font-semibold ${getUserRequestStyles()}`}>
|
||||
{user_request}
|
||||
</div>
|
||||
<div className="ml-1">
|
||||
Тип посылки:{' '}
|
||||
<span className="text-orange font-semibold ml-1">
|
||||
{cargo_type}
|
||||
</span>
|
||||
Тип посылки: <span className="text-orange ml-1 font-semibold">{cargo_type}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
text="Откликнуться"
|
||||
className="bg-orange hover:bg-orange/80 text-white px-10 py-3 text-base font-semibold transition-colors cursor-pointer"
|
||||
className="bg-orange hover:bg-orange/80 cursor-pointer px-10 py-3 text-base font-semibold text-white transition-colors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-[#f8f8f8] rounded-lg p-5">
|
||||
<div className="rounded-lg bg-[#f8f8f8] p-5">
|
||||
<div className="flex items-baseline gap-2">
|
||||
<span className="text-gray-600">{user_comment}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-gray-500 text-sm flex justify-end pt-2">
|
||||
Объявление № {id}
|
||||
</div>
|
||||
<div className="flex justify-end pt-2 text-sm text-gray-500">Объявление № {id}</div>
|
||||
|
||||
<div className="flex items-center justify-between mt-6">
|
||||
<div className="mt-6 flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
{user_request === 'Нужен перевозчик' ? (
|
||||
<span className="text-gray-500">Забрать из:</span>
|
||||
@@ -93,25 +86,16 @@ const SearchCard = ({
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center">
|
||||
<Image
|
||||
src={country_from_icon}
|
||||
width={26}
|
||||
height={13}
|
||||
alt={country_from_code}
|
||||
/>
|
||||
<span className="text-gray-400 pr-2 pl-1">
|
||||
{country_from_code}
|
||||
</span>
|
||||
<Image src={country_from_icon} width={26} height={13} alt={country_from_code} />
|
||||
<span className="pr-2 pl-1 text-gray-400">{country_from_code}</span>
|
||||
<span className="text-base font-semibold">
|
||||
{start_point} / {country_from}
|
||||
</span>
|
||||
</div>
|
||||
{user_request === 'Могу перевезти' && (
|
||||
<div className="text-sm text-gray-500 mt-1">
|
||||
<div className="mt-1 text-sm text-gray-500">
|
||||
<span className="text-sm font-normal">Отправление:</span>{' '}
|
||||
<span className="text-sm font-semibold">
|
||||
{day_out?.toLocaleDateString()}
|
||||
</span>
|
||||
<span className="text-sm font-semibold">{day_out?.toLocaleDateString()}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -125,10 +109,10 @@ const SearchCard = ({
|
||||
width={15}
|
||||
height={15}
|
||||
alt="route vector"
|
||||
className="w-[15px] h-[15px] object-contain"
|
||||
className="h-[15px] w-[15px] object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between w-[500px] mx-auto my-1">
|
||||
<div className="mx-auto my-1 flex w-[500px] items-center justify-between">
|
||||
<Image
|
||||
src="/images/vector.svg"
|
||||
width={500}
|
||||
@@ -136,8 +120,8 @@ const SearchCard = ({
|
||||
alt="route vector"
|
||||
className="absolute"
|
||||
/>
|
||||
<div className="w-5 h-5 rounded-full bg-white border-3 border-[#065bff] relative z-10" />
|
||||
<div className="w-5 h-5 rounded-full bg-white border-3 border-[#45c226] relative z-10" />
|
||||
<div className="relative z-10 h-5 w-5 rounded-full border-3 border-[#065bff] bg-white" />
|
||||
<div className="relative z-10 h-5 w-5 rounded-full border-3 border-[#45c226] bg-white" />
|
||||
</div>
|
||||
|
||||
{user_request === 'Нужен перевозчик' && (
|
||||
@@ -150,7 +134,7 @@ const SearchCard = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col -mb-[14px]">
|
||||
<div className="-mb-[14px] flex flex-col">
|
||||
{user_request === 'Нужен перевозчик' ? (
|
||||
<div className="text-base text-gray-500">Доставить в:</div>
|
||||
) : (
|
||||
@@ -159,15 +143,8 @@ const SearchCard = ({
|
||||
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center">
|
||||
<Image
|
||||
src={country_to_icon}
|
||||
width={26}
|
||||
height={13}
|
||||
alt={country_to_code}
|
||||
/>
|
||||
<span className="text-gray-400 pr-2 pl-1">
|
||||
{country_to_code}
|
||||
</span>
|
||||
<Image src={country_to_icon} width={26} height={13} alt={country_to_code} />
|
||||
<span className="pr-2 pl-1 text-gray-400">{country_to_code}</span>
|
||||
<span className="text-base font-semibold">
|
||||
{end_point} / {country_to}
|
||||
</span>
|
||||
@@ -175,9 +152,7 @@ const SearchCard = ({
|
||||
{user_request === 'Могу перевезти' && (
|
||||
<div className="text-sm text-gray-500">
|
||||
<span className="text-sm font-normal">Прибытие:</span>{' '}
|
||||
<span className="text-sm font-semibold">
|
||||
{day_in?.toLocaleDateString()}
|
||||
</span>
|
||||
<span className="text-sm font-semibold">{day_in?.toLocaleDateString()}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -188,41 +163,37 @@ const SearchCard = ({
|
||||
|
||||
{/* мобилка */}
|
||||
<div className="block sm:hidden">
|
||||
<div className="bg-white rounded-xl shadow-lg p-4 w-full my-4">
|
||||
<div className="my-4 w-full rounded-xl bg-white p-4 shadow-lg">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className={`text-sm font-semibold ${getUserRequestStyles()}`}>
|
||||
{user_request}
|
||||
</div>
|
||||
<div className={`text-sm font-semibold ${getUserRequestStyles()}`}>{user_request}</div>
|
||||
<div className="text-sm font-semibold">
|
||||
Тип посылки: <span className="text-orange">{cargo_type}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row items-center justify-between mt-5 mb-2 gap-3">
|
||||
<div className="min-w-[64px] w-16 h-16 bg-gray-200 rounded-full flex items-center justify-center shrink-0">
|
||||
<div className="mt-5 mb-2 flex flex-row items-center justify-between gap-3">
|
||||
<div className="flex h-16 w-16 min-w-[64px] shrink-0 items-center justify-center rounded-full bg-gray-200">
|
||||
<Image
|
||||
src={userImg}
|
||||
alt={username}
|
||||
width={52}
|
||||
height={52}
|
||||
className="rounded-full object-cover aspect-square w-[52px] h-[52px]"
|
||||
className="aspect-square h-[52px] w-[52px] rounded-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-[#f8f8f8] rounded-lg text-sm font-normal p-4 flex-1">
|
||||
<div className="flex-1 rounded-lg bg-[#f8f8f8] p-4 text-sm font-normal">
|
||||
{user_comment}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-gray-500 text-xs flex justify-end">
|
||||
Объявление № {id}
|
||||
</div>
|
||||
<div className="flex justify-end text-xs text-gray-500">Объявление № {id}</div>
|
||||
|
||||
{user_request === 'Нужен перевозчик' ? (
|
||||
<span className="text-gray-500 pl-7 text-sm">Забрать из:</span>
|
||||
<span className="pl-7 text-sm text-gray-500">Забрать из:</span>
|
||||
) : (
|
||||
<span className="text-gray-500 pl-7 text-sm">Выезжаю из:</span>
|
||||
<span className="pl-7 text-sm text-gray-500">Выезжаю из:</span>
|
||||
)}
|
||||
<div className="flex flex-row items-stretch mt-4 mb-2 gap-4">
|
||||
<div className="flex flex-col items-center h-[150px] relative">
|
||||
<div className="h-full w-[10px] flex items-center justify-center relative">
|
||||
<div className="mt-4 mb-2 flex flex-row items-stretch gap-4">
|
||||
<div className="relative flex h-[150px] flex-col items-center">
|
||||
<div className="relative flex h-full w-[10px] items-center justify-center">
|
||||
<Image
|
||||
src="/images/vectormob.png"
|
||||
width={6}
|
||||
@@ -230,44 +201,35 @@ const SearchCard = ({
|
||||
alt="route vector"
|
||||
className="absolute h-[150px] w-auto"
|
||||
/>
|
||||
<div className="absolute -top-[10px] w-4 h-4 rounded-full bg-white border-3 border-[#065bff] z-10" />
|
||||
<div className="absolute -bottom-[10px] w-4 h-4 rounded-full bg-white border-3 border-[#45c226] z-10" />
|
||||
<div className="absolute -top-[10px] z-10 h-4 w-4 rounded-full border-3 border-[#065bff] bg-white" />
|
||||
<div className="absolute -bottom-[10px] z-10 h-4 w-4 rounded-full border-3 border-[#45c226] bg-white" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col justify-between">
|
||||
<div className="flex items-center -mt-[14px]">
|
||||
<Image
|
||||
src={country_from_icon}
|
||||
width={26}
|
||||
height={13}
|
||||
alt={country_from_code}
|
||||
/>
|
||||
<span className="text-gray-400 text-sm pr-2 pl-1">
|
||||
{country_from_code}
|
||||
</span>
|
||||
<div className="flex flex-1 flex-col justify-between">
|
||||
<div className="-mt-[14px] flex items-center">
|
||||
<Image src={country_from_icon} width={26} height={13} alt={country_from_code} />
|
||||
<span className="pr-2 pl-1 text-sm text-gray-400">{country_from_code}</span>
|
||||
<span className="text-base font-semibold">
|
||||
{start_point} / {country_from}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col ">
|
||||
<div className="flex gap-4 items-center">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-base">{moving_type}</span>
|
||||
<Image
|
||||
src={setMovingTypeIcon()}
|
||||
width={15}
|
||||
height={15}
|
||||
alt="route vector"
|
||||
className="w-[15px] h-[15px] object-contain"
|
||||
className="h-[15px] w-[15px] object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-[165px] h-[2px] bg-gray-200 my-2" />
|
||||
<div className="text-sm">
|
||||
Дата доставки: {estimated_date.toLocaleDateString()}
|
||||
</div>
|
||||
<div className="my-2 h-[2px] w-[165px] bg-gray-200" />
|
||||
<div className="text-sm">Дата доставки: {estimated_date.toLocaleDateString()}</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col -mb-[14px]">
|
||||
<div className="-mb-[14px] flex flex-col">
|
||||
{user_request === 'Нужен перевозчик' ? (
|
||||
<div className="text-sm text-gray-500">Доставить в:</div>
|
||||
) : (
|
||||
@@ -275,15 +237,8 @@ const SearchCard = ({
|
||||
)}
|
||||
|
||||
<div className="flex items-center">
|
||||
<Image
|
||||
src={country_to_icon}
|
||||
width={26}
|
||||
height={13}
|
||||
alt={country_to_code}
|
||||
/>
|
||||
<span className="text-gray-400 pr-2 pl-1">
|
||||
{country_to_code}
|
||||
</span>
|
||||
<Image src={country_to_icon} width={26} height={13} alt={country_to_code} />
|
||||
<span className="pr-2 pl-1 text-gray-400">{country_to_code}</span>
|
||||
<span className="text-base font-semibold">
|
||||
{end_point} / {country_to}
|
||||
</span>
|
||||
@@ -292,11 +247,9 @@ const SearchCard = ({
|
||||
</div>
|
||||
</div>
|
||||
{user_request === 'Могу перевезти' && (
|
||||
<div className="text-sm text-gray-500 mt-3 ml-7">
|
||||
<div className="mt-3 ml-7 text-sm text-gray-500">
|
||||
<span className="text-sm font-normal">Прибытие:</span>{' '}
|
||||
<span className="text-sm font-semibold">
|
||||
{day_in?.toLocaleDateString()}
|
||||
</span>
|
||||
<span className="text-sm font-semibold">{day_in?.toLocaleDateString()}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
69
frontend/app/constants/index.ts
Normal file
69
frontend/app/constants/index.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import avatar from '../../public/images/avatar.png'
|
||||
import belarusIcon from '../../public/images/belarus.png'
|
||||
import russiaIcon from '../../public/images/russia.png'
|
||||
import { CargoType, TransportType } from '../types'
|
||||
|
||||
const userImg = avatar
|
||||
const blIcon = belarusIcon
|
||||
const ruIcon = russiaIcon
|
||||
|
||||
export const routes = 12845
|
||||
|
||||
export const data = [
|
||||
{
|
||||
id: 1123,
|
||||
username: 'John Doe',
|
||||
userImg: userImg,
|
||||
start_point: 'Минск',
|
||||
country_from: 'Беларусь',
|
||||
end_point: 'Москва',
|
||||
country_to: 'Россия',
|
||||
cargo_type: 'Документы',
|
||||
user_request: 'Нужен перевозчик',
|
||||
user_comment: 'Нужно перевезти документы из Минска в Москву',
|
||||
country_from_icon: blIcon,
|
||||
country_to_icon: ruIcon,
|
||||
country_from_code: 'BY',
|
||||
country_to_code: 'RU',
|
||||
moving_type: 'Авиатранспорт',
|
||||
estimated_date: new Date(2025, 4, 15),
|
||||
},
|
||||
{
|
||||
id: 2423,
|
||||
username: 'John Doe',
|
||||
userImg: userImg,
|
||||
start_point: 'Минск',
|
||||
country_from: 'Беларусь',
|
||||
end_point: 'Москва',
|
||||
country_to: 'Россия',
|
||||
cargo_type: 'Документы',
|
||||
user_request: 'Могу перевезти',
|
||||
user_comment: 'Нужно перевезти документы из Минска в Москву',
|
||||
moving_type: 'Автоперевозка',
|
||||
estimated_date: new Date(2025, 5, 18),
|
||||
country_from_icon: blIcon,
|
||||
country_to_icon: ruIcon,
|
||||
country_from_code: 'BY',
|
||||
country_to_code: 'RU',
|
||||
day_out: new Date(2025, 5, 21),
|
||||
day_in: new Date(2025, 5, 25),
|
||||
},
|
||||
]
|
||||
|
||||
export const cargo_types: CargoType[] = ['letter', 'package', 'passenger', 'parcel', 'cargo']
|
||||
|
||||
export const cargo_type_translations: Record<CargoType, string> = {
|
||||
letter: 'Письмо или Документы',
|
||||
package: 'Посылка (до 30кг)',
|
||||
passenger: 'Попутчик',
|
||||
parcel: 'Бандероль (до 5кг)',
|
||||
cargo: 'Груз (свыше 30 кг)',
|
||||
}
|
||||
|
||||
export const transport_types: TransportType[] = ['road', 'avia', 'both']
|
||||
|
||||
export const transport_translations: Record<TransportType, string> = {
|
||||
road: 'Авто',
|
||||
avia: 'Авиа',
|
||||
both: 'Любой',
|
||||
}
|
||||
@@ -3,8 +3,8 @@ import Image from 'next/image'
|
||||
import AddressSelector from '@/components/AddressSelector'
|
||||
import SearchCard from '@/app/(urls)/search/components/SearchCard'
|
||||
import FAQ from '@/components/FAQ'
|
||||
import { data } from '@/app/staticData'
|
||||
import { routes } from '@/app/staticData'
|
||||
import { data } from '@/app/constants'
|
||||
import { routes } from '@/app/constants'
|
||||
import Button from '@/components/ui/Button'
|
||||
import News from '@/components/News'
|
||||
import { getFAQs } from '@/lib/main/fetchFAQ'
|
||||
@@ -15,7 +15,7 @@ export default async function Home() {
|
||||
const news = await getNews()
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center max-w-[93%] mx-auto">
|
||||
<div className="mx-auto flex max-w-[93%] flex-col items-center justify-center">
|
||||
{/* main */}
|
||||
<div className="flex items-center justify-center space-x-16">
|
||||
<div>
|
||||
@@ -28,7 +28,7 @@ export default async function Home() {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center space-y-5">
|
||||
<h1 className="text-3xl sm:text-4xl text-center font-bold">
|
||||
<h1 className="text-center text-3xl font-bold sm:text-4xl">
|
||||
<div className="pb-1">
|
||||
Сервис по <span className="text-orange">поиску</span>{' '}
|
||||
</div>
|
||||
@@ -36,9 +36,8 @@ export default async function Home() {
|
||||
<span className="text-orange">перевозчиков</span> посылок
|
||||
</div>
|
||||
</h1>
|
||||
<p className="text-base sm:text-lg text-center pb-3">
|
||||
Доставка посылок с попутчиками: от документов до крупногабаритных
|
||||
грузов
|
||||
<p className="pb-3 text-center text-base sm:text-lg">
|
||||
Доставка посылок с попутчиками: от документов до крупногабаритных грузов
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
@@ -55,102 +54,94 @@ export default async function Home() {
|
||||
{/* форма на серч */}
|
||||
<AddressSelector />
|
||||
|
||||
<div className="text-lg font-normal text-[#272424] underline decoration-orange underline-offset-4 mb-20 hover:text-orange transition-colors cursor-pointer">
|
||||
<div className="decoration-orange hover:text-orange mb-20 cursor-pointer text-lg font-normal text-[#272424] underline underline-offset-4 transition-colors">
|
||||
Я могу взять с собой посылку
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center justify-center mb-8">
|
||||
<h2 className="text-4xl text-center font-bold">Все объявления</h2>
|
||||
<div className="text-base my-3">
|
||||
На нашем сайте размещено уже{' '}
|
||||
<span className="text-orange">{routes}</span> объявлений по отправке и
|
||||
перевозке посылок
|
||||
<div className="mb-8 flex flex-col items-center justify-center">
|
||||
<h2 className="text-center text-4xl font-bold">Все объявления</h2>
|
||||
<div className="my-3 text-base">
|
||||
На нашем сайте размещено уже <span className="text-orange">{routes}</span> объявлений по
|
||||
отправке и перевозке посылок
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* первые пять серч карточек -- бекенд??? */}
|
||||
<div className="w-full max-w-[1250px] mx-auto">
|
||||
<div className="grid grid-cols-1 gap-4 w-full">
|
||||
{data.map((card) => (
|
||||
<div className="mx-auto w-full max-w-[1250px]">
|
||||
<div className="grid w-full grid-cols-1 gap-4">
|
||||
{data.map(card => (
|
||||
<SearchCard key={card.id} {...card} />
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-center py-4">
|
||||
<Button
|
||||
text="Разместить объявление"
|
||||
className=" bg-orange text-white text-xl font-semibold px-6 py-3 rounded-lg"
|
||||
className="bg-orange rounded-lg px-6 py-3 text-xl font-semibold text-white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* how it works */}
|
||||
<div className="w-full max-w-[1250px] mx-auto my-20">
|
||||
<h2 className="text-4xl text-center font-bold">Как это работает</h2>
|
||||
<div className="flex flex-col items-center justify-center text-base font-medium text-center py-4 mb-4">
|
||||
<div className="mx-auto my-20 w-full max-w-[1250px]">
|
||||
<h2 className="text-center text-4xl font-bold">Как это работает</h2>
|
||||
<div className="mb-4 flex flex-col items-center justify-center py-4 text-center text-base font-medium">
|
||||
<span>
|
||||
TWB - это сервис, созданный для того, чтобы отправитель и перевозчик
|
||||
нашли друг-друга!
|
||||
</span>
|
||||
<span>
|
||||
Наш сервис предлагает вам прямые контакты, а не является посредником
|
||||
!
|
||||
TWB - это сервис, созданный для того, чтобы отправитель и перевозчик нашли друг-друга!
|
||||
</span>
|
||||
<span>Наш сервис предлагает вам прямые контакты, а не является посредником !</span>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row justify-center items-center sm:items-start sm:space-x-4 md:space-x-8 lg:space-x-20 space-y-8 sm:space-y-0">
|
||||
<div className="flex flex-col items-center w-full sm:w-1/3 px-4 sm:px-2">
|
||||
<div className="flex flex-col items-center h-[260px] sm:h-[300px]">
|
||||
<div className="flex flex-col items-center justify-center space-y-8 sm:flex-row sm:items-start sm:space-y-0 sm:space-x-4 md:space-x-8 lg:space-x-20">
|
||||
<div className="flex w-full flex-col items-center px-4 sm:w-1/3 sm:px-2">
|
||||
<div className="flex h-[260px] flex-col items-center sm:h-[300px]">
|
||||
<Image
|
||||
src="/images/laptop.png"
|
||||
alt="laptop"
|
||||
width={230}
|
||||
height={230}
|
||||
className="mb-6 w-[180px] h-[180px] sm:w-[230px] sm:h-[230px]"
|
||||
className="mb-6 h-[180px] w-[180px] sm:h-[230px] sm:w-[230px]"
|
||||
/>
|
||||
<div className="text-xl sm:text-2xl font-semibold text-center">
|
||||
<div className="text-center text-xl font-semibold sm:text-2xl">
|
||||
Найдите перевозчика
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm sm:text-base text-gray-600 text-center px-2 sm:px-4 md:px-7">
|
||||
В форме поиска укажите откуда и куда Вам нужно доставить посылку,
|
||||
нажмите кнопку "найти перевозчика". Если по вашему запросу ничего
|
||||
не найдено - Вы можете сами разместить объявление и тогда
|
||||
перевозчики Вас найдут.
|
||||
<div className="px-2 text-center text-sm text-gray-600 sm:px-4 sm:text-base md:px-7">
|
||||
В форме поиска укажите откуда и куда Вам нужно доставить посылку, нажмите кнопку
|
||||
"найти перевозчика". Если по вашему запросу ничего не найдено - Вы можете сами
|
||||
разместить объявление и тогда перевозчики Вас найдут.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center w-full sm:w-1/3 px-4 sm:px-2">
|
||||
<div className="flex flex-col items-center h-[260px] sm:h-[300px]">
|
||||
<div className="flex w-full flex-col items-center px-4 sm:w-1/3 sm:px-2">
|
||||
<div className="flex h-[260px] flex-col items-center sm:h-[300px]">
|
||||
<Image
|
||||
src="/images/phone.png"
|
||||
alt="phone"
|
||||
width={230}
|
||||
height={230}
|
||||
className="mb-6 w-[180px] h-[180px] sm:w-[230px] sm:h-[230px]"
|
||||
className="mb-6 h-[180px] w-[180px] sm:h-[230px] sm:w-[230px]"
|
||||
/>
|
||||
<div className="text-xl sm:text-2xl font-semibold text-center">
|
||||
<div className="text-center text-xl font-semibold sm:text-2xl">
|
||||
Свяжитесь с перевозчиком
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm sm:text-base text-gray-600 text-center px-2 sm:px-4 md:px-7">
|
||||
Нажмите на кнопку «ОТКЛИКНУТЬСЯ», свяжитесь и договоритесь о месте
|
||||
встречи и условиях перевозки
|
||||
<div className="px-2 text-center text-sm text-gray-600 sm:px-4 sm:text-base md:px-7">
|
||||
Нажмите на кнопку «ОТКЛИКНУТЬСЯ», свяжитесь и договоритесь о месте встречи и условиях
|
||||
перевозки
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center w-full sm:w-1/3 px-4 sm:px-2">
|
||||
<div className="flex flex-col items-center h-[260px] sm:h-[300px]">
|
||||
<div className="flex w-full flex-col items-center px-4 sm:w-1/3 sm:px-2">
|
||||
<div className="flex h-[260px] flex-col items-center sm:h-[300px]">
|
||||
<Image
|
||||
src="/images/package.png"
|
||||
alt="package"
|
||||
width={230}
|
||||
height={230}
|
||||
className="mb-6 w-[180px] h-[180px] sm:w-[230px] sm:h-[230px]"
|
||||
className="mb-6 h-[180px] w-[180px] sm:h-[230px] sm:w-[230px]"
|
||||
/>
|
||||
<div className="text-xl sm:text-2xl font-semibold text-center">
|
||||
Передайте посылку
|
||||
</div>
|
||||
<div className="text-center text-xl font-semibold sm:text-2xl">Передайте посылку</div>
|
||||
</div>
|
||||
<div className="text-sm sm:text-base text-gray-600 text-center px-2 sm:px-4 md:px-7">
|
||||
<div className="px-2 text-center text-sm text-gray-600 sm:px-4 sm:text-base md:px-7">
|
||||
Встречайтесь, знакомьтесь и передавайте посылку
|
||||
</div>
|
||||
</div>
|
||||
@@ -158,82 +149,62 @@ export default async function Home() {
|
||||
<div className="flex justify-center pt-8 sm:pt-10">
|
||||
<Button
|
||||
text="Отправить посылку"
|
||||
className="bg-orange hover:bg-orange/80 text-white text-base sm:text-lg px-8 sm:px-12 py-2.5 sm:py-3 rounded-lg"
|
||||
className="bg-orange hover:bg-orange/80 rounded-lg px-8 py-2.5 text-base text-white sm:px-12 sm:py-3 sm:text-lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* преимущества */}
|
||||
<div className="w-full max-w-[1250px] mx-auto my-10 px-4 sm:px-6">
|
||||
<h2 className="text-3xl sm:text-4xl text-center font-bold mb-12">
|
||||
Преимущества сервиса
|
||||
</h2>
|
||||
<div className="flex flex-col lg:flex-row items-center justify-between gap-6 lg:gap-4">
|
||||
<div className="flex flex-col w-full lg:w-1/4 space-y-4">
|
||||
<div className="mx-auto my-10 w-full max-w-[1250px] px-4 sm:px-6">
|
||||
<h2 className="mb-12 text-center text-3xl font-bold sm:text-4xl">Преимущества сервиса</h2>
|
||||
<div className="flex flex-col items-center justify-between gap-6 lg:flex-row lg:gap-4">
|
||||
<div className="flex w-full flex-col space-y-4 lg:w-1/4">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="text-xl text-center sm:text-left font-semibold">
|
||||
Прямой контакт
|
||||
</div>
|
||||
<span className="text-base text-center sm:text-left text-gray-600">
|
||||
<div className="text-center text-xl font-semibold sm:text-left">Прямой контакт</div>
|
||||
<span className="text-center text-base text-gray-600 sm:text-left">
|
||||
Общаешься напрямую с перевозчиком, никаких посредников
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="text-xl text-center sm:text-left font-semibold">
|
||||
Своя цена
|
||||
</div>
|
||||
<span className="text-base text-center sm:text-left text-gray-600">
|
||||
<div className="text-center text-xl font-semibold sm:text-left">Своя цена</div>
|
||||
<span className="text-center text-base text-gray-600 sm:text-left">
|
||||
Стоимость перевозки самостоятельно обговариваете с перевозчиком.
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="text-xl text-center sm:text-left font-semibold">
|
||||
<div className="text-center text-xl font-semibold sm:text-left">
|
||||
Нет доп. расходов
|
||||
</div>
|
||||
<span className="text-base text-center sm:text-left text-gray-600">
|
||||
Никаких комиссий, переплат и дополнительных расходов за
|
||||
отправку.
|
||||
<span className="text-center text-base text-gray-600 sm:text-left">
|
||||
Никаких комиссий, переплат и дополнительных расходов за отправку.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full lg:w-auto px-4 sm:px-0">
|
||||
<Image
|
||||
src="/images/advantage.svg"
|
||||
alt="advantages"
|
||||
width={648}
|
||||
height={403}
|
||||
/>
|
||||
<div className="w-full px-4 sm:px-0 lg:w-auto">
|
||||
<Image src="/images/advantage.svg" alt="advantages" width={648} height={403} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col w-full lg:w-1/4 space-y-4">
|
||||
<div className="flex w-full flex-col space-y-4 lg:w-1/4">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="text-xl text-center sm:text-left font-semibold">
|
||||
Уведомления
|
||||
</div>
|
||||
<span className="text-base text-center sm:text-left text-gray-600">
|
||||
Можешь самостоятельно найти перевозчиков или разместить
|
||||
<div className="text-center text-xl font-semibold sm:text-left">Уведомления</div>
|
||||
<span className="text-center text-base text-gray-600 sm:text-left">
|
||||
Можешь самостоятельно найти перевозчиков или разместить объявление на сайте.
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="text-center text-xl font-semibold sm:text-left">Удобный поиск</div>
|
||||
<span className="text-center text-base text-gray-600 sm:text-left">
|
||||
Как только по твоему объявлению найдется перевозчик мы сообщим на E-mail.
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="text-center text-xl font-semibold sm:text-left">Экономия времени</div>
|
||||
<span className="text-center text-base text-gray-600 sm:text-left">
|
||||
Не нужно искать группы, чаты, и кидать "клич", а просто достаточно разместить
|
||||
объявление на сайте.
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="text-xl text-center sm:text-left font-semibold">
|
||||
Удобный поиск
|
||||
</div>
|
||||
<span className="text-base text-center sm:text-left text-gray-600">
|
||||
Как только по твоему объявлению найдется перевозчик мы сообщим
|
||||
на E-mail.
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="text-xl text-center sm:text-left font-semibold">
|
||||
Экономия времени
|
||||
</div>
|
||||
<span className="text-base text-center sm:text-left text-gray-600">
|
||||
Не нужно искать группы, чаты, и кидать "клич", а просто
|
||||
достаточно разместить объявление на сайте.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
import avatar from '../../public/images/avatar.png'
|
||||
import belarusIcon from '../../public/images/belarus.png'
|
||||
import russiaIcon from '../../public/images/russia.png'
|
||||
|
||||
const userImg = avatar
|
||||
const blIcon = belarusIcon
|
||||
const ruIcon = russiaIcon
|
||||
|
||||
export const routes = 12845
|
||||
|
||||
export const data = [
|
||||
{
|
||||
id: 1123,
|
||||
username: 'John Doe',
|
||||
userImg: userImg,
|
||||
start_point: 'Минск',
|
||||
country_from: 'Беларусь',
|
||||
end_point: 'Москва',
|
||||
country_to: 'Россия',
|
||||
cargo_type: 'Документы',
|
||||
user_request: 'Нужен перевозчик',
|
||||
user_comment: 'Нужно перевезти документы из Минска в Москву',
|
||||
country_from_icon: blIcon,
|
||||
country_to_icon: ruIcon,
|
||||
country_from_code: 'BY',
|
||||
country_to_code: 'RU',
|
||||
moving_type: 'Авиатранспорт',
|
||||
estimated_date: new Date(2025, 4, 15),
|
||||
},
|
||||
{
|
||||
id: 2423,
|
||||
username: 'John Doe',
|
||||
userImg: userImg,
|
||||
start_point: 'Минск',
|
||||
country_from: 'Беларусь',
|
||||
end_point: 'Москва',
|
||||
country_to: 'Россия',
|
||||
cargo_type: 'Документы',
|
||||
user_request: 'Могу перевезти',
|
||||
user_comment: 'Нужно перевезти документы из Минска в Москву',
|
||||
moving_type: 'Автоперевозка',
|
||||
estimated_date: new Date(2025, 5, 18),
|
||||
country_from_icon: blIcon,
|
||||
country_to_icon: ruIcon,
|
||||
country_from_code: 'BY',
|
||||
country_to_code: 'RU',
|
||||
day_out: new Date(2025, 5, 21),
|
||||
day_in: new Date(2025, 5, 25),
|
||||
},
|
||||
]
|
||||
|
||||
export const news = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'Высокий уровень вовлечения представителей целевой аудитории',
|
||||
description:
|
||||
'Значимость этих проблем настолько очевидна, что экономическая повестка сегодняшнего дня не оставляет шанса для',
|
||||
image: '/images/news.svg',
|
||||
slug: 'vysoki-uroven-vlezheniya-predstaviteley-tselyovoi-auditorii',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Высокий уровень вовлечения представителей целевой аудитории',
|
||||
description:
|
||||
'Значимость этих проблем настолько очевидна, что экономическая повестка сегодняшнего дня не оставляет шанса для',
|
||||
image: '/images/news.svg',
|
||||
slug: 'vysoki-uroven-vlezheniya-predstaviteley-tselyovoi-auditorii',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'Высокий уровень вовлечения представителей целевой аудитории',
|
||||
description:
|
||||
'Значимость этих проблем настолько очевидна, что экономическая повестка сегодняшнего дня не оставляет шанса дляЗначимость этих проблем настолько очевидна, что экономическая повестка сегодняшнего дня не оставляет шанса дляЗначимость этих проблем настолько очевидна, что экономическая повестка сегодняшнего дня не оставляет шанса дляЗначимость этих проблем настолько очевидна, что экономическая повестка сегодняшнего дня не оставляет шанса для',
|
||||
image: '/images/news.svg',
|
||||
slug: 'vysoki-uroven-vlezheniya-predstaviteley-tselyovoi-auditorii',
|
||||
},
|
||||
]
|
||||
@@ -153,3 +153,62 @@ export interface TextAreaProps {
|
||||
placeholder: string
|
||||
height?: string | number
|
||||
}
|
||||
|
||||
export interface Route {
|
||||
id: string
|
||||
from_city_name: string
|
||||
to_city_name: string
|
||||
from_country_name: string
|
||||
to_country_name: string
|
||||
formatted_departure: string
|
||||
formatted_arrival: string
|
||||
formatted_cargo_type: string
|
||||
formatted_transport: string
|
||||
comment?: string
|
||||
owner_type: string
|
||||
}
|
||||
|
||||
export interface SelectOption {
|
||||
id: number
|
||||
label: string
|
||||
}
|
||||
|
||||
export interface MultiSelectProps {
|
||||
value: number[]
|
||||
handleChange: (e: { target: { id: string; value: number[] } }) => void
|
||||
label?: string
|
||||
placeholder?: string
|
||||
name: string
|
||||
options: SelectOption[]
|
||||
className?: string
|
||||
noOptionsMessage?: string
|
||||
}
|
||||
|
||||
type FormValue = string | number | boolean | string[] | number[]
|
||||
|
||||
export interface SenderPageProps extends Record<string, FormValue> {
|
||||
transport: string
|
||||
country_from: string
|
||||
city_from: string
|
||||
country_to: string
|
||||
city_to: string
|
||||
cargo_type: string
|
||||
departure: string
|
||||
arrival: string
|
||||
contact_number: string
|
||||
comment: string
|
||||
email_notification: boolean
|
||||
}
|
||||
|
||||
export type CargoType = 'letter' | 'package' | 'passenger' | 'parcel' | 'cargo'
|
||||
export type TransportType = 'road' | 'avia' | 'both'
|
||||
|
||||
export interface CheckboxProps {
|
||||
checked: boolean
|
||||
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void
|
||||
label: string
|
||||
name: string
|
||||
info?: string
|
||||
disabledText: string
|
||||
enabledText: string
|
||||
}
|
||||
|
||||
62
frontend/components/ui/CheckboxInput.tsx
Normal file
62
frontend/components/ui/CheckboxInput.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import { CheckboxProps } from '@/app/types'
|
||||
import { HiQuestionMarkCircle } from 'react-icons/hi'
|
||||
|
||||
const CheckboxInput = ({
|
||||
handleChange,
|
||||
label,
|
||||
name,
|
||||
checked,
|
||||
info,
|
||||
enabledText,
|
||||
disabledText,
|
||||
}: CheckboxProps) => {
|
||||
const [showTooltip, setShowTooltip] = useState(false)
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<label className="mb-2 block text-sm font-medium text-gray-500" htmlFor={name}>
|
||||
{label}
|
||||
</label>
|
||||
<div className="flex items-center space-x-3">
|
||||
<label className="relative inline-flex cursor-pointer items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={name}
|
||||
checked={checked || false}
|
||||
onChange={handleChange}
|
||||
className="peer sr-only"
|
||||
/>
|
||||
<div className="peer peer-checked:bg-orange h-6 w-11 rounded-full bg-gray-200 peer-focus:outline-none after:absolute after:start-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[''] peer-checked:after:translate-x-full peer-checked:after:border-white rtl:peer-checked:after:-translate-x-full"></div>
|
||||
<span
|
||||
className={`ms-3 text-sm select-none ${
|
||||
checked ? 'font-medium text-green-600' : 'text-gray-700'
|
||||
}`}
|
||||
>
|
||||
{checked ? `${enabledText}` : `${disabledText}`}
|
||||
</span>
|
||||
</label>
|
||||
<div className="relative flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
className="text-gray-400 hover:text-gray-600 focus:outline-none"
|
||||
onMouseEnter={() => setShowTooltip(true)}
|
||||
onMouseLeave={() => setShowTooltip(false)}
|
||||
onClick={() => setShowTooltip(!showTooltip)}
|
||||
>
|
||||
<HiQuestionMarkCircle className="h-5 w-5" />
|
||||
</button>
|
||||
{showTooltip && (
|
||||
<div className="absolute bottom-full left-1/2 z-10 mb-3 w-max max-w-xs -translate-x-1/2 rounded-xl bg-white px-4 py-2 text-center text-sm text-gray-700 shadow-lg">
|
||||
{info}
|
||||
<div className="absolute -bottom-2 left-1/2 h-2 w-2 -translate-x-1/2 rotate-45 transform bg-white"></div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CheckboxInput
|
||||
113
frontend/components/ui/Selector.tsx
Normal file
113
frontend/components/ui/Selector.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import React from 'react'
|
||||
import Select from 'react-select'
|
||||
import { MultiSelectProps } from '@/app/types'
|
||||
|
||||
const MultiSelect = ({
|
||||
value,
|
||||
handleChange,
|
||||
label,
|
||||
placeholder = 'Выберите опции',
|
||||
name,
|
||||
options,
|
||||
className = '',
|
||||
noOptionsMessage = 'Нет доступных опций',
|
||||
}: MultiSelectProps) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
{label && (
|
||||
<label className="my-2 block text-sm font-medium text-gray-500" htmlFor={name}>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<Select
|
||||
isMulti
|
||||
name={name}
|
||||
id={name}
|
||||
options={options}
|
||||
value={options.filter(option => value.includes(option.id))}
|
||||
onChange={selectedOptions => {
|
||||
handleChange({
|
||||
target: {
|
||||
id: name,
|
||||
value: selectedOptions ? selectedOptions.map(opt => opt.id) : [],
|
||||
},
|
||||
})
|
||||
}}
|
||||
getOptionValue={option => option.id.toString()}
|
||||
className="rounded-lg"
|
||||
classNamePrefix="select"
|
||||
placeholder={placeholder}
|
||||
noOptionsMessage={() => noOptionsMessage}
|
||||
styles={{
|
||||
control: base => ({
|
||||
...base,
|
||||
borderRadius: '0.75rem',
|
||||
backgroundColor: '#F3F4F6',
|
||||
border: '1px solid #E5E7EB',
|
||||
padding: '2px',
|
||||
'&:hover': {
|
||||
borderColor: '#E5E7EB',
|
||||
},
|
||||
'&:focus-within': {
|
||||
backgroundColor: '#FFFFFF',
|
||||
borderColor: '#E5E7EB',
|
||||
boxShadow: '0 0 0 2px rgba(59, 130, 246, 0.5)',
|
||||
},
|
||||
}),
|
||||
menu: base => ({
|
||||
...base,
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
zIndex: 9999,
|
||||
marginTop: '4px',
|
||||
borderRadius: '0.75rem',
|
||||
overflow: 'hidden',
|
||||
}),
|
||||
menuList: base => ({
|
||||
...base,
|
||||
padding: '4px',
|
||||
}),
|
||||
multiValue: base => ({
|
||||
...base,
|
||||
backgroundColor: '#EFF6FF',
|
||||
borderRadius: '0.5rem',
|
||||
}),
|
||||
multiValueLabel: base => ({
|
||||
...base,
|
||||
color: '#2563EB',
|
||||
}),
|
||||
multiValueRemove: base => ({
|
||||
...base,
|
||||
color: '#2563EB',
|
||||
':hover': {
|
||||
backgroundColor: '#DBEAFE',
|
||||
color: '#1E40AF',
|
||||
},
|
||||
}),
|
||||
menuPortal: base => ({
|
||||
...base,
|
||||
zIndex: 9999,
|
||||
}),
|
||||
option: (base, state) => ({
|
||||
...base,
|
||||
fontSize: '0.875rem',
|
||||
padding: '8px 12px',
|
||||
backgroundColor: state.isSelected ? '#EFF6FF' : state.isFocused ? '#F3F4F6' : 'white',
|
||||
color: state.isSelected ? '#2563EB' : '#1F2937',
|
||||
cursor: 'pointer',
|
||||
'&:active': {
|
||||
backgroundColor: '#DBEAFE',
|
||||
},
|
||||
'&:hover': {
|
||||
backgroundColor: state.isSelected ? '#EFF6FF' : '#F3F4F6',
|
||||
},
|
||||
}),
|
||||
}}
|
||||
menuPortalTarget={document.body}
|
||||
menuPosition="fixed"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MultiSelect
|
||||
Reference in New Issue
Block a user