LocalPro Finder v2 — без AI-диагностики, Coming Soon окно
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "freelancer-match-frontend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@radix-ui/react-dialog": "^1.1.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-select": "^2.1.2",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-tabs": "^1.1.1",
|
||||
"@tanstack/react-query": "^5.60.5",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.460.0",
|
||||
"next": "^14.2.0",
|
||||
"react": "^18.3.0",
|
||||
"react-dom": "^18.3.0",
|
||||
"react-hook-form": "^7.53.2",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"zod": "^3.23.8",
|
||||
"zustand": "^4.5.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.17.0",
|
||||
"@types/react": "^18.3.0",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^8.57.0",
|
||||
"postcss": "^8.4.49",
|
||||
"tailwindcss": "^3.4.15",
|
||||
"typescript": "^5.6.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { useState } from "react";
|
||||
import { Search, MapPin, Star, MessageCircle, ShieldCheck, Clock, ChevronRight } from "lucide-react";
|
||||
|
||||
export default function Home() {
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [location, setLocation] = useState("");
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-blue-50 to-white">
|
||||
{/* Hero */}
|
||||
<section className="relative overflow-hidden bg-gradient-to-r from-blue-600 to-indigo-700 text-white py-24 px-8">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<h1 className="text-5xl font-bold mb-4">LocalPro Finder</h1>
|
||||
<p className="text-xl opacity-90 mb-8">Найдите проверенных мастеров рядом с вами</p>
|
||||
|
||||
{/* Search */}
|
||||
<div className="flex gap-3 max-w-2xl mx-auto">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Что вам нужно?"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="flex-1 px-4 py-3 rounded-lg text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-300"
|
||||
/>
|
||||
<button className="px-6 py-3 bg-white text-blue-700 font-semibold rounded-lg hover:bg-blue-50 transition">
|
||||
Найти
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Location */}
|
||||
<div className="flex items-center justify-center gap-2 mt-4 text-sm opacity-80">
|
||||
<MapPin size={16} />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Ваш город или адрес"
|
||||
value={location}
|
||||
onChange={(e) => setLocation(e.target.value)}
|
||||
className="bg-transparent border-none outline-none text-white placeholder-blue-200 w-full max-w-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Categories */}
|
||||
<section className="py-16 px-8">
|
||||
<h2 className="text-3xl font-bold text-center mb-12">Категории мастеров</h2>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-6 max-w-5xl mx-auto">
|
||||
{[
|
||||
{ icon: "🔧", name: "Сантехника" },
|
||||
{ icon: "⚡", name: "Электрика" },
|
||||
{ icon: "🏠", name: "Ремонт" },
|
||||
{ icon: "🎨", name: "Дизайн" },
|
||||
{ icon: "🌿", name: "Ландшафт" },
|
||||
{ icon: "🔑", name: "Замок." },
|
||||
].map((cat) => (
|
||||
<div key={cat.name} className="bg-white rounded-xl p-6 shadow-sm hover:shadow-md transition cursor-pointer text-center">
|
||||
<span className="text-4xl">{cat.icon}</span>
|
||||
<p className="mt-3 font-medium">{cat.name}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* AI Diagnosis — Coming Soon */}
|
||||
<section className="py-16 px-8 bg-indigo-50">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<ShieldCheck size={48} className="mx-auto mb-4 text-indigo-600" />
|
||||
<h2 className="text-3xl font-bold mb-2">AI Диагностика</h2>
|
||||
<p className="text-lg opacity-75 mb-6">Умная диагностика проблемы — скоро!</p>
|
||||
|
||||
<div className="inline-block bg-white rounded-xl p-8 shadow-sm border border-indigo-100 max-w-md">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Clock size={20} className="text-indigo-500" />
|
||||
<span className="font-semibold text-indigo-700">Coming Soon</span>
|
||||
</div>
|
||||
<p className="text-sm opacity-60">
|
||||
AI задаст вопросы о вашей проблеме и поможет мастеру точнее оценить стоимость работ.
|
||||
Функция в разработке — следите за обновлениями!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* How it works */}
|
||||
<section className="py-16 px-8">
|
||||
<h2 className="text-3xl font-bold text-center mb-12">Как это работает</h2>
|
||||
<div className="grid md:grid-cols-4 gap-8 max-w-5xl mx-auto">
|
||||
{[
|
||||
{ step: "1", title: "Опишите задачу", desc: "Расскажите что нужно сделать" },
|
||||
{ step: "2", title: "Найдите мастера", desc: "Сравните отзывы и цены" },
|
||||
{ step: "3", title: "Обсудите детали", desc: "Чат с мастером в приложении" },
|
||||
{ step: "4", title: "Оплатите безопасно", desc: "Escrow-гарант сделки" },
|
||||
].map((item) => (
|
||||
<div key={item.step} className="text-center">
|
||||
<div className="w-12 h-12 rounded-full bg-blue-600 text-white flex items-center justify-center mx-auto mb-4 font-bold">{item.step}</div>
|
||||
<h3 className="font-semibold mb-2">{item.title}</h3>
|
||||
<p className="text-sm opacity-75">{item.desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="py-8 text-center text-sm opacity-60">
|
||||
© 2026 LocalPro Finder. Все права защищены.
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useState } from "react";
|
||||
import { Mail, Lock, Eye, EyeOff } from "lucide-react";
|
||||
|
||||
export default function Login() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-blue-50 to-white flex items-center justify-center px-4">
|
||||
<div className="bg-white rounded-xl shadow-lg p-8 w-full max-w-md">
|
||||
<h1 className="text-2xl font-bold text-center mb-6">Вход в LocalPro Finder</h1>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
placeholder="your@email.com"
|
||||
className="w-full px-4 py-3 rounded-lg border focus:outline-none focus:ring-2 focus:ring-blue-300"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Пароль</label>
|
||||
<input
|
||||
type={showPassword ? "text" : "password"}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
className="w-full px-4 py-3 rounded-lg border focus:outline-none focus:ring-2 focus:ring-blue-300"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button className="w-full py-3 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition">
|
||||
Войти
|
||||
</button>
|
||||
|
||||
<p className="text-center text-sm opacity-60">
|
||||
Нет аккаунта?{" "}
|
||||
<a href="/register" className="text-blue-600 hover:underline">Зарегистрироваться</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useState } from "react";
|
||||
import { Mail, Lock, User, Phone } from "lucide-react";
|
||||
|
||||
export default function Register() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [firstName, setFirstName] = useState("");
|
||||
const [lastName, setLastName] = useState("");
|
||||
const [phone, setPhone] = useState("");
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-blue-50 to-white flex items-center justify-center px-4 py-12">
|
||||
<div className="bg-white rounded-xl shadow-lg p-8 w-full max-w-md">
|
||||
<h1 className="text-2xl font-bold text-center mb-6">Регистрация</h1>
|
||||
|
||||
<div className="space-y-4">
|
||||
<input value={firstName} onChange={(e) => setFirstName(e.target.value)} placeholder="Имя" className="w-full px-4 py-3 rounded-lg border focus:outline-none focus:ring-2 focus:ring-blue-300" />
|
||||
<input value={lastName} onChange={(e) => setLastName(e.target.value)} placeholder="Фамилия" className="w-full px-4 py-3 rounded-lg border focus:outline-none focus:ring-2 focus:ring-blue-300" />
|
||||
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Email" className="w-full px-4 py-3 rounded-lg border focus:outline-none focus:ring-2 focus:ring-blue-300" />
|
||||
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Пароль" className="w-full px-4 py-3 rounded-lg border focus:outline-none focus:ring-2 focus:ring-blue-300" />
|
||||
<input type="tel" value={phone} onChange={(e) => setPhone(e.target.value)} placeholder="Телефон (необязательно)" className="w-full px-4 py-3 rounded-lg border focus:outline-none focus:ring-2 focus:ring-blue-300" />
|
||||
|
||||
<button className="w-full py-3 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition">
|
||||
Создать аккаунт
|
||||
</button>
|
||||
|
||||
<p className="text-center text-sm opacity-60">
|
||||
Уже есть аккаунт?{" "}
|
||||
<a href="/login" className="text-blue-600 hover:underline">Войти</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user