Add footer to website
This commit is contained in:
70
astro/src/components/footer/Footer.astro
Normal file
70
astro/src/components/footer/Footer.astro
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
import { getFooter } from "@/content/footer/footer";
|
||||||
|
import { Image } from "astro:assets";
|
||||||
|
|
||||||
|
const footer = await getFooter();
|
||||||
|
---
|
||||||
|
|
||||||
|
<footer class="w-full mt-4">
|
||||||
|
<div class="flex flex-col pt-12 pb-8 px-12 lg:container mx-auto">
|
||||||
|
<div class="flex md:flex-row flex-col justify-center pt-12 pb-8 px-12 lg:container mx-auto md:gap-y-8 gap-y-12 gap-x-24">
|
||||||
|
{ (footer.title !== null || footer.logo !== null) && (
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
{ footer.title !== null && <h2 class="text-5xl font-bold">{footer.title}</h2>}
|
||||||
|
{ footer.logo !== null && (
|
||||||
|
<Image
|
||||||
|
src={footer.logo.url}
|
||||||
|
width={footer.logo.width}
|
||||||
|
height={footer.logo.height}
|
||||||
|
alt={footer.title ?? ""}
|
||||||
|
class="w-50 h-auto"
|
||||||
|
/>
|
||||||
|
) }
|
||||||
|
</div>
|
||||||
|
) }
|
||||||
|
|
||||||
|
{ footer.columns.map((column) => (
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<h2 class="text-4xl font-bold">{column.title}</h2>
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
{column.links.map((link) => (
|
||||||
|
<a class="text-lg text-neutral-800" href={link.url}>{link.text}</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)) }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ footer.socials !== null && (
|
||||||
|
<div class="flex flex-row gap-3 justify-center">
|
||||||
|
{ footer.socials.map((social) => (
|
||||||
|
<a href={social.url} target="_blank" class="bg-neutral-300 hover:bg-neutral-400 duration-300 rounded-full">
|
||||||
|
<div class="p-2">
|
||||||
|
<Image
|
||||||
|
src={social.icon.url}
|
||||||
|
width={32}
|
||||||
|
height={32}
|
||||||
|
alt={social.name}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
)) }
|
||||||
|
</div>
|
||||||
|
) }
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{ (footer.copyright !== null || footer.secondaryLinks !== null) && (
|
||||||
|
<div class="border-t border-t-neutral-200 w-full bg-neutral-50">
|
||||||
|
<div class="flex md:flex-row flex-col justify-between py-8 px-12 lg:container mx-auto gap-y-8 gap-x-24">
|
||||||
|
{ footer.copyright !== null && (<div class="text-neutral-600">{footer.copyright}</div>) }
|
||||||
|
{ footer.secondaryLinks !== null && (
|
||||||
|
<div class="flex flex-row gap-1.5">
|
||||||
|
{ footer.secondaryLinks.map((link) => (
|
||||||
|
<a class="text-neutral-600 w-fit" href={link.url}>{link.text}</a>
|
||||||
|
)) }
|
||||||
|
</div>
|
||||||
|
) }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) }
|
||||||
|
</footer>
|
||||||
110
astro/src/content/footer/footer.ts
Normal file
110
astro/src/content/footer/footer.ts
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import { createDirectusConnection } from "@/lib/directus";
|
||||||
|
import { print } from 'graphql';
|
||||||
|
import type { Footer, FooterColumn, FooterSecondaryLink, FooterSocial } from "@/types/footers/footer";
|
||||||
|
import getFooterQuery from '@/graphql/footer/getFooter.graphql';
|
||||||
|
import { getImageUrl } from "@/lib/images";
|
||||||
|
|
||||||
|
export async function getFooter(): Promise<Footer> {
|
||||||
|
const client = await createDirectusConnection();
|
||||||
|
const result = await client.query(print(getFooterQuery));
|
||||||
|
|
||||||
|
const footerRecord = result['Footer'];
|
||||||
|
|
||||||
|
let dates: string[] = [
|
||||||
|
footerRecord['date_created'],
|
||||||
|
footerRecord['date_updated']
|
||||||
|
];
|
||||||
|
|
||||||
|
let footer: Footer = {
|
||||||
|
id: footerRecord['id'],
|
||||||
|
title: footerRecord['title'],
|
||||||
|
logo: {
|
||||||
|
url: getImageUrl(footerRecord['logo']['filename_disk']),
|
||||||
|
width: footerRecord['logo']['width'],
|
||||||
|
height: footerRecord['logo']['height']
|
||||||
|
},
|
||||||
|
copyright: footerRecord['copyright'],
|
||||||
|
columns: [],
|
||||||
|
socials: null,
|
||||||
|
secondaryLinks: null,
|
||||||
|
lastModified: new Date()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (footerRecord['columns'] !== null) {
|
||||||
|
footerRecord['columns'].forEach((footerColumn: any) => {
|
||||||
|
const column: FooterColumn = {
|
||||||
|
id: footerColumn['id'],
|
||||||
|
title: footerColumn['title'],
|
||||||
|
links: []
|
||||||
|
};
|
||||||
|
|
||||||
|
footerColumn['links'].forEach((columnLink: any) => {
|
||||||
|
column.links.push({
|
||||||
|
id: columnLink['id'],
|
||||||
|
text: columnLink['text'],
|
||||||
|
url: columnLink['url']
|
||||||
|
});
|
||||||
|
|
||||||
|
dates.push(columnLink['date_created']);
|
||||||
|
dates.push(columnLink['date_updated']);
|
||||||
|
});
|
||||||
|
|
||||||
|
footer.columns.push(column);
|
||||||
|
|
||||||
|
dates.push(footerColumn['date_created']);
|
||||||
|
dates.push(footerColumn['date_updated']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (footerRecord['socials'] !== null) {
|
||||||
|
let socials: FooterSocial[] = [];
|
||||||
|
|
||||||
|
footerRecord['socials'].forEach((footerSocial: any) => {
|
||||||
|
socials.push({
|
||||||
|
id: footerSocial['id'],
|
||||||
|
name: footerSocial['name'],
|
||||||
|
url: footerSocial['url'],
|
||||||
|
icon: {
|
||||||
|
url: getImageUrl(footerSocial['icon']['filename_disk']),
|
||||||
|
width: footerSocial['icon']['width'],
|
||||||
|
height: footerSocial['icon']['height']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dates.push(footerSocial['date_created']);
|
||||||
|
dates.push(footerSocial['date_updated']);
|
||||||
|
});
|
||||||
|
|
||||||
|
footer.socials = socials;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (footerRecord['secondary_links'] !== null) {
|
||||||
|
let secondaryLinks: FooterSecondaryLink[] = [];
|
||||||
|
|
||||||
|
footerRecord['secondary_links'].forEach((footerSecondaryLink: any) => {
|
||||||
|
secondaryLinks.push({
|
||||||
|
id: footerSecondaryLink['id'],
|
||||||
|
text: footerSecondaryLink['text'],
|
||||||
|
url: footerSecondaryLink['url']
|
||||||
|
});
|
||||||
|
|
||||||
|
dates.push(footerSecondaryLink['date_created']);
|
||||||
|
dates.push(footerSecondaryLink['date_updated']);
|
||||||
|
});
|
||||||
|
|
||||||
|
footer.secondaryLinks = secondaryLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dates.filter(e => e !== null).length === 0) {
|
||||||
|
footer.lastModified = new Date();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const sortedDates: string[] = dates.sort((a: string, b: string) => {
|
||||||
|
return new Date(b).getTime() - new Date(a).getTime();
|
||||||
|
});
|
||||||
|
|
||||||
|
footer.lastModified = new Date(sortedDates[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return footer;
|
||||||
|
}
|
||||||
50
astro/src/graphql/footer/getFooter.graphql
Normal file
50
astro/src/graphql/footer/getFooter.graphql
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
query getFooter {
|
||||||
|
Footer {
|
||||||
|
id,
|
||||||
|
date_created,
|
||||||
|
date_updated,
|
||||||
|
title,
|
||||||
|
logo {
|
||||||
|
id,
|
||||||
|
created_on,
|
||||||
|
filename_disk,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
},
|
||||||
|
copyright,
|
||||||
|
columns {
|
||||||
|
id,
|
||||||
|
date_created,
|
||||||
|
date_updated,
|
||||||
|
title,
|
||||||
|
links {
|
||||||
|
id,
|
||||||
|
date_created,
|
||||||
|
date_updated,
|
||||||
|
text,
|
||||||
|
url
|
||||||
|
}
|
||||||
|
},
|
||||||
|
socials {
|
||||||
|
id,
|
||||||
|
date_created,
|
||||||
|
date_updated,
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
icon {
|
||||||
|
id,
|
||||||
|
created_on,
|
||||||
|
filename_disk,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
}
|
||||||
|
},
|
||||||
|
secondary_links {
|
||||||
|
id,
|
||||||
|
date_created,
|
||||||
|
date_updated,
|
||||||
|
text,
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import '@/styles/global.css';
|
import '@/styles/global.css';
|
||||||
import { getSettings } from "@/content/settings/settings";
|
import { getSettings } from "@/content/settings/settings";
|
||||||
import { getTextColor } from '@/lib/colors';
|
import { getTextColor } from '@/lib/colors';
|
||||||
|
import Footer from '@/components/footer/Footer.astro';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
settings: BlogLayoutProps;
|
settings: BlogLayoutProps;
|
||||||
@@ -77,7 +78,10 @@ const css = {
|
|||||||
|
|
||||||
<!-- Scripts and Style -->
|
<!-- Scripts and Style -->
|
||||||
</head>
|
</head>
|
||||||
<body style={ css } class="bg-[#fcfcfc]">
|
|
||||||
<slot name="content" />
|
<body style={ css } class="bg-[#fcfcfc] flex flex-col min-h-screen">
|
||||||
|
<slot class="grow" name="content" />
|
||||||
|
|
||||||
|
<Footer />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import '@/styles/global.css';
|
import '@/styles/global.css';
|
||||||
import { getSettings } from "@/content/settings/settings";
|
import { getSettings } from "@/content/settings/settings";
|
||||||
import { getTextColor } from '@/lib/colors';
|
import { getTextColor } from '@/lib/colors';
|
||||||
|
import Footer from '@/components/footer/Footer.astro';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
settings: WebpageLayoutProps;
|
settings: WebpageLayoutProps;
|
||||||
@@ -74,7 +75,10 @@ const css = {
|
|||||||
|
|
||||||
<!-- Scripts and Style -->
|
<!-- Scripts and Style -->
|
||||||
</head>
|
</head>
|
||||||
<body style={ css } class="bg-[#fcfcfc]">
|
|
||||||
<slot name="content" />
|
<body style={ css } class="bg-[#fcfcfc] flex flex-col min-h-screen">
|
||||||
|
<slot class="grow" name="content" />
|
||||||
|
|
||||||
|
<Footer />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import '@/styles/global.css';
|
import '@/styles/global.css';
|
||||||
import { getSettings } from "@/content/settings/settings";
|
import { getSettings } from "@/content/settings/settings";
|
||||||
import { getTextColor } from '@/lib/colors';
|
import { getTextColor } from '@/lib/colors';
|
||||||
|
import Footer from '@/components/footer/Footer.astro';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
settings: BlogLayoutProps;
|
settings: BlogLayoutProps;
|
||||||
@@ -77,7 +78,10 @@ const css = {
|
|||||||
|
|
||||||
<!-- Scripts and Style -->
|
<!-- Scripts and Style -->
|
||||||
</head>
|
</head>
|
||||||
<body style={ css } class="bg-[#fcfcfc]">
|
|
||||||
<slot name="content" />
|
<body style={ css } class="bg-[#fcfcfc] flex flex-col min-h-screen">
|
||||||
|
<slot class="grow" name="content" />
|
||||||
|
|
||||||
|
<Footer />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import '@/styles/global.css';
|
import '@/styles/global.css';
|
||||||
import { getSettings } from "@/content/settings/settings";
|
import { getSettings } from "@/content/settings/settings";
|
||||||
import { getTextColor } from '@/lib/colors';
|
import { getTextColor } from '@/lib/colors';
|
||||||
|
import Footer from '@/components/footer/Footer.astro';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
settings: WebpageLayoutProps;
|
settings: WebpageLayoutProps;
|
||||||
@@ -74,7 +75,10 @@ const css = {
|
|||||||
|
|
||||||
<!-- Scripts and Style -->
|
<!-- Scripts and Style -->
|
||||||
</head>
|
</head>
|
||||||
<body style={ css } class="bg-[#fcfcfc]">
|
|
||||||
<slot name="content" />
|
<body style={ css } class="bg-[#fcfcfc] flex flex-col min-h-screen">
|
||||||
|
<slot class="grow" name="content" />
|
||||||
|
|
||||||
|
<Footer />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
44
astro/src/types/footers/footer.d.ts
vendored
Normal file
44
astro/src/types/footers/footer.d.ts
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import type { StringValidation } from "astro:schema";
|
||||||
|
|
||||||
|
type Footer = {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
title: string | null;
|
||||||
|
logo: PhotoProps | null;
|
||||||
|
copyright: string | null;
|
||||||
|
|
||||||
|
columns: FooterColumn[];
|
||||||
|
socials: FooterSocial[] | null;
|
||||||
|
secondaryLinks: FooterSecondaryLink[] | null;
|
||||||
|
|
||||||
|
lastModified: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
type FooterColumn = {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
title: string;
|
||||||
|
links: FooterColumnLink[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type FooterSocial = {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
icon: PhotoProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
type FooterSecondaryLink = {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
text: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type FooterColumnLink = {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
text: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user