first commit

This commit is contained in:
Samandar Turgunboyev
2025-10-18 17:14:59 +05:00
parent edf364b389
commit 036a36ce90
92 changed files with 14614 additions and 135 deletions

View File

@@ -0,0 +1,316 @@
"use client";
import { Button } from "@/shared/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/shared/ui/card";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/shared/ui/dialog";
import { Input } from "@/shared/ui/input";
import { Label } from "@/shared/ui/label";
import { Map, Placemark, YMaps } from "@pbe/react-yandex-maps";
import { Edit, Plus, Trash } from "lucide-react";
import { useEffect, useState } from "react";
type MapClickEvent = {
get: (key: "coords") => [number, number];
};
type ContactInfo = {
telegram?: string;
instagram?: string;
facebook?: string;
twiter?: string;
linkedin?: string;
address?: string;
email?: string;
phonePrimary?: string;
phoneSecondary?: string;
};
const STORAGE_KEY = "site_contact_info";
async function getAddressFromCoords(lat: number, lon: number) {
const response = await fetch(
`https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=json`,
);
const data = await response.json();
return data.display_name || "";
}
export default function ContactSettings() {
const [contact, setContact] = useState<ContactInfo | null>(null);
const [open, setOpen] = useState(false);
const [editing, setEditing] = useState(false);
const [form, setForm] = useState<ContactInfo>({});
const [coords, setCoords] = useState({
latitude: 41.311081,
longitude: 69.240562,
}); // Toshkent default
// Load saved contact
useEffect(() => {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) setContact(JSON.parse(raw));
}, []);
// Populate form when editing
useEffect(() => {
if (open && editing && contact) setForm(contact);
if (!open && !editing) setForm({});
}, [open, editing, contact]);
const handleChange = <K extends keyof ContactInfo>(
key: K,
value: ContactInfo[K],
) => {
setForm((s) => ({ ...s, [key]: value }));
};
const handleMapClick = async (e: MapClickEvent) => {
const lat = e.get("coords")[0];
const lon = e.get("coords")[1];
setCoords({ latitude: lat, longitude: lon });
const addressName = await getAddressFromCoords(lat, lon);
setForm((s) => ({ ...s, address: addressName }));
};
const saveContact = () => {
if (!form.email && !form.phonePrimary) {
alert("Iltimos email yoki telefon kiriting");
return;
}
localStorage.setItem(STORAGE_KEY, JSON.stringify(form));
setContact(form);
setOpen(false);
setEditing(false);
};
const startAdd = () => {
setForm({});
setEditing(false);
setOpen(true);
};
const startEdit = () => {
setEditing(true);
setOpen(true);
};
const removeContact = () => {
if (!confirm("Contact ma'lumotlarini o'chirishni xohlaysizmi?")) return;
localStorage.removeItem(STORAGE_KEY);
setContact(null);
};
return (
<div className="w-full mx-auto p-4">
<div className="flex items-center justify-between mb-6">
<h2 className="text-2xl font-semibold text-gray-100">
Contact settings
</h2>
{!contact && (
<Button onClick={startAdd} className="flex items-center gap-2">
<Plus size={16} /> Qo'shish
</Button>
)}
</div>
{!contact ? (
<Card className="bg-gray-900">
<CardHeader>
<CardTitle>Hozircha kontakt ma'lumotlari qo'shilmagan</CardTitle>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
Sayt uchun telegram, instagram, manzil, email va telefonni bu
yerda saqlang. Siz faqat bir marta qo'sha olasiz keyin
tahrirlash mumkin.
</p>
<div className="mt-4">
<Button onClick={startAdd} className="flex items-center gap-2">
<Plus size={14} /> Qo'shish
</Button>
</div>
</CardContent>
</Card>
) : (
<Card className="bg-gray-900">
<CardHeader className="flex items-center justify-between">
<CardTitle>Kontakt ma'lumotlari</CardTitle>
<div className="flex gap-2">
<Button
variant="outline"
onClick={startEdit}
className="flex items-center gap-2"
>
<Edit size={14} /> Tahrirlash
</Button>
<Button
variant="destructive"
onClick={removeContact}
className="flex items-center gap-2"
>
<Trash size={14} /> O'chirish
</Button>
</div>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<div className="text-sm text-muted-foreground">Telegram</div>
<div className="text-sm">{contact.telegram || "—"}</div>
</div>
<div>
<div className="text-xs text-muted-foreground">Instagram</div>
<div className="text-sm">{contact.instagram || "—"}</div>
</div>
<div>
<div className="text-xs text-muted-foreground">Facebook</div>
<div className="text-sm">{contact.facebook || "—"}</div>
</div>
<div>
<div className="text-xs text-muted-foreground">LinkedIn</div>
<div className="text-sm">{contact.linkedin || "—"}</div>
</div>
<div>
<div className="text-xs text-muted-foreground">Twitter</div>
<div className="text-sm">{contact.twiter || "—"}</div>
</div>
<div>
<div className="text-xs text-muted-foreground">Manzil</div>
<div className="text-sm">{contact.address || "—"}</div>
</div>
<div>
<div className="text-xs text-muted-foreground">Email</div>
<div className="text-sm">{contact.email || "—"}</div>
</div>
<div>
<div className="text-xs text-muted-foreground">Telefonlar</div>
<div className="text-sm">
{contact.phonePrimary || "—"}
{contact.phoneSecondary ? ` • ${contact.phoneSecondary}` : ""}
</div>
</div>
</div>
</CardContent>
</Card>
)}
{/* Dialog */}
<Dialog
open={open}
onOpenChange={(v) => {
setOpen(v);
if (!v) setEditing(false);
}}
>
<DialogContent className="h-[90%] overflow-y-scroll">
<DialogHeader>
<DialogTitle>
{editing ? "Kontaktni tahrirlash" : "Kontakt qo'shish"}
</DialogTitle>
</DialogHeader>
{/* Map */}
<YMaps query={{ lang: "en_RU" }}>
<Map
defaultState={{
center: [coords.latitude, coords.longitude],
zoom: 13,
}}
width="100%"
height="400px"
onClick={handleMapClick}
>
<Placemark geometry={[coords.latitude, coords.longitude]} />
</Map>
</YMaps>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6 mt-4">
<div className="flex flex-col gap-2 sm:col-span-2">
<Label>Manzil</Label>
<Input
value={form.address || ""}
onChange={(e) => handleChange("address", e.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label>Telegram</Label>
<Input
value={form.telegram || ""}
onChange={(e) => handleChange("telegram", e.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label>Instagram</Label>
<Input
value={form.instagram || ""}
onChange={(e) => handleChange("instagram", e.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label>Linkedin</Label>
<Input
value={form.linkedin || ""}
onChange={(e) => handleChange("linkedin", e.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label>Facebook</Label>
<Input
value={form.facebook || ""}
onChange={(e) => handleChange("facebook", e.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label>Twitter</Label>
<Input
value={form.twiter || ""}
onChange={(e) => handleChange("twiter", e.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label>Email</Label>
<Input
value={form.email || ""}
onChange={(e) => handleChange("email", e.target.value)}
/>
</div>
<div className="flex flex-col gap-2">
<Label>Asosiy telefon</Label>
<Input
value={form.phonePrimary || ""}
onChange={(e) => handleChange("phonePrimary", e.target.value)}
/>
</div>
<div className="flex flex-col gap-2 w-full">
<Label>Qo'shimcha telefon</Label>
<Input
value={form.phoneSecondary || ""}
onChange={(e) => handleChange("phoneSecondary", e.target.value)}
/>
</div>
</div>
<DialogFooter className="mt-6 flex justify-end gap-2">
<Button
variant="ghost"
onClick={() => {
setOpen(false);
setEditing(false);
}}
>
Bekor qilish
</Button>
<Button onClick={saveContact}>Saqlash</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}