Skip to content

Commit f29de9e

Browse files
committed
added footer
1 parent 68bf17e commit f29de9e

File tree

11 files changed

+639
-40
lines changed

11 files changed

+639
-40
lines changed

package-lock.json

Lines changed: 98 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"preview": "vite preview"
1111
},
1212
"dependencies": {
13+
"@radix-ui/react-accordion": "^1.2.12",
1314
"@radix-ui/react-avatar": "^1.1.10",
1415
"@radix-ui/react-dialog": "^1.1.15",
1516
"@radix-ui/react-dropdown-menu": "^2.1.15",

src/App.tsx

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import ProjectDetails from "./components/pages/project-details";
88
import { useEffect } from "react";
99
import Apps from "./components/pages/apps";
1010
import AppDetails from "./components/pages/app-details";
11+
import { Footer } from "./components/footer";
1112

1213
function Root() {
1314
const { state } = useLocation();
@@ -27,23 +28,30 @@ function Root() {
2728
);
2829
}
2930

31+
function RootLayout() {
32+
return (
33+
<div className="min-h-screen bg-background container mx-auto px-4">
34+
<div className="mx-auto max-w-5xl">
35+
<Header />
36+
<main className="py-20">
37+
<Routes>
38+
<Route path="/" element={<Root />} />
39+
<Route path="/projects/:id" element={<ProjectDetails />} />
40+
<Route path="/apps" element={<Apps />} />
41+
<Route path="/apps/:id" element={<AppDetails />} />
42+
</Routes>
43+
</main>
44+
<Footer />
45+
</div>
46+
</div>
47+
);
48+
}
49+
3050
function App() {
3151
return (
3252
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
3353
<BrowserRouter>
34-
<div className="min-h-screen bg-background">
35-
<Header />
36-
<main className="container mx-auto px-4">
37-
<div className="mx-auto max-w-5xl">
38-
<Routes>
39-
<Route path="/" element={<Root />} />
40-
<Route path="/projects/:id" element={<ProjectDetails />} />
41-
<Route path="/apps" element={<Apps />} />
42-
<Route path="/apps/:id" element={<AppDetails />} />
43-
</Routes>
44-
</div>
45-
</main>
46-
</div>
54+
<RootLayout />
4755
</BrowserRouter>
4856
</ThemeProvider>
4957
);

src/components/footer.tsx

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import logoImage from "@/assets/simon-icon.png";
2+
import { useApps } from "@/hooks/useApps";
3+
import { useProjects } from "@/hooks/useProjects";
4+
import { useLocation } from "react-router";
5+
import { Button } from "./ui/button";
6+
7+
interface MenuItem {
8+
title: string;
9+
links: {
10+
text: string;
11+
url: string;
12+
}[];
13+
}
14+
15+
interface FooterProps {
16+
tagline?: string;
17+
menuItems?: MenuItem[];
18+
copyright?: string;
19+
}
20+
21+
const Footer = ({
22+
tagline = "Full-stack developer.",
23+
menuItems = [
24+
{
25+
title: "Portfolio",
26+
links: [
27+
{ text: "Overview", url: "/" },
28+
{ text: "Projects", url: "/" },
29+
{ text: "About", url: "/about" },
30+
],
31+
},
32+
{
33+
title: "Social",
34+
links: [
35+
{ text: "GitHub", url: "https://github.com/smanzler" },
36+
{ text: "LinkedIn", url: "https://www.linkedin.com/in/smanzler/" },
37+
{ text: "Email", url: "mailto:simanzler@gmail.com" },
38+
],
39+
},
40+
],
41+
copyright = ${new Date().getFullYear()} Simon Manzler. All rights reserved.`,
42+
}: FooterProps) => {
43+
const location = useLocation();
44+
const { apps } = useApps();
45+
const { projects } = useProjects();
46+
47+
const getBottomLinks = () => {
48+
if (location.pathname.startsWith("/apps/")) {
49+
const appTitle = location.pathname.split("/")[2];
50+
return [
51+
{
52+
text: "Privacy Policy",
53+
url: `/apps/${appTitle}/privacy`,
54+
},
55+
{
56+
text: "Terms of Service",
57+
url: `/apps/${appTitle}/terms`,
58+
},
59+
];
60+
} else if (location.pathname === "/apps") {
61+
return [{ text: "Back to Home", url: "/" }];
62+
}
63+
return [];
64+
};
65+
66+
const bottomLinks = getBottomLinks();
67+
68+
const footerMenuItems = [
69+
...menuItems,
70+
{
71+
title: "Projects",
72+
links: projects.map((project) => ({
73+
text: project.title,
74+
url: `/projects/${project.title}`,
75+
})),
76+
},
77+
{
78+
title: "Apps",
79+
links: apps.map((app) => ({
80+
text: app.title,
81+
url: `/apps/${app.title}`,
82+
})),
83+
},
84+
];
85+
86+
return (
87+
<section className="py-32 px-6">
88+
<div className="container">
89+
<footer>
90+
<div className="grid grid-cols-2 gap-8 lg:grid-cols-6">
91+
<div className="col-span-2 mb-8 lg:mb-0">
92+
<div className="flex items-center gap-2 lg:justify-start">
93+
<img
94+
src={logoImage}
95+
alt="Simon Manzler"
96+
title="Simon Manzler"
97+
className="h-10"
98+
/>
99+
</div>
100+
<p className="mt-4 font-bold">{tagline}</p>
101+
</div>
102+
{footerMenuItems.map((section, sectionIdx) => (
103+
<div key={sectionIdx} className="lg:text-right">
104+
<h3 className="mb-4 font-bold">{section.title}</h3>
105+
<ul className="space-y-4">
106+
{section.links.map((link, linkIdx) => (
107+
<li key={linkIdx}>
108+
<Button
109+
className="p-0 h-auto text-muted-foreground hover:text-primary font-medium"
110+
variant="link"
111+
asChild
112+
>
113+
<a href={link.url}>{link.text}</a>
114+
</Button>
115+
</li>
116+
))}
117+
</ul>
118+
</div>
119+
))}
120+
</div>
121+
<div className="text-muted-foreground mt-24 flex flex-col justify-between gap-4 border-t pt-8 text-sm font-medium md:flex-row md:items-center">
122+
<p>{copyright}</p>
123+
<ul className="flex gap-4">
124+
{bottomLinks.map((link, linkIdx) => (
125+
<li key={linkIdx} className="hover:text-primary underline">
126+
<a href={link.url}>{link.text}</a>
127+
</li>
128+
))}
129+
</ul>
130+
</div>
131+
</footer>
132+
</div>
133+
</section>
134+
);
135+
};
136+
137+
export { Footer };

0 commit comments

Comments
 (0)