3 Commits

Author SHA1 Message Date
e766284333 create ui components 2025-05-13 17:49:51 +03:00
837c0f1fb6 footer desktop 2025-05-13 11:43:16 +03:00
a5ffa7ae13 create header 2025-05-12 17:23:05 +03:00
32 changed files with 1052 additions and 24 deletions

80
Pipfile Normal file
View File

@@ -0,0 +1,80 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
aiohappyeyeballs = "==2.4.3"
aiohttp = "==3.11.7"
aiosignal = "==1.3.1"
asgiref = "==3.8.1"
async-timeout = "==5.0.1"
attrs = "==24.2.0"
autobahn = "==24.4.2"
automat = "==24.8.1"
certifi = "==2024.8.30"
cffi = "==1.17.1"
channels = "==4.0.0"
channels-redis = "==4.1.0"
charset-normalizer = "==3.4.0"
constantly = "==23.10.4"
cryptography = "==43.0.3"
daphne = "==4.0.0"
defusedxml = "==0.7.1"
django = "==4.2.2"
django-allauth = "==0.60.0"
django-ckeditor = "==6.5.1"
django-colorfield = "==0.11.0"
django-js-asset = "==2.2.0"
django-modeltranslation = "==0.18.10"
django-rosetta = "==0.10.0"
django-webpush = "==0.3.6"
frozenlist = "==1.5.0"
geographiclib = "==2.0"
geopy = "==2.4.1"
h3 = "==3.7.7"
http-ece = "==1.2.1"
hyperlink = "==21.0.0"
idna = "==3.10"
incremental = "==24.7.2"
msgpack = "==1.1.0"
multidict = "==6.1.0"
numpy = "==2.0.2"
oauthlib = "==3.2.2"
osm2geojson = "==0.2.5"
overpass = "==0.7.2"
pillow = "==9.5.0"
polib = "==1.2.0"
propcache = "==0.2.0"
psycopg2-binary = "==2.9.6"
py-vapid = "==1.9.2"
pyasn1 = "==0.6.1"
pyasn1-modules = "==0.4.1"
pycparser = "==2.22"
pyjwt = "==2.10.0"
pyopenssl = "==24.2.1"
python3-openid = "==3.2.0"
pytz = "==2024.1"
pywebpush = "==2.0.3"
redis = "==5.2.0"
requests = "==2.32.3"
requests-oauthlib = "==2.0.0"
requests-pkcs12 = "==1.24"
service-identity = "==24.2.0"
shapely = "==2.0.6"
six = "==1.16.0"
sqlparse = "==0.5.2"
timezonefinder = "==6.5.2"
tomli = "==2.1.0"
twisted = "==24.10.0"
txaio = "==23.1.1"
typing-extensions = "==4.12.2"
urllib3 = "==2.2.3"
yarl = "==1.18.0"
"zope.interface" = "==7.1.1"
[dev-packages]
[requires]
python_version = "3.12"
python_full_version = "3.12.5"

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-05-12 13:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('RoutesApp', '0013_alter_route_cargo_type'),
]
operations = [
migrations.AlterField(
model_name='route',
name='cargo_type',
field=models.CharField(choices=[('letter', 'Письмо\\Документы'), ('package', 'Посылка (до 30кг)'), ('passenger', 'Попутчик'), ('parcel', 'Бандероль (до 5кг)'), ('cargo', 'Груз (свыше 30 кг)')], default='letter', verbose_name='Могу перевезти'),
),
]

View File

@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/4.2/ref/settings/
"""
from pathlib import Path
from django.urls import reverse_lazy
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@@ -57,7 +58,7 @@ ACCOUNT_AUTHENTICATION_METHOD = 'email'
ACCOUNT_EMAIL_VERIFICATION = 'optional'
LOGIN_REDIRECT_URL = '/profile/page/dashboard/'
# LOGIN_URL = '/profile/login/'
from django.urls import reverse_lazy
LOGIN_URL = reverse_lazy('login_profile')
LOGOUT_REDIRECT_URL = '/profile/login/'
@@ -204,9 +205,9 @@ CHANNEL_LAYERS = {
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'twbDBv2',
'USER': 'test_user',
'PASSWORD': 'test_db_pass',
'NAME': 'tripwithbonus',
'USER': 'twbuser',
'PASSWORD': 'twbuser',
'HOST': '127.0.0.1',
'PORT': '5432',
}

41
frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

36
frontend/README.md Normal file
View File

@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

BIN
frontend/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

27
frontend/app/globals.css Normal file
View File

@@ -0,0 +1,27 @@
@import 'tailwindcss';
:root {
--background: #eeebeb;
--foreground: #171717;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-orange: #ff613a;
}
@media (prefers-color-scheme: light) {
:root {
--background: #eeebeb;
--foreground: #171717;
}
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}

37
frontend/app/layout.tsx Normal file
View File

@@ -0,0 +1,37 @@
import type { Metadata } from 'next'
import { Geist, Geist_Mono } from 'next/font/google'
import './globals.css'
import Header from '@/components/Header'
import Footer from '@/components/Footer'
const geistSans = Geist({
variable: '--font-geist-sans',
subsets: ['latin'],
})
const geistMono = Geist_Mono({
variable: '--font-geist-mono',
subsets: ['latin'],
})
export const metadata: Metadata = {
title: 'Отправка посылок в любую точку мира | TripWB',
description:
'Международная отправка посылок ✓ Отправка посылки в любую точку планеты ✓ Приемлемая цена отправки посылки ✓ Доставка в кратчайшие сроки ➡️ Обращайтесь к нам',
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en" className={`${geistSans.variable} ${geistMono.variable}`}>
<body className="min-h-screen flex flex-col">
<Header />
<main className="flex-grow">{children}</main>
<Footer />
</body>
</html>
)
}

48
frontend/app/page.tsx Normal file
View File

@@ -0,0 +1,48 @@
import React from 'react'
import Image from 'next/image'
import AddressSelector from '@/components/AddressSelector'
export default function Home() {
const routes = 38464
return (
<div className="flex flex-col items-center justify-center max-w-[93%] mx-auto">
<div className="flex items-center justify-center space-x-16">
<div>
<Image src="/images/box1.png" alt="main" width={220} height={220} />
</div>
<div className="flex flex-col items-center justify-center space-y-5">
<h1 className="text-4xl text-center font-bold">
<div className="pb-1">
Сервис по <span className="text-orange">поиску</span>{' '}
</div>
<div>
<span className="text-orange">перевозчиков</span> посылок
</div>
</h1>
<p className="text-lg text-center">
Доставка посылок с попутчиками: от документов до крупногабаритных
грузов
</p>
</div>
<div>
<Image src="/images/box2.png" alt="main" width={220} height={220} />
</div>
</div>
<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>
<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>
</div>
</div>
)
}

11
frontend/app/types.ts Normal file
View File

@@ -0,0 +1,11 @@
export interface TextInputProps {
value: string
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void
label?: string
placeholder?: string
name: string
type?: string
className?: string
maxLength?: number
tooltip?: string | React.ReactNode
}

View File

@@ -0,0 +1,18 @@
export interface TextInputProps {
value: string
handleChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
label?: string
placeholder?: string
name: string
type?: 'text' | 'email' | 'password'
className?: string
maxLength?: number
tooltip?: string | React.ReactNode
}
export interface ButtonProps {
onClick?: () => void
className?: string
text?: string
type?: 'button'
}

View File

@@ -0,0 +1,45 @@
'use client'
import React, { useState } from 'react'
import TextInput from './ui/TextInput'
import Button from './ui/Button'
export default function AddressSelector() {
const [fromAddress, setFromAddress] = useState('')
const [toAddress, setToAddress] = useState('')
return (
<div className="bg-white rounded-xl shadow-lg p-6 w-full my-4">
<div className="flex flex-col sm:flex-row items-end gap-3">
<div className="flex-[3] min-w-0 px-1">
<TextInput
placeholder="Минск, Беларусь"
tooltip="Укажите пункт (Город/Страна), откуда необходимо забрать посылку."
label="Забрать посылку из"
value={fromAddress}
handleChange={(e) => setFromAddress(e.target.value)}
name="fromAddress"
/>
</div>
<div className="flex-[3] min-w-0 px-1">
<TextInput
placeholder="Москва, Россия"
label="Доставить посылку в"
tooltip="Укажите пункт (Город/Страна), куда необходимо доставить посылку."
value={toAddress}
handleChange={(e) => setToAddress(e.target.value)}
name="toAddress"
/>
</div>
<Button
text="Найти перевозчика"
className="flex-1 whitespace-nowrap bg-orange hover:bg-orange/80 text-white p-4"
/>
<Button
text="Найти посылку"
className="flex-1 whitespace-nowrap bg-gray-100 hover:bg-gray-200 text-gray-800 p-4"
/>
</div>
</div>
)
}

View File

@@ -0,0 +1,36 @@
'use client'
import React, { useState } from 'react'
import Image from 'next/image'
const EmailHandler = () => {
const [email, setEmail] = useState('')
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setEmail(e.target.value)
}
return (
<div className="relative w-full">
<input
type="email"
value={email}
onChange={handleChange}
name="email"
placeholder="Введите ваш e-mail"
className="w-full px-4 py-3 rounded-md bg-white text-black placeholder:text-gray-400"
/>
<button className="absolute right-0 top-1/2 -translate-y-1/2 flex items-center justify-center cursor-pointer">
<Image
src="/images/subscribe.png"
alt="submit"
width={50}
height={50}
className="rounded-md"
/>
</button>
</div>
)
}
export default EmailHandler

View File

@@ -0,0 +1,255 @@
import React from 'react'
import Image from 'next/image'
import Link from 'next/link'
import {
FaInstagram,
FaTelegram,
FaVk,
FaFacebook,
FaYoutube,
FaTiktok,
} from 'react-icons/fa'
import EmailHandler from './EmailHandler'
const Footer = () => {
return (
<div>
<div className="bg-[#272424]">
<div className="flex flex-col md:flex-row px-6 py-8 w-[93%] mx-auto gap-8 md:gap-16">
{/* левый таб */}
<div className="flex flex-col space-y-5 w-full md:w-1/4 items-center md:items-start text-center md:text-left">
<Image
src="/images/footerLogo.png"
alt="logo"
width={50}
height={50}
/>
<span className="text-sm text-gray-300">
Подпишись и будь в курсе всех событий, а также получай подарки и
бонусы от Trip With Bonus
</span>
<EmailHandler />
<div className="flex gap-4 justify-center md:justify-start">
<a
href="https://instagram.com"
target="_blank"
rel="noopener noreferrer"
>
<FaInstagram
size={20}
className="text-white hover:text-orange transition-colors"
/>
</a>
<a
href="https://telegram.com"
target="_blank"
rel="noopener noreferrer"
>
<FaTelegram
size={20}
className="text-white hover:text-orange transition-colors"
/>
</a>
<a
href="https://vk.com"
target="_blank"
rel="noopener noreferrer"
>
<FaVk
size={20}
className="text-white hover:text-orange transition-colors"
/>
</a>
<a
href="https://facebook.com"
target="_blank"
rel="noopener noreferrer"
>
<FaFacebook
size={20}
className="text-white hover:text-orange transition-colors"
/>
</a>
<a
href="https://tiktok.com"
target="_blank"
rel="noopener noreferrer"
>
<FaTiktok
size={20}
className="text-white hover:text-orange transition-colors"
/>
</a>
<a
href="https://youtube.com"
target="_blank"
rel="noopener noreferrer"
>
<FaYoutube
size={20}
className="text-white hover:text-orange transition-colors"
/>
</a>
</div>
</div>
{/* ссылки */}
<div className="flex flex-col md:flex-row justify-between w-full md:w-2/4 space-y-8 md:space-y-0 items-center md:items-start text-center md:text-left">
{/* информация */}
<div className="flex flex-col space-y-4 items-center md:items-start">
<div className="text-white text-xl font-medium md:pb-5">
Информация
</div>
<div className="flex flex-col space-y-1">
<Link
href="/"
className="text-gray-300 hover:text-orange transition-colors"
>
Перевезти посылку
</Link>
<Link
href="/"
className="text-gray-300 hover:text-orange transition-colors"
>
Отправить посылку
</Link>
<Link
href="/"
className="text-gray-300 hover:text-orange transition-colors"
>
Для отправителя
</Link>
<Link
href="/"
className="text-gray-300 hover:text-orange transition-colors"
>
Для перевозчика
</Link>
</div>
</div>
{/* О Trip With Bonus */}
<div className="flex flex-col space-y-4 items-center md:items-start">
<div className="text-white text-xl font-medium md:pb-5">
О Trip With Bonus
</div>
<div className="flex flex-col space-y-1">
<Link
href="/"
className="text-gray-300 hover:text-orange transition-colors"
>
Новости
</Link>
<Link
href="/"
className="text-gray-300 hover:text-orange transition-colors"
>
Партнерам
</Link>
<Link
href="/"
className="text-gray-300 hover:text-orange transition-colors"
>
Реклама
</Link>
</div>
</div>
<div className="flex flex-col space-y-4 items-center md:items-start">
<div className="text-white text-xl font-medium md:pb-5">
Кооперация
</div>
<div className="flex flex-col space-y-1">
<Link
href="/"
className="text-gray-300 hover:text-orange transition-colors"
>
Реклама
</Link>
<Link
href="/"
className="text-gray-300 hover:text-orange transition-colors"
>
Поддержка
</Link>
<Link
href="/"
className="text-gray-300 hover:text-orange transition-colors"
>
Контакты
</Link>
</div>
</div>
</div>
<div className="flex flex-col space-y-4 w-full md:w-1/4 items-center md:items-start text-center md:text-left">
<div className="text-white text-xl font-medium md:pb-5">
Свяжитесь с нами:
</div>
<div>
<div className="flex flex-col space-y-3">
<a
href="tel:+375291234567"
target="_blank"
rel="noopener noreferrer"
className="hover:opacity-80 transition-opacity text-gray-300 hover:text-orange"
>
+ 7 (777) 777-77-77{' '}
</a>
<a
href="mailto:sales@tripwb.com"
target="_blank"
rel="noopener noreferrer"
className="hover:opacity-80 transition-opacity text-gray-300 hover:text-orange"
>
sales@tripwb.com
</a>
<a
href="mailto:support@tripwb.com"
target="_blank"
rel="noopener noreferrer"
className="hover:opacity-80 transition-opacity text-gray-300 hover:text-orange"
>
support@tripwb.com
</a>
</div>
<div className="flex flex-col md:flex-row gap-2 mt-7 justify-center md:justify-start">
<Link
href="/register"
className="bg-orange hover:bg-orange/80 text-white px-4 py-2 rounded-2xl text-base font-medium"
>
Регистрация
</Link>
<Link
href="/login"
className="text-white hover:text-orange transition-colors py-2"
>
Войти
</Link>
</div>
</div>
</div>
</div>
</div>
{/* Нижняя часть футера */}
<div>
<div className="flex flex-col justify-between md:flex-row px-6 py-4 md:py-8 w-[95%] mx-auto text-center md:text-left">
<div>Copyright © {new Date().getFullYear()}. Все права защищены.</div>
<div className="flex flex-col md:flex-row py-4 md:py-0 md:space-x-5 items-center md:items-start">
<Link href="/" className="hover:text-orange">
Публичная оферта
</Link>
<Link href="/" className="hover:text-orange">
Политика конфиденциальности
</Link>
<Link href="/" className="hover:text-orange">
Правила пользования сервисом
</Link>
</div>
</div>
</div>
</div>
)
}
export default Footer

View File

@@ -0,0 +1,66 @@
import React from 'react'
import Image from 'next/image'
import Link from 'next/link'
import Burger from './ui/Burger'
import LangSwitcher from './LangSwitcher'
import Button from './ui/Button'
const Header = () => {
return (
<div className="flex justify-between items-center px-6 py-8 w-[93%] mx-auto">
<div className="flex items-center justify-center space-x-10">
<Image
src="/images/logo.png"
alt="logo"
width={50}
height={50}
priority
/>
<div className="hidden md:block">
<Burger />
</div>
<Link
href="/"
className="text-base font-medium px-4 hover:text-orange transition-colors hidden md:block"
>
Могу взять посылку
</Link>
</div>
<div className="flex items-center justify-center space-x-10">
<LangSwitcher />
<Button
text="Разместить объявление"
className="hidden md:block bg-orange hover:bg-orange/80 px-4 py-3 text-white"
/>
<div className="hidden md:block text-base font-medium">
<Link
href="/register"
className="hover:text-orange transition-colors"
>
Регистрация
</Link>
<span> / </span>
<Link href="/login" className="hover:text-orange transition-colors">
Войти
</Link>
</div>
<div className="flex items-center space-x-4 md:hidden">
<Link href="/login">
<Image
src="/images/userlogo.png"
alt="user"
width={24}
height={24}
/>
</Link>
<Burger />
</div>
</div>
</div>
)
}
export default Header

View File

@@ -0,0 +1,31 @@
'use client'
import React, { useState } from 'react'
const LangSwitcher = () => {
const [selectedLang, setSelectedLang] = useState('ru')
return (
<div className="flex items-center space-x-2">
<button
onClick={() => setSelectedLang('ru')}
className={`${
selectedLang === 'ru' ? 'text-orange' : 'text-gray-500'
} cursor-pointer`}
>
RU
</button>
<span className="text-gray-500">/</span>
<button
onClick={() => setSelectedLang('en')}
className={`${
selectedLang === 'en' ? 'text-orange' : 'text-gray-500'
} cursor-pointer`}
>
EN
</button>
</div>
)
}
export default LangSwitcher

View File

@@ -0,0 +1,82 @@
'use client'
import React, { useState } from 'react'
import Link from 'next/link'
import Button from './Button'
const Burger = () => {
const [isOpen, setIsOpen] = useState(false)
return (
<>
<button
onClick={() => setIsOpen(!isOpen)}
className="relative z-50 w-8 h-8 flex flex-col justify-center items-center"
>
<div className="relative w-8 h-8">
<span
className={`absolute h-0.5 bg-orange transition-all duration-300 ease-in-out ${
isOpen ? 'w-8 rotate-45 top-4' : 'w-8 top-2'
}`}
></span>
<span
className={`absolute h-0.5 bg-orange transition-all duration-300 ease-in-out ${
isOpen ? 'w-0 opacity-0 top-4' : 'w-8 top-4'
}`}
></span>
<span
className={`absolute h-0.5 bg-orange transition-all duration-300 ease-in-out ${
isOpen ? 'w-8 -rotate-45 top-4' : 'w-8 top-6'
}`}
></span>
</div>
</button>
{/* меню */}
<div
className={`fixed z-50 left-0 w-full bg-[#eeebeb] shadow-lg transition-all duration-300 ease-in-out origin-top ${
isOpen
? 'top-[80px] h-[calc(100vh-80px)] opacity-100 scale-y-100'
: 'top-[80px] h-0 opacity-0 scale-y-0'
} ${isOpen ? 'pointer-events-auto' : 'pointer-events-none'}`}
>
<div
className={`flex flex-col items-center pt-12 space-y-8 transition-all duration-300 ${
isOpen ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-4'
}`}
>
<Link
href="/"
className="text-xl hover:text-orange transition-colors duration-200"
onClick={() => setIsOpen(false)}
>
Ссылка 1
</Link>
<Link
href="/search"
className="text-xl hover:text-orange transition-colors duration-200"
onClick={() => setIsOpen(false)}
>
Ссылка 2
</Link>
<Link
href="/about"
className="text-xl hover:text-orange transition-colors duration-200"
onClick={() => setIsOpen(false)}
>
Ссылка 3
</Link>
<Link
href="/"
className="text-xl hover:text-orange transition-colors duration-200"
onClick={() => setIsOpen(false)}
>
Могу взять посылку
</Link>
<Button text="Разместить объявление" />
</div>
</div>
</>
)
}
export default Burger

View File

@@ -0,0 +1,16 @@
import React from 'react'
import { ButtonProps } from '@/app/types'
const Button = ({ onClick, className, text, type }: ButtonProps) => {
return (
<button
onClick={onClick}
className={`text-base font-medium rounded-2xl cursor-pointer ${className}`}
type={type}
>
<span>{text}</span>
</button>
)
}
export default Button

View File

@@ -0,0 +1,41 @@
import React from 'react'
import { TextInputProps } from '@/app/types'
import Tooltip from './Tooltip'
const TextInput = ({
value,
handleChange,
label,
placeholder,
name,
type = 'text',
className = '',
maxLength,
tooltip,
}: TextInputProps) => {
return (
<div className={className}>
{label && (
<div className="flex items-center gap-2 my-2">
<label className="font-medium text-sm text-gray-500" htmlFor={name}>
{label}
</label>
{tooltip && <Tooltip content={tooltip} />}
</div>
)}
<input
type={type}
id={name}
name={name}
placeholder={placeholder}
value={value || ''}
onChange={handleChange}
className="w-full p-4 border border-gray-300 text-black rounded-xl focus:outline-none focus:ring-2 focus:ring-mainblocks focus:bg-white"
autoComplete={name}
maxLength={maxLength}
/>
</div>
)
}
export default TextInput

View File

@@ -0,0 +1,34 @@
'use client'
import { useState } from 'react'
import { CiCircleInfo } from 'react-icons/ci'
interface TooltipProps {
content: string | React.ReactNode
}
const Tooltip = ({ content }: TooltipProps) => {
const [showTooltip, setShowTooltip] = useState(false)
return (
<div className="relative flex items-center overflow-visible">
<button
type="button"
className="text-orange hover:text-orange/80 focus:outline-none"
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
onClick={() => setShowTooltip(!showTooltip)}
>
<CiCircleInfo className="w-4 h-4" />
</button>
{showTooltip && (
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-3 px-4 py-2 bg-white rounded-xl text-center shadow-lg text-sm text-gray-700 w-max max-w-xs z-10">
{content}
<div className="absolute -bottom-2 left-1/2 -translate-x-1/2 w-2 h-2 bg-white transform rotate-45"></div>
</div>
)}
</div>
)
}
export default Tooltip

View File

@@ -0,0 +1,16 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];
export default eslintConfig;

7
frontend/next.config.ts Normal file
View File

@@ -0,0 +1,7 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
};
export default nextConfig;

View File

@@ -0,0 +1,5 @@
const config = {
plugins: ["@tailwindcss/postcss"],
};
export default config;

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

27
frontend/tsconfig.json Normal file
View File

@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}

View File

@@ -1,20 +0,0 @@
Django==4.2.2
django-ckeditor==6.5.1
psycopg2-binary==2.9.6
requests
Pillow==9.5.0
django-modeltranslation==0.18.10
overpass
geopy
channels==4.0.0
daphne==4.0.0
channels-redis==4.1.0
django-colorfield
django-webpush==0.3.6
django-allauth==0.60.0
pytz==2024.1
requests-pkcs12==1.24
#django-tz-detect==0.4.0
django-rosetta==0.10.0
timezonefinder==6.5.2

70
requirements.txt Normal file
View File

@@ -0,0 +1,70 @@
aiohappyeyeballs==2.6.1
aiohttp==3.11.18
aiosignal==1.3.2
asgiref==3.8.1
async-timeout==5.0.1
attrs==25.3.0
autobahn==24.4.2
Automat==25.4.16
certifi==2025.4.26
cffi==1.17.1
channels==4.2.2
channels_redis==4.2.1
charset-normalizer==3.4.2
constantly==23.10.4
cryptography==44.0.3
daphne==4.1.2
defusedxml==0.7.1
Django==5.2.1
django-allauth==65.8.0
django-ckeditor==6.7.2
django-colorfield==0.14.0
django-js-asset==3.1.2
django-modeltranslation==0.19.14
django-rosetta==0.10.2
django-webpush==0.3.6
frozenlist==1.6.0
geographiclib==2.0
geojson==3.2.0
geopy==2.4.1
h3==4.2.2
http_ece==1.2.1
hyperlink==21.0.0
idna==3.10
incremental==24.7.2
msgpack==1.1.0
multidict==6.4.3
numpy==2.2.5
oauthlib==3.2.2
osm2geojson==0.2.6
overpass==0.7
pillow==11.2.1
polib==1.2.0
propcache==0.3.1
psycopg2-binary==2.9.10
py-vapid==1.9.2
pyasn1==0.6.1
pyasn1_modules==0.4.2
pycparser==2.22
PyJWT==2.10.1
pyOpenSSL==25.0.0
python3-openid==3.2.0
pytz==2025.2
pywebpush==2.0.3
redis==6.0.0
requests==2.32.3
requests-oauthlib==2.0.0
requests-pkcs12==1.25
service-identity==24.2.0
setuptools==80.4.0
shapely==2.1.0
six==1.17.0
sqlparse==0.5.3
timezonefinder==6.5.9
tomli==2.2.1
Twisted==24.11.0
txaio==23.1.1
typing_extensions==4.13.2
urllib3==2.4.0
yarl==1.20.0
zope.interface==7.2