Skip to content

Commit ff8e642

Browse files
committed
feat: add Footer component to multiple pages and create Footer layout
1 parent aa1cdcf commit ff8e642

7 files changed

Lines changed: 191 additions & 2 deletions

File tree

src/app/about/page.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Metadata } from "next";
66
import { appName, NEXT_PUBLIC_APP_URL } from "@lib/constants";
77
import AboutStructuredData from "@components/about-structured-data";
88
import BreadcrumbStructuredData from "@components/breadcrumb-structured-data";
9+
import Footer from "src/components/layouts/footer";
910

1011
const appPositions = ["I'm a Senior Front-end Developer", "and a Freelance UI/UX Designer."];
1112
const description = `My name is <span className="text-primary font-semibold">Leat Sophat</span>, also known as <span className="text-primary font-semibold">PPhat</span>.
@@ -74,6 +75,8 @@ const AboutPage = () => {
7475
</BlurFade>
7576
</section>
7677

78+
<Footer />
79+
7780
{/* <SectionNavigation sections={[ 'about-hero', 'experience', ]}/> */}
7881
</main>
7982
)

src/app/posts/[slug]/page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import BreadcrumbStructuredData from '@components/breadcrumb-structured-data';
1717
import { MarkdownRenderer } from '@components/ui/markdown-renderer';
1818
import { ScrollToTopButton } from '@components/ui/scroll-to-top-button';
1919
import { PostCoverImage } from '@components/ui/post-cover-image';
20+
import Footer from 'src/components/layouts/footer';
2021

2122
interface Params {
2223
params: Promise<{ slug: string; }>;
@@ -186,7 +187,7 @@ export default async function PostDetail(props: Params) {
186187
<div className="flex max-xs:flex-col max-sm:items-center max-sm:justify-center w-full items-center space-x-4">
187188
<div className='flex gap-5 flex-wrap border-t sm:py-3 border-background'>
188189
{post.authors?.map((author, index) => (
189-
<Link rel="noopener noreferrer" target='_blank' href={ author.url === "" ? String(author.profile).replace('.png', '') : author.url } key={index} className="flex items-center space-x-2">
190+
<Link rel="noopener noreferrer" target='_blank' href={author.url === "" ? String(author.profile).replace('.png', '') : author.url} key={index} className="flex items-center space-x-2">
190191
<Avatar className="w-8 h-8">
191192
<AvatarImage src={author.profile} alt={author.name} />
192193
<AvatarFallback>
@@ -256,6 +257,7 @@ export default async function PostDetail(props: Params) {
256257
</div>
257258
</div>
258259
</article>
260+
<Footer />
259261
<ScrollToTopButton />
260262
</>
261263
);

src/app/posts/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { useSearchParams, useRouter } from "next/navigation";
1212
import { Badge } from "@components/ui/badge";
1313
import { cn } from "@lib/utils";
1414
import { Button } from "src/components/ui";
15+
import Footer from "src/components/layouts/footer";
1516

1617
const PostsContent = () => {
1718
const router = useRouter();
@@ -210,6 +211,7 @@ const Posts = () => {
210211
</main>
211212
}>
212213
<PostsContent />
214+
<Footer />
213215
</Suspense>
214216
);
215217
};

src/app/projects/[slug]/page.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { MarkdownRenderer } from '@components/ui/markdown-renderer';
1717
import { ScrollToTopButton } from '@components/ui/scroll-to-top-button';
1818
import { PostCoverImage } from '@components/ui/post-cover-image';
1919
import { DividerVerticalIcon } from '@radix-ui/react-icons';
20+
import Footer from 'src/components/layouts/footer';
2021

2122
interface Params {
2223
params: Promise<{ slug: string }>;
@@ -271,6 +272,8 @@ export default async function ProjectDetail(props: Params) {
271272

272273
</article>
273274

275+
<Footer />
276+
274277
<ScrollToTopButton />
275278
</>
276279
);

src/app/projects/page.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import ProjectsStructuredData from "@components/projects-structured-data";
1212
import { useSearchParams, useRouter } from "next/navigation";
1313
import { Button } from "src/components/ui";
1414
import { cn } from "@lib/utils";
15+
import Footer from "src/components/layouts/footer";
1516

1617
const ProjectsContent = () => {
1718
const router = useRouter();
@@ -190,6 +191,8 @@ const ProjectsContent = () => {
190191
</InfiniteScroll>
191192
</article>
192193
</BlurFade>
194+
195+
<Footer />
193196
</main>
194197
);
195198
};

src/components/heros/home-hero.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { FlipWords } from "@components/flip-words";
77
import { GridPattern } from "@components/ui/grid-pattern";
88
import { appDescriptions, appName, appPositions } from "@lib/constants";
99
import { NavMenu } from "@components/dock-menu";
10-
import { ThemeToggle } from "@components/ui/theme-switch";
1110
import { MagneticArea } from "@components/ui/magnetic-button";
1211
import { cn } from "@lib/utils";
1312
import { Logos3 } from "@components/ui/logos3";

src/components/layouts/footer.tsx

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import Link from "next/link";
2+
import {
3+
GITHUB_URL,
4+
LINKEDIN_URL,
5+
TWITTER_URL,
6+
appTitle,
7+
// NEXT_PUBLIC_APP_URL,
8+
// appDescriptions
9+
} from "@lib/constants";
10+
import { GitHubIcon } from "../icons/github";
11+
// import { LinkedInIcon } from "../icons/linkedin";
12+
// import { TwitterLogoIcon } from "@radix-ui/react-icons";
13+
import { ArrowLeftIcon, ArrowRightIcon, MailCheckIcon } from "lucide-react";
14+
import { Button } from "../ui";
15+
// import { Github, Linkedin, Twitter, Mail } from "lucide-react";
16+
17+
const footerLinks = {
18+
product: [
19+
{ label: "Projects", href: "/projects" },
20+
{ label: "Gallery", href: "/gallery" },
21+
],
22+
company: [
23+
{ label: "About Me", href: "/about" },
24+
{ label: "Contact", href: "/contact" },
25+
{ label: "Blogs", href: "/posts" },
26+
],
27+
resources: [
28+
{ label: "Documentation", href: "/posts" },
29+
{ label: "Community", href: GITHUB_URL, external: true },
30+
{ label: "Support", href: "/contact" },
31+
],
32+
};
33+
34+
const socialLinks = [
35+
{
36+
label: "GitHub",
37+
href: GITHUB_URL,
38+
icon: GitHubIcon
39+
},
40+
{
41+
label: "LinkedIn",
42+
href: LINKEDIN_URL,
43+
icon: GitHubIcon
44+
},
45+
{
46+
47+
label: "Email",
48+
href: "/contact",
49+
icon: MailCheckIcon
50+
},
51+
];
52+
53+
export default function Footer() {
54+
const currentYear = new Date().getFullYear();
55+
56+
return (
57+
<footer className="w-full border-t px-3 border-border/40 bg-gradient-to-b from-background via-background/95 to-background backdrop-blur supports-[backdrop-filter]:bg-background/60">
58+
<div className="max-w-5xl mx-auto max-sm:px-5 py-12 md:py-16 lg:py-20">
59+
{/* Main Footer Content */}
60+
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-5 gap-8 md:gap-12 mb-12">
61+
{/* Brand Section */}
62+
<div className="col-span-2 md:col-span-4 lg:col-span-1 space-y-4">
63+
<Link href="/" className="inline-block group">
64+
<h2 className="text-xl font-bold text-primary group-hover:text-primary transition-colors">
65+
Sophat L.
66+
</h2>
67+
</Link>
68+
<p className="text-sm text-muted-foreground max-w-xs leading-relaxed">
69+
Building scalable systems & crafting digital experiences.
70+
</p>
71+
</div>
72+
73+
{/* Product Links */}
74+
<div className="space-y-4">
75+
<h3 className="font-semibold text-base text-primary pl-2">Product</h3>
76+
<ul className="gap-2">
77+
{footerLinks.product.map((link) => (
78+
<li key={link.href}>
79+
<Button asChild className="mt-0 text-foreground hover:text-primary -translate-x-2.5">
80+
<Link href={link.href}>
81+
<ArrowRightIcon className='w-4 h-4' /> {link.label}
82+
</Link>
83+
</Button>
84+
</li>
85+
))}
86+
</ul>
87+
</div>
88+
89+
{/* Company Links */}
90+
<div className="space-y-4">
91+
<h3 className="font-semibold text-base text-primary pl-2">Company</h3>
92+
<ul className="gap-2">
93+
{footerLinks.company.map((link) => (
94+
<li key={link.href}>
95+
<Button asChild className="mt-0 text-foreground hover:text-primary -translate-x-2.5">
96+
<Link href={link.href}>
97+
<ArrowRightIcon className='w-4 h-4' /> {link.label}
98+
</Link>
99+
</Button>
100+
</li>
101+
))}
102+
</ul>
103+
</div>
104+
105+
{/* Resources Links */}
106+
<div className="space-y-4">
107+
<h3 className="font-semibold text-base text-primary pl-2">Resources</h3>
108+
<ul className="gap-2">
109+
{footerLinks.resources.map((link) => (
110+
<li key={link.href}>
111+
<Button asChild className="mt-0 text-foreground hover:text-primary -translate-x-2.5">
112+
<Link href={link.href}>
113+
<ArrowRightIcon className='w-4 h-4' /> {link.label}
114+
</Link>
115+
</Button>
116+
</li>
117+
))}
118+
</ul>
119+
</div>
120+
121+
{/* Social Links */}
122+
<div className="space-y-4">
123+
<h3 className="font-semibold text-base pl-2 text-primary">Social</h3>
124+
<ul className="gap-2">
125+
{socialLinks.map((link) => {
126+
const Icon = link.icon;
127+
128+
return (
129+
<li key={link.href}>
130+
<Button asChild className="mt-0 text-foreground hover:text-primary -translate-x-2.5">
131+
<Link href={link.href}>
132+
<ArrowRightIcon className='w-4 h-4' /> {link.label}
133+
</Link>
134+
</Button>
135+
</li>
136+
);
137+
})}
138+
</ul>
139+
</div>
140+
</div>
141+
142+
{/* Bottom Bar */}
143+
<div className="pt-8 border-t border-border/40">
144+
<div className="flex flex-col md:flex-row justify-between items-center gap-4">
145+
<div className="flex items-center gap-2">
146+
<p className="text-sm text-muted-foreground text-center md:text-left">
147+
Copyright © {currentYear}{" "}
148+
<Link
149+
href="/"
150+
className="font-semibold text-foreground hover:text-primary transition-colors"
151+
>
152+
{appTitle}
153+
</Link>
154+
{" "}· All rights reserved
155+
</p>
156+
</div>
157+
158+
{/* <div className="flex items-center gap-6">
159+
<Link
160+
href="/privacy"
161+
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
162+
>
163+
Privacy Policy
164+
</Link>
165+
<Link
166+
href="/terms"
167+
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
168+
>
169+
Terms of Service
170+
</Link>
171+
</div> */}
172+
</div>
173+
</div>
174+
</div>
175+
</footer>
176+
);
177+
}

0 commit comments

Comments
 (0)