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