Compare commits
2 Commits
2f10fb70a9
...
3dfce04ab0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3dfce04ab0 | ||
|
|
ca80c6920c |
@@ -12,6 +12,7 @@ export function ContactForm() {
|
||||
const { t } = useLanguage();
|
||||
|
||||
const productName = useProductStore((state) => state.productName);
|
||||
const reset = useProductStore((state) => state.resetProductName);
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
@@ -26,15 +27,29 @@ export function ContactForm() {
|
||||
text: string;
|
||||
} | null>(null);
|
||||
|
||||
const phoneRegex = /^\+?\d*$/; // + majburiy
|
||||
|
||||
const handleChange = (
|
||||
e: React.ChangeEvent<
|
||||
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
||||
>
|
||||
) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[e.target.name]: e.target.value,
|
||||
}));
|
||||
const { name, value } = e.target;
|
||||
|
||||
if (name === "phone") {
|
||||
// regexga mos kelmasa — state o‘zgarmaydi
|
||||
if (!phoneRegex.test(value)) return;
|
||||
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
phone: value,
|
||||
}));
|
||||
} else {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
@@ -62,6 +77,8 @@ ${formData.message || "—"}
|
||||
text: text,
|
||||
});
|
||||
setMessage({ type: "success", text: t.contact.success });
|
||||
reset();
|
||||
setFormData({ name: "", phone: "", message: "", productName: "" });
|
||||
} catch {
|
||||
setMessage({ type: "error", text: t.contact.error });
|
||||
} finally {
|
||||
@@ -168,7 +185,7 @@ ${formData.message || "—"}
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
placeholder={t.contact.namePlaceholder}
|
||||
className="w-full px-4 py-2 border border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
className="w-full text-black px-4 py-2 border border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -184,7 +201,7 @@ ${formData.message || "—"}
|
||||
onChange={handleChange}
|
||||
placeholder={t.contact.phonePlaceholder}
|
||||
required
|
||||
className="w-full px-4 py-2 border border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
className="w-full text-black px-4 py-2 border border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -199,7 +216,7 @@ ${formData.message || "—"}
|
||||
onChange={handleChange}
|
||||
placeholder={t.contact.messagePlaceholder}
|
||||
rows={4}
|
||||
className="w-full px-4 py-2 border border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
|
||||
className="w-full text-black px-4 py-2 border border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -214,7 +231,7 @@ ${formData.message || "—"}
|
||||
value={productName ? productName : formData.productName}
|
||||
onChange={handleChange}
|
||||
placeholder={t.contact.product}
|
||||
className="w-full px-4 py-2 border border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
className="w-full text-black px-4 py-2 border border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ export function ProductCard({ product, onViewDetails }: ProductCardProps) {
|
||||
className="w-full h-full"
|
||||
>
|
||||
<img
|
||||
src={`https://api.serenmebel.uz${product.image}`}
|
||||
src={`https://admin.promtechno.uz${product.image}`}
|
||||
alt={languageIndex?product.name_uz:product.name_ru}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
|
||||
@@ -61,7 +61,7 @@ export function ProductModal({ product, onClose }: ProductModalProps) {
|
||||
{/* Image */}
|
||||
<div className="relative max-sm:w-full max-md:h-50">
|
||||
<Image
|
||||
src={`https://api.serenmebel.uz${product.image}`}
|
||||
src={`https://admin.promtechno.uz${product.image}`}
|
||||
alt="image"
|
||||
fill
|
||||
className="object-contain max-md:h-50"
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ChevronsRight } from "lucide-react";
|
||||
import { ProductCard } from "./ProductCard";
|
||||
import { ProductModal } from "./ProductModal";
|
||||
import axios from "axios";
|
||||
import EmptyState from "../productsPage/emptyData";
|
||||
|
||||
// hello everyone
|
||||
|
||||
@@ -20,11 +21,13 @@ export function ProductsGrid() {
|
||||
|
||||
useEffect(() => {
|
||||
async function getData() {
|
||||
await axios.get("https://admin.promtechno.uz/api/products/").then((res) => {
|
||||
console.log("all data main page: ", res?.data);
|
||||
const allData = res?.data || [];
|
||||
setAllProducts(allData.slice(0,3));
|
||||
});
|
||||
await axios
|
||||
.get("https://admin.promtechno.uz/api/products/")
|
||||
.then((res) => {
|
||||
console.log("all data main page: ", res?.data);
|
||||
const allData = res?.data || [];
|
||||
setAllProducts(allData.slice(0, 3));
|
||||
});
|
||||
}
|
||||
getData();
|
||||
}, []);
|
||||
@@ -64,21 +67,25 @@ export function ProductsGrid() {
|
||||
</motion.div>
|
||||
|
||||
{/* Product Grid */}
|
||||
<motion.div
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"
|
||||
>
|
||||
{allProducts.map((product: any) => (
|
||||
<motion.div key={product.id} >
|
||||
<ProductCard
|
||||
product={product}
|
||||
onViewDetails={handleViewDetails}
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
{allProducts && allProducts.length > 0 ? (
|
||||
<motion.div
|
||||
initial="hidden"
|
||||
whileInView="visible"
|
||||
viewport={{ once: true }}
|
||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"
|
||||
>
|
||||
{allProducts.map((product: any) => (
|
||||
<motion.div key={product.id}>
|
||||
<ProductCard
|
||||
product={product}
|
||||
onViewDetails={handleViewDetails}
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
) : (
|
||||
<EmptyState page="main" />
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-10 w-full flex items-center justify-center">
|
||||
<Link
|
||||
@@ -90,7 +97,7 @@ export function ProductsGrid() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Product Modal */}
|
||||
{/* Product Modalll */}
|
||||
{selectedProduct && (
|
||||
<ProductModal
|
||||
product={selectedProduct}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useLanguage } from "@/context/language-context";
|
||||
|
||||
//salomalr
|
||||
|
||||
export default function EmptyState() {
|
||||
export default function EmptyState({page}:{page: string}) {
|
||||
const { t } = useLanguage();
|
||||
|
||||
const container = {
|
||||
@@ -23,7 +23,7 @@ export default function EmptyState() {
|
||||
animate="show"
|
||||
variants={container}
|
||||
>
|
||||
<div className="max-w-5xl w-full bg-white/60 bg-linear-to-br from-primary to-gray-800 backdrop-blur-md rounded-2xl shadow-lg overflow-hidden">
|
||||
<div className="max-w-5xl w-full bg-linear-to-br from-primary via-primary/50 to-gray-300 backdrop-blur-md rounded-2xl shadow-lg overflow-hidden">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 items-center p-8 md:p-12">
|
||||
{/* Illustration / Icon */}
|
||||
<motion.div className="flex items-center justify-center">
|
||||
@@ -54,14 +54,16 @@ export default function EmptyState() {
|
||||
{t.empty_data.description}
|
||||
</p>
|
||||
|
||||
<div className="mt-6 flex flex-col sm:flex-row items-center sm:items-start gap-3 md:gap-4 justify-center md:justify-start">
|
||||
<motion.div
|
||||
className="inline-flex items-center justify-center px-5 py-2.5 bg-primary/70 hover:bg-primary text-white rounded-lg shadow-md transition-colors text-sm font-medium"
|
||||
// @ts-ignore allow Link-like anchor
|
||||
>
|
||||
<Link href="/">{t.empty_data.back || "Bosh sahifa"}</Link>
|
||||
</motion.div>
|
||||
</div>
|
||||
{page !== "main" && (
|
||||
<div className="mt-6 flex flex-col sm:flex-row items-center sm:items-start gap-3 md:gap-4 justify-center md:justify-start">
|
||||
<motion.div
|
||||
className="inline-flex items-center justify-center px-5 py-2.5 bg-primary/70 hover:bg-primary text-white rounded-lg shadow-md transition-colors text-sm font-medium"
|
||||
// @ts-ignore allow Link-like anchor
|
||||
>
|
||||
<Link href="/">{t.empty_data.back || "Bosh sahifa"}</Link>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function Products() {
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<EmptyState />
|
||||
<EmptyState page="products" />
|
||||
)}
|
||||
{/* Product Modal */}
|
||||
{selectedProduct && (
|
||||
|
||||
Reference in New Issue
Block a user