updated compoennt file structure
This commit is contained in:
81
components/pages/about/aboutDetail/baza.tsx
Normal file
81
components/pages/about/aboutDetail/baza.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { ShieldCheck } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { NormativeCard } from "./normativeCard";
|
||||
|
||||
const fadeUp = (delay = 0) => ({
|
||||
initial: { opacity: 0, y: 36 },
|
||||
animate: { opacity: 1, y: 0 },
|
||||
transition: { duration: 0.65, ease: [0.22, 1, 0.36, 1] as any, delay },
|
||||
});
|
||||
|
||||
const fadeUpView = (delay = 0) => ({
|
||||
initial: { opacity: 0, y: 48 },
|
||||
whileInView: { opacity: 1, y: 0 },
|
||||
transition: { duration: 0.65, ease: [0.22, 1, 0.36, 1] as any, delay },
|
||||
viewport: { once: true },
|
||||
});
|
||||
|
||||
export default function NormativBazaPage() {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<div className="bg-[#0f0e0d] text-white min-h-screen pt-10 pb-20">
|
||||
{/* ── Hero ── */}
|
||||
<section className="relative w-full px-2">
|
||||
{/* Content */}
|
||||
<div className="relative z-10 flex flex-col justify-end h-full max-w-6xl mx-auto">
|
||||
<motion.span
|
||||
{...fadeUp(0)}
|
||||
className="text-xs font-black uppercase tracking-[0.22em] text-red-600 mb-4"
|
||||
>
|
||||
{t("about.normativBaza.hero.label")}
|
||||
</motion.span>
|
||||
|
||||
<motion.h1
|
||||
{...fadeUp(0.1)}
|
||||
className="text-4xl md:text-5xl lg:text-7xl font-black uppercase leading-[0.95] tracking-tight text-white"
|
||||
>
|
||||
{t("about.normativBaza.hero.title1")}
|
||||
<br />
|
||||
<span className="text-red-700">
|
||||
{t("about.normativBaza.hero.title2")}
|
||||
</span>
|
||||
</motion.h1>
|
||||
|
||||
<motion.p
|
||||
{...fadeUp(0.22)}
|
||||
className="mt-6 max-w-xl text-sm md:text-base text-gray-300 leading-relaxed"
|
||||
>
|
||||
{t("about.normativBaza.hero.description")}
|
||||
</motion.p>
|
||||
|
||||
{/* Decorative line */}
|
||||
<motion.div
|
||||
initial={{ scaleX: 0, originX: 0 }}
|
||||
animate={{ scaleX: 1 }}
|
||||
transition={{ delay: 0.4, duration: 0.8, ease: [0.22, 1, 0.36, 1] }}
|
||||
className="mt-10 w-24 h-px bg-red-700"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<NormativeCard />
|
||||
|
||||
{/* ── Bottom quote band ── */}
|
||||
<motion.section
|
||||
{...fadeUpView(0)}
|
||||
className="border-t border-white/5 max-w-6xl mx-auto px-6 py-10 flex flex-col md:flex-row items-start md:items-center gap-6 justify-between"
|
||||
>
|
||||
<p className="text-xl md:text-2xl font-bold text-white/80 max-w-lg leading-snug">
|
||||
{t("about.normativBaza.bottomText")}
|
||||
</p>
|
||||
<div className="shrink-0 w-16 h-16 rounded-2xl bg-red-400/10 border border-red-400/20 flex items-center justify-center">
|
||||
<ShieldCheck size={28} className="text-red-600" />
|
||||
</div>
|
||||
</motion.section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
49
components/pages/about/aboutDetail/card.tsx
Normal file
49
components/pages/about/aboutDetail/card.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import { Download } from "lucide-react";
|
||||
|
||||
interface DownloadCardProps {
|
||||
title: string;
|
||||
fileType?: string;
|
||||
fileSize: string;
|
||||
fileUrl: string;
|
||||
}
|
||||
|
||||
export default function DownloadCard({
|
||||
title,
|
||||
fileType = "PDF",
|
||||
fileSize,
|
||||
fileUrl,
|
||||
}: DownloadCardProps) {
|
||||
return (
|
||||
<a
|
||||
href={fileUrl}
|
||||
download
|
||||
className="min-h-40 h-full group relative w-full max-w-md border border-white/10 bg-[#171616b8] transition rounded-lg p-4 flex flex-col gap-4 items-start justify-between"
|
||||
>
|
||||
{/* Background glow effect */}
|
||||
<div className="absolute inset-0 bg-linear-to-t from-red-600/20 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
|
||||
|
||||
{/* Decorative corner accent */}
|
||||
<div className="absolute top-0 right-0 w-24 h-24 bg-linear-to-br from-red-500/20 to-transparent rounded-bl-full opacity-0 group-hover:opacity-00 transition-opacity duration-500" />
|
||||
{/* Top section */}
|
||||
<div className="flex justify-between items-start">
|
||||
<h3 className="text-xl font-unbounded font-bold group-hover:text-red-500 text-white leading-tight transition-colors duration-300">
|
||||
{title}
|
||||
</h3>
|
||||
|
||||
<span className="text-sm font-medium text-white">{fileType}</span>
|
||||
</div>
|
||||
|
||||
{/* Bottom section */}
|
||||
<div className="flex w-full justify-between items-center">
|
||||
<span className="text-sm text-gray-200">{fileSize}</span>
|
||||
|
||||
<Download
|
||||
size={20}
|
||||
className="text-gray-600 transition group-hover:text-red-700 duration-300"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
73
components/pages/about/aboutDetail/guides.tsx
Normal file
73
components/pages/about/aboutDetail/guides.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
"use client";
|
||||
import { useTranslations } from "next-intl";
|
||||
import DownloadCard from "./card";
|
||||
import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import httpClient from "@/request/api";
|
||||
import { endPoints } from "@/request/links";
|
||||
import PaginationLite from "@/components/paginationUI";
|
||||
import { DownloadCardSkeleton } from "./loading/guidLoading";
|
||||
|
||||
export function Guides() {
|
||||
const t = useTranslations();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const guides = [
|
||||
{
|
||||
file: "/varnix.pdf",
|
||||
name: t("about.notePPPage.varnix"),
|
||||
file_type: "PDF",
|
||||
file_size: "368.51 KB",
|
||||
},
|
||||
{
|
||||
file: "/ppFlanes.pdf",
|
||||
name: t("about.notePPPage.ppFlanes"),
|
||||
file_type: "PDF",
|
||||
file_size: "368.51 KB",
|
||||
},
|
||||
{
|
||||
file: "/ppFiting.pdf",
|
||||
name: t("about.notePPPage.ppFiting"),
|
||||
file_type: "PDF",
|
||||
file_size: "368.51 KB",
|
||||
},
|
||||
];
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ["guides"],
|
||||
queryFn: () => httpClient(endPoints.guides),
|
||||
select: (res) => ({
|
||||
results: res.data?.data?.results,
|
||||
current_page: res.data?.data?.current_page,
|
||||
total_pages: res.data?.data?.total_pages,
|
||||
}),
|
||||
});
|
||||
|
||||
const guidedata = data?.results ?? guides;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="grid lg:grid-cols-3 min-[580px]:grid-cols-2 grid-cols-1 gap-4 max-w-7xl mx-auto py-5">
|
||||
{isLoading ? (
|
||||
<DownloadCardSkeleton />
|
||||
) : (
|
||||
guidedata.map((guide: any, index: number) => (
|
||||
<DownloadCard
|
||||
key={index}
|
||||
title={guide.name}
|
||||
fileType={guide.file_type}
|
||||
fileSize={guide.file_size}
|
||||
fileUrl={guide.file}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
{data?.total_pages > 1 && (
|
||||
<PaginationLite
|
||||
currentPage={currentPage}
|
||||
totalPages={data?.total_pages}
|
||||
onChange={setCurrentPage}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
24
components/pages/about/aboutDetail/loading/guidLoading.tsx
Normal file
24
components/pages/about/aboutDetail/loading/guidLoading.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
export function DownloadCardSkeleton() {
|
||||
return (
|
||||
<div
|
||||
className="min-h-40 h-full relative w-full max-w-md border border-white/10 bg-[#171616b8] rounded-lg p-4 flex flex-col gap-4 items-start justify-between"
|
||||
>
|
||||
{/* Top section */}
|
||||
<div className="flex justify-between items-start w-full gap-4">
|
||||
{/* Title */}
|
||||
<div className="space-y-2 flex-1">
|
||||
<div className="h-4 w-3/4 rounded bg-white/8 animate-pulse" />
|
||||
<div className="h-4 w-1/2 rounded bg-white/8 animate-pulse" />
|
||||
</div>
|
||||
{/* File type badge */}
|
||||
<div className="h-4 w-10 rounded bg-white/8 animate-pulse shrink-0" />
|
||||
</div>
|
||||
|
||||
{/* Bottom section */}
|
||||
<div className="flex w-full justify-between items-center">
|
||||
<div className="h-3 w-16 rounded bg-white/8 animate-pulse" />
|
||||
<div className="w-5 h-5 rounded bg-white/8 animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
44
components/pages/about/aboutDetail/loading/loading.tsx
Normal file
44
components/pages/about/aboutDetail/loading/loading.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
export function CertCardSkeleton({ count = 6 }: { count?: number }) {
|
||||
return (
|
||||
<>
|
||||
<article className="flex flex-col rounded-2xl overflow-hidden sm:p-5 p-2 bg-[#161514] border border-white/5 w-full">
|
||||
{/* Badge row */}
|
||||
<div className="flex flex-col justify-between flex-1 min-w-0 py-1 gap-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{/* Award badge */}
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="w-2.5 h-2.5 rounded-sm bg-red-900/40 animate-pulse" />
|
||||
<div className="h-2.5 w-20 rounded-full bg-red-900/40 animate-pulse" />
|
||||
</div>
|
||||
{/* Category pill */}
|
||||
<div className="h-4 w-16 rounded-full border border-white/10 bg-white/5 animate-pulse" />
|
||||
</div>
|
||||
|
||||
{/* Title lines */}
|
||||
<div className="space-y-1.5 pt-1">
|
||||
<div className="h-3.5 w-full rounded bg-white/8 animate-pulse" />
|
||||
<div className="h-3.5 w-3/4 rounded bg-white/8 animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="mx-4 h-px bg-white/5 sm:my-5 my-2" />
|
||||
|
||||
{/* Document list */}
|
||||
<ul className="flex flex-col gap-2.5">
|
||||
{Array.from({ length: 3 }).map((_, di) => (
|
||||
<li key={di} className="flex items-start gap-2.5">
|
||||
<span className="mt-1 flex-none w-1.5 h-1.5 rounded-full bg-red-900/40 animate-pulse" />
|
||||
<div
|
||||
className="h-3 rounded bg-white/8 animate-pulse"
|
||||
style={{ width: `${75 - di * 10}%` }}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
</>
|
||||
);
|
||||
}
|
||||
95
components/pages/about/aboutDetail/normativeCard.tsx
Normal file
95
components/pages/about/aboutDetail/normativeCard.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Award } from "lucide-react";
|
||||
import { normativeData } from "@/lib/demoData";
|
||||
import httpClient from "@/request/api";
|
||||
import { endPoints } from "@/request/links";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useState } from "react";
|
||||
import PaginationLite from "@/components/paginationUI";
|
||||
import { CertCardSkeleton } from "./loading/loading";
|
||||
|
||||
export function NormativeCard() {
|
||||
const t = useTranslations();
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ["normativeData"],
|
||||
queryFn: () => httpClient(endPoints.normative),
|
||||
select: (res) => ({
|
||||
results: res.data?.data?.results,
|
||||
current_page: res.data?.data?.current_page,
|
||||
total_pages: res.data?.data?.total_pages,
|
||||
}),
|
||||
});
|
||||
|
||||
const generallyData = data?.results || normativeData;
|
||||
|
||||
if (isLoading) return <CertCardSkeleton />;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col gap-8 py-10 max-w-6xl mx-auto px-2">
|
||||
{generallyData.map((c: any, i: number) => (
|
||||
<motion.article
|
||||
key={i}
|
||||
initial={{ opacity: 0, y: 28 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.55, delay: i * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
className="group flex flex-col rounded-2xl overflow-hidden sm:p-5 p-2 bg-[#161514] border border-white/5 hover:border-red-600/20 transition-colors duration-300 w-full"
|
||||
>
|
||||
{/* Meta + actions */}
|
||||
<div className="flex flex-col justify-between flex-1 min-w-0 py-1 gap-4">
|
||||
<div className="space-y-2">
|
||||
{/* Badge row */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Award size={11} className="text-red-600 shrink-0" />
|
||||
<span className="text-[10px] font-black uppercase tracking-widest text-red-600">
|
||||
{t("about.certificatePage.card.badge")}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-[10px] font-bold uppercase tracking-wider text-white/20 border border-white/10 px-2 py-0.5 rounded-full">
|
||||
{c.artikul}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="font-bold text-sm md:text-base text-white leading-snug">
|
||||
{t(c.title)}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<div className="mx-4 h-px bg-white/5 sm:my-5 my-2" />
|
||||
|
||||
{/* Documents list */}
|
||||
<div className="overflow-hidden">
|
||||
<ul className="flex flex-col gap-2.5">
|
||||
{c.features.map((doc: any, di: number) => (
|
||||
<li key={di} className="flex items-start gap-2.5">
|
||||
<span className="mt-1 flex-none w-1.5 h-1.5 rounded-full bg-red-600/60" />
|
||||
<p className="text-xs text-gray-400 leading-relaxed">
|
||||
{t(doc?.name)}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</motion.article>
|
||||
))}
|
||||
</div>
|
||||
{data?.total_pages > 1 && (
|
||||
<PaginationLite
|
||||
currentPage={currentPage}
|
||||
totalPages={data?.total_pages}
|
||||
onChange={setCurrentPage}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
64
components/pages/about/aboutDetail/sertificateCard.tsx
Normal file
64
components/pages/about/aboutDetail/sertificateCard.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
"use client";
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { certs } from "@/lib/demoData";
|
||||
import { Award } from "lucide-react";
|
||||
|
||||
export function CertCard({ c, i }: { c: (typeof certs)[0]; i: number }) {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<motion.article
|
||||
initial={{ opacity: 0, y: 28 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.55, delay: i * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
className="group flex flex-col rounded-2xl overflow-hidden sm:p-5 p-2 bg-[#161514] border border-white/5 hover:border-red-600/20 transition-colors duration-300 w-full"
|
||||
>
|
||||
{/* Right: meta + actions for gitea */}
|
||||
<div className="flex flex-col justify-between flex-1 min-w-0 py-1 gap-4">
|
||||
<div className="space-y-2">
|
||||
{/* Badge row */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Award size={11} className="text-red-600 shrink-0" />
|
||||
<span className="text-[10px] font-black uppercase tracking-widest text-red-600">
|
||||
{t("about.certificatePage.card.badge")}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-[10px] font-bold uppercase tracking-wider text-white/20 border border-white/10 px-2 py-0.5 rounded-full">
|
||||
{c.artikul}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="font-bold text-sm md:text-base text-white leading-snug">
|
||||
{c.title}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Divider ── */}
|
||||
<div className="mx-4 h-px bg-white/5 sm:my-5 my-2" />
|
||||
|
||||
{/* Collapsible document list */}
|
||||
<div className="overflow-hidden">
|
||||
<ul className="flex flex-col gap-2.5">
|
||||
{c.features.length > 0 &&
|
||||
c.features.map((doc: any, di: number) => {
|
||||
const { name } = doc;
|
||||
return (
|
||||
<li key={di} className="flex items-start gap-2.5">
|
||||
<span className="mt-1 flex-none w-1.5 h-1.5 rounded-full bg-red-600/60" />
|
||||
<p className="text-xs text-gray-400 leading-relaxed">
|
||||
{name || ""}
|
||||
</p>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</motion.article>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user