feat: add featured flag to project, display featured projects on home page

This commit is contained in:
mstfyldz
2026-06-05 21:46:54 +03:00
parent 1f1bb8d913
commit 59b2afbc5a
6 changed files with 43 additions and 7 deletions
+3 -1
View File
@@ -1,6 +1,7 @@
import { getDictionary } from "@/get-dictionary"; import { getDictionary } from "@/get-dictionary";
import type { Locale } from "@/i18n-config"; import type { Locale } from "@/i18n-config";
import HomeClient from "@/components/HomeClient"; import HomeClient from "@/components/HomeClient";
import { getFeaturedProjects } from "../actions";
export async function generateMetadata({ params }: { params: Promise<{ lang: Locale }> }) { export async function generateMetadata({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params; const { lang } = await params;
@@ -17,6 +18,7 @@ export async function generateMetadata({ params }: { params: Promise<{ lang: Loc
export default async function Page({ params }: { params: Promise<{ lang: Locale }> }) { export default async function Page({ params }: { params: Promise<{ lang: Locale }> }) {
const { lang } = await params; const { lang } = await params;
const dict = await getDictionary(lang); const dict = await getDictionary(lang);
const featuredProjects = await getFeaturedProjects();
return <HomeClient lang={lang} dict={dict} />; return <HomeClient lang={lang} dict={dict} featuredProjects={featuredProjects} />;
} }
+16 -1
View File
@@ -19,9 +19,22 @@ export async function getProjects() {
} }
} }
export async function getFeaturedProjects() {
try {
return await prisma.project.findMany({
where: { featured: true },
orderBy: { num: "asc" },
take: 4,
});
} catch (error) {
console.error("Error fetching featured projects:", error);
return [];
}
}
export async function saveProject(data: any) { export async function saveProject(data: any) {
try { try {
const { id, num, slug, title, tag, desc, spec, year, client, duration, tech, challenge, solution, results, image, gallery, website } = data; const { id, num, slug, title, tag, desc, spec, year, client, duration, tech, challenge, solution, results, image, gallery, website, featured } = data;
let project; let project;
if (id) { if (id) {
@@ -45,6 +58,7 @@ export async function saveProject(data: any) {
image: image || "", image: image || "",
gallery: gallery || [], gallery: gallery || [],
website: website || "", website: website || "",
featured: featured || false,
}, },
}); });
} else { } else {
@@ -73,6 +87,7 @@ export async function saveProject(data: any) {
image: image || "", image: image || "",
gallery: gallery || [], gallery: gallery || [],
website: website || "", website: website || "",
featured: featured || false,
}, },
}); });
} }
+17
View File
@@ -33,6 +33,7 @@ interface ProjectData {
image: string; image: string;
gallery?: string[]; gallery?: string[];
website?: string; website?: string;
featured?: boolean;
} }
interface PartnerData { interface PartnerData {
@@ -94,6 +95,7 @@ export default function AdminClient({
const [formGallery, setFormGallery] = useState<string[]>([]); const [formGallery, setFormGallery] = useState<string[]>([]);
const [newGalleryItem, setNewGalleryItem] = useState(""); const [newGalleryItem, setNewGalleryItem] = useState("");
const [formWebsite, setFormWebsite] = useState(""); const [formWebsite, setFormWebsite] = useState("");
const [formFeatured, setFormFeatured] = useState(false);
const [uploadingImage, setUploadingImage] = useState(false); const [uploadingImage, setUploadingImage] = useState(false);
const [uploadingGallery, setUploadingGallery] = useState(false); const [uploadingGallery, setUploadingGallery] = useState(false);
@@ -178,6 +180,7 @@ export default function AdminClient({
setFormGallery(p.gallery ? [...p.gallery] : []); setFormGallery(p.gallery ? [...p.gallery] : []);
setNewGalleryItem(""); setNewGalleryItem("");
setFormWebsite(p.website || ""); setFormWebsite(p.website || "");
setFormFeatured(p.featured || false);
}; };
// Open modal for project adding // Open modal for project adding
@@ -200,6 +203,7 @@ export default function AdminClient({
setFormGallery([]); setFormGallery([]);
setNewGalleryItem(""); setNewGalleryItem("");
setFormWebsite(""); setFormWebsite("");
setFormFeatured(false);
}; };
// Save project // Save project
@@ -237,6 +241,7 @@ export default function AdminClient({
image: formImage, image: formImage,
gallery: formGallery, gallery: formGallery,
website: formWebsite, website: formWebsite,
featured: formFeatured,
}; };
const res = await saveProject(projectDataToSave); const res = await saveProject(projectDataToSave);
@@ -1157,6 +1162,18 @@ export default function AdminClient({
className="w-full font-mono text-[11px] bg-[#EDE8E0] border border-[#C8C2B8] focus:border-[#0A0A0A] outline-none px-3 py-2 text-[#0A0A0A]" className="w-full font-mono text-[11px] bg-[#EDE8E0] border border-[#C8C2B8] focus:border-[#0A0A0A] outline-none px-3 py-2 text-[#0A0A0A]"
/> />
</div> </div>
<div className="space-y-1">
<label className="font-mono text-[9px] uppercase text-[#A0998E] block">Öne Çıkan Proje (Ana Sayfa İçin)</label>
<div className="flex items-center h-[30px]">
<input
type="checkbox"
checked={formFeatured}
onChange={(e) => setFormFeatured(e.target.checked)}
className="w-4 h-4 cursor-pointer"
/>
<span className="ml-2 font-mono text-[10px] text-[#0A0A0A]">Bu bizim kendi projemiz (Ana sayfada göster)</span>
</div>
</div>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
+2 -2
View File
@@ -175,7 +175,7 @@ function Label({ children }: { children: React.ReactNode }) {
} }
// ── MAIN COMPONENT ── // ── MAIN COMPONENT ──
export default function HomeClient({ lang, dict }: { lang: Locale; dict: any }) { export default function HomeClient({ lang, dict, featuredProjects }: { lang: Locale; dict: any; featuredProjects?: any[] }) {
const heroRef = useRef<HTMLDivElement>(null); const heroRef = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ target: heroRef, offset: ["start start", "end start"] }); const { scrollYProgress } = useScroll({ target: heroRef, offset: ["start start", "end start"] });
const heroY = useTransform(scrollYProgress, [0, 1], ["0%", "25%"]); const heroY = useTransform(scrollYProgress, [0, 1], ["0%", "25%"]);
@@ -283,7 +283,7 @@ export default function HomeClient({ lang, dict }: { lang: Locale; dict: any })
</div> </div>
</section> </section>
<HomeClientBelowFold lang={lang} dict={dict} /> <HomeClientBelowFold lang={lang} dict={dict} featuredProjects={featuredProjects} />
</div> </div>
); );
} }
+4 -3
View File
@@ -146,7 +146,7 @@ function Label({ children }: { children: React.ReactNode }) {
); );
} }
export default function HomeClientBelowFold({ lang, dict }: { lang: Locale; dict: any }) { export default function HomeClientBelowFold({ lang, dict, featuredProjects }: { lang: Locale; dict: any; featuredProjects?: any[] }) {
const [activeFaq, setActiveFaq] = useState<number | null>(null); const [activeFaq, setActiveFaq] = useState<number | null>(null);
const [formSent, setFormSent] = useState(false); const [formSent, setFormSent] = useState(false);
const [hoverCase, setHoverCase] = useState<number | null>(null); const [hoverCase, setHoverCase] = useState<number | null>(null);
@@ -169,9 +169,10 @@ export default function HomeClientBelowFold({ lang, dict }: { lang: Locale; dict
})) }))
: []; : [];
const caseStudies = (dict.work.items as any[]).map((item: any, idx: number) => ({ const rawCases = featuredProjects && featuredProjects.length > 0 ? featuredProjects : (dict.work.items as any[]);
const caseStudies = rawCases.map((item: any, idx: number) => ({
...item, ...item,
tech: [ tech: item.tech || [
["Python", "TensorFlow", "Next.js", "PostgreSQL"], ["Python", "TensorFlow", "Next.js", "PostgreSQL"],
["Solidity", "React", "IPFS", "Node.js"], ["Solidity", "React", "IPFS", "Node.js"],
["Flutter", "Firebase", "Django", "AWS"], ["Flutter", "Firebase", "Django", "AWS"],
+1
View File
@@ -27,6 +27,7 @@ model Project {
image String @default("") image String @default("")
gallery String[] @default([]) gallery String[] @default([])
website String @default("") website String @default("")
featured Boolean @default(false)
} }
model BlogPost { model BlogPost {