8000 Footers for tenants by rchlfryn · Pull Request #201 · NWACus/web · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Footers for tenants #201

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
May 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 76 additions & 15 deletions src/Footer/Component.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,88 @@
import { getCachedGlobal } from '@/utilities/getGlobals'
import configPromise from '@payload-config'
import Link from 'next/link'
import invariant from 'tiny-invariant'

import { CMSLink } from '@/components/Link'
import { Logo } from '@/components/Logo/Logo'
import { ImageMedia } from '@/components/Media/ImageMedia'
import { Icons } from '@/components/ui/icons'
import { getPayload } from 'payload'

export async function Footer({ center }: { center?: string }) {
const footerData = await getCachedGlobal('footer', 1)()
const payload = await getPayload({ config: configPromise })
const { docs: data } = await payload.find({
collection: 'footer',
depth: 10,
where: {
'tenant.slug': {
equals: center,
},
},
})
const { address, email, hashtag, footerLogo, name, phone, privacy, socialMedia, terms } =
data?.[0]

const navItems = footerData?.navItems || []
invariant(
typeof terms === 'object',
`Depth not set correctly when querying footer. Terms for tenant ${center} not an object.`,
)

invariant(
typeof privacy === 'object',
`Depth not set correctly when querying footer. Privacy for tenant ${center} not an object.`,
)

const currentYear = new Date().getFullYear()
return (
<footer className="mt-auto border-t border-border bg-footer text-footer-foreground">
<div className="container py-8 gap-8 flex flex-col md:flex-row md:justify-between">
<Link className="flex items-center" href="/">
<Logo center={center} />
</Link>

<div className="flex flex-col-reverse items-start md:flex-row gap-4 md:items-center">
<nav className="flex flex-col md:flex-row gap-4">
{navItems.map(({ link }, i) => {
return <CMSLink className="text-footer-foreground" key={i} {...link} />
})}
</nav>
<div className="container py-8 gap-8 grid grid-cols-3">
<div>
<h4 className="font-medium text-xl mb-2">Stay updated!</h4>
<p>Sign up for our newsletter</p>
</div>
<div>
<Link className="flex items-center" href="/">
{footerLogo ? <ImageMedia resource={footerLogo} /> : <Logo center={center} />}
</Link>
</div>
<div>
<div className="flex flex-col items-start">
<h4 className="font-medium text-xl mb-2">{name}</h4>
{address && <div className="whitespace-pre-line">{address}</div>}
{phone && <a href={`tel:${phone}`}>{phone}</a>}
{email && (
<a className="mb-6 text-secondary underline" href={`mailto:${email}`}>
{email}
</a>
)}
{socialMedia && (
<div className="flex gap-x-4 mb-2">
{socialMedia.instagram && <Icons.instagram />}
{socialMedia.facebook && <Icons.facebook />}
{socialMedia.twitter && <Icons.twitter />}
{socialMedia.linkedin && <Icons.linkedin />}
{socialMedia.youtube && <Icons.youtube />}
</div>
)}
{hashtag && <p className="mb-4 text-secondary underline">{hashtag}</p>}
</div>
</div>
</div>
<div className="container text-center pb-8">
<p className="mb-2">
All Content © {currentYear} {name}
</p>
<div className="flex gap-x-2 justify-center">
{terms && (
<a href={`/${terms.slug}`} className="underline">
{terms.title}
</a>
)}
{terms && privacy && <div>|</div>}
{privacy && (
<a href={`/${privacy.slug}`} className="underline">
{privacy.title}
</a>
)}
</div>
</div>
</footer>
Expand Down
13 changes: 0 additions & 13 deletions src/Footer/RowLabel.tsx

This file was deleted.

32 changes: 0 additions & 32 deletions src/Footer/config.ts

This file was deleted.

2 changes: 0 additions & 2 deletions src/app/(payload)/admin/importMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { default as default_55a7d1ebef7afeed563b856ae2e2cbf4 } from '@/component
import { default as default_9d7720c4b50db35595dfefa592fabd33 } from '@/components/TenantSelector'
import { LinkLabelDescription as LinkLabelDescription_cc2cf53f1598892c0c926f3cb616a721 } from '@/fields/navLink/components/LinkLabelDescription'
import { SlugComponent as SlugComponent_92cc057d0a2abb4f6cf0307edf59f986 } from '@/fields/slug/SlugComponent'
import { RowLabel as RowLabel_1f6ff6ff633e3695d348f4f3c58f1466 } from '@/Footer/RowLabel'
import { TenantField as TenantField_1d0591e3cf4f332c83a86da13a0de59a } from '@payloadcms/plugin-multi-tenant/client'
import {
GlobalViewRedirect as GlobalViewRedirect_d6d5f193a167989e2ee7d14202901e62,
Expand Down Expand Up @@ -90,7 +89,6 @@ export const importMap = {
LinkLabelDescription_cc2cf53f1598892c0c926f3cb616a721,
'@payloadcms/plugin-search/client#LinkToDoc': LinkToDoc_aead06e4cbf6b2620c5c51c9ab283634,
'@payloadcms/plugin-search/client#ReindexButton': ReindexButton_aead06e4cbf6b2620c5c51c9ab283634,
'@/Footer/RowLabel#RowLabel': RowLabel_1f6ff6ff633e3695d348f4f3c58f1466,
'@payloadcms/plugin-multi-tenant/rsc#GlobalViewRedirect':
GlobalViewRedirect_d6d5f193a167989e2ee7d14202901e62,
'@/components/BeforeDashboard#default': default_1a7510af427896d367a49dbf838d2de6,
Expand Down
154 changes: 154 additions & 0 deletions src/collections/Footer/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { accessByTenant } from '@/access/byTenant'
import { filterByTenant } from '@/access/filterByTenant'
import { contentHashField } from '@/fields/contentHashField'
import { tenantField } from '@/fields/tenantField'
import { generatePreviewPath } from '@/utilities/generatePreviewPath'
import type { CollectionConfig, TextFieldValidation } from 'payload'

const validateHashtag: TextFieldValidation = (value: string | null | undefined): string | true => {
return value
? /^#[A-Za-z0-9_](?:[A-Za-z0-9_]|(?:\.(?!\.))){0,28}[A-Za-z0-9_]$/.test(value) ||
`${value} is not a valid hashtag`
: true
}

const validateTelephone: TextFieldValidation = (
value: string | null | undefined,
): string | true => {
return value
? /^(\+1\s?|1\s?)?(\(\d{3}\)|\d{3})[\s.-]?\d{3}[\s.-]?\d{4}$/.test(value) ||
`${value} is not a valid phone number`
: true
}

export const Footer: CollectionConfig = {
slug: 'footer',
access: accessByTenant('footer'),
admin: {
// the GlobalViewRedirect will never allow a user to visit the list view of this collection but including this list filter as a precaution
baseListFilter: filterByTenant,
group: 'Globals',
livePreview: {
url: async ({ data, req, payload }) => {
let tenant = data.tenant

if (typeof tenant === 'number') {
tenant = await payload.findByID({
collection: 'tenants',
id: tenant,
depth: 2,
})
}

const path = generatePreviewPath({
slug: '',
collection: 'pages',
tenant,
req,
})

return path
},
},
},
fields: [
tenantField({ unique: true }),
{
name: 'footerLogo',
type: 'upload',
relationTo: 'media',
filterOptions: {
mimeType: { contains: 'image' },
},
},
{
name: 'name',
type: 'text',
required: true,
},
{
name: 'address',
type: 'textarea',
},
{
name: 'phone',
type: 'text',
validate: validateTelephone,
},
{
name: 'email',
type: 'email',
},
{
name: 'socialMedia',
type: 'group',
fields: [
{
type: 'row',
fields: [
{
name: 'instagram',
type: 'text',
admin: {
width: '33%',
},
},
{
name: 'facebook',
type: 'text',
admin: {
width: '33%',
},
},
{
name: 'twitter',
type: 'text',
admin: {
width: '33%',
},
},
],
},
{
type: 'row',
fields: [
{
name: 'linkedin',
type: 'text',
admin: {
width: '33%',
},
},
{
name: 'youtube',
type: 'text',
admin: {
width: '33%',
},
},
],
},
],
admin: {
description:
'Add link to social media page to have the icon appear in the footer. Leave the field blank if you do not want the icon to show.',
},
},
{
name: 'hashtag',
type: 'text',
validate: validateHashtag,
},
{
name: 'terms',
type: 'relationship',
relationTo: 'pages',
},
{
name: 'privacy',
type: 'relationship',
relationTo: 'pages',
},
contentHashField(),
],
}
Loading
0