Skip to content
This repository was archived by the owner on Oct 7, 2025. It is now read-only.

Commit bab148d

Browse files
committed
feat(db/brands): add sidebar, flatten social media links, and add validation
1 parent 1744086 commit bab148d

File tree

8 files changed

+371
-247
lines changed

8 files changed

+371
-247
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import type { CollectionBeforeChangeHook, CollectionConfig } from 'payload/types'
2+
3+
const SOCIAL_MEDIA_PLATFORMS = [
4+
{ key: 'github', domain: 'github.com', label: 'GitHub' },
5+
{ key: 'linkedin', domain: 'linkedin.com', label: 'LinkedIn' },
6+
{ key: 'instagram', domain: 'instagram.com', label: 'Instagram' },
7+
{ key: 'discord', domain: 'discord.gg', label: 'Discord' },
8+
{ key: 'behance', domain: 'behance.net', label: 'Behance' },
9+
{ key: 'figma', domain: 'figma.com', label: 'Figma' },
10+
{ key: 'linktree', domain: 'linktr.ee', label: 'Linktree' },
11+
] as const
12+
13+
// function validateSocialMediaHandle(platformLabel: string, domain: string) {
14+
// return (value: string) => {
15+
// if (!value)
16+
// return true
17+
// const sanitizedValue = value.replace(/^(https?:\/\/)?(www\.)?/, '')
18+
19+
// if (!sanitizedValue.includes('/')) {
20+
// return true
21+
// }
22+
23+
// if (!sanitizedValue.startsWith(domain)) {
24+
// return `Invalid ${platformLabel} URL. Expected a link from ${domain}.`
25+
// }
26+
27+
// const extractedHandle = sanitizedValue.replace(new RegExp(`^${domain}/?`), '')
28+
29+
// if (!extractedHandle || extractedHandle.includes('/')) {
30+
// return `Invalid ${platformLabel} handle. Only usernames or profile handles are allowed.`
31+
// }
32+
33+
// return true
34+
// }
35+
// }
36+
37+
const generateSocialLinks: CollectionBeforeChangeHook = async ({ data }) => {
38+
SOCIAL_MEDIA_PLATFORMS.forEach(({ key, domain }) => {
39+
const handle = data[key]
40+
if (handle) {
41+
const sanitizedHandle = handle.replace(/^(https?:\/\/)?(www\.)?/, '')
42+
43+
if (sanitizedHandle.includes(domain)) {
44+
const extractedHandle = sanitizedHandle.replace(new RegExp(`^${domain}/?`), '')
45+
46+
// if (!extractedHandle || extractedHandle.includes('/')) {
47+
// throw new Error(`Invalid ${key} URL format. Only usernames or profile handles are allowed.`)
48+
// }
49+
50+
data[`${key}Url`] = `https://${domain}/${extractedHandle}`
51+
data[key] = extractedHandle
52+
}
53+
else {
54+
// throw new Error(`Invalid ${key} URL. Expected a link from ${domain}.`)
55+
}
56+
}
57+
})
58+
59+
return data
60+
}
61+
62+
const socialMediaFields = SOCIAL_MEDIA_PLATFORMS.map(({ key, label,
63+
// domain
64+
}) => ({
65+
name: key,
66+
type: 'text',
67+
label,
68+
required: false,
69+
admin: { position: 'sidebar' },
70+
// validate: validateSocialMediaHandle(label, domain),
71+
}))
72+
73+
export const Brands: CollectionConfig = {
74+
slug: 'brands',
75+
access: {
76+
read: () => true,
77+
},
78+
admin: {
79+
useAsTitle: 'name',
80+
defaultColumns: ['name', 'description', 'id', 'updatedAt', 'createdAt'],
81+
},
82+
hooks: {
83+
beforeChange: [generateSocialLinks],
84+
},
85+
fields: [
86+
{ name: 'name', type: 'text', required: true, label: 'Name' },
87+
{ name: 'description', type: 'textarea', required: false, label: 'Description' },
88+
{ name: 'domain', type: 'text', required: false, admin: { position: 'sidebar' } },
89+
{
90+
name: 'links',
91+
type: 'array',
92+
label: 'Links',
93+
labels: {
94+
singular: 'Link',
95+
plural: 'Links',
96+
},
97+
required: false,
98+
fields: [
99+
{
100+
name: 'name',
101+
type: 'text',
102+
required: false,
103+
},
104+
{
105+
name: 'link',
106+
type: 'text',
107+
required: false,
108+
},
109+
],
110+
},
111+
{ name: 'email', type: 'email', label: 'Email Address', required: false },
112+
{ name: 'phone', type: 'number', label: 'Phone Number', required: false },
113+
{ name: 'location', type: 'text', label: 'Location', required: false },
114+
{ name: 'symbol', type: 'upload', relationTo: 'media', label: 'Symbol', required: false },
115+
{ name: 'wordmark', type: 'upload', relationTo: 'media', label: 'Wordmark', required: false },
116+
...socialMediaFields,
117+
],
118+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './Brands'
2+
export * from './seed'

libs/db/collections/Brands/seed.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
export const brandSeedData = [
2+
{
3+
name: 'cuHacking',
4+
description: 'Carleton University\'s Official Hackathon',
5+
symbol:
6+
'https://avatars.githubusercontent.com/u/29588588?s=400&u=8b806db4b4a6277abc155ed5d290dcfe6a6e7598&v=4',
7+
domain: 'cuhacking.ca',
8+
links: [
9+
{ name: 'Website', link: 'cuhacking.ca' },
10+
{ name: 'Portal', link: 'portal.cuhacking.ca' },
11+
{ name: 'Design', link: 'design.cuhacking.ca/' },
12+
{ name: 'Architecture', link: 'arch.cuhacking.ca/view/index' },
13+
{
14+
name: 'Graph',
15+
link: 'graph.cuhacking.ca/#/projects/all?groupByFolder=true',
16+
},
17+
{
18+
name: 'Tooling',
19+
link: 'docs.cuhacking.ca/contribution-guidelines/coding-standards/tooling',
20+
},
21+
{ name: 'ESLint', link: 'eslint.cuhacking.ca/rules' },
22+
{ name: 'Discord', link: 'discord.gg/h2cQqF9aZf' },
23+
{ name: 'Instagram', link: 'instagram.com/cuhacking/' },
24+
{
25+
name: 'LinkedIn',
26+
link: 'www.linkedin.com/company/cuhacking/',
27+
},
28+
{ name: 'Linktree', link: 'linktr.ee/cuhacking_' },
29+
{
30+
name: 'Brand',
31+
link: 'www.figma.com/design/wc1JOWR48tBNkjcjwY3AzB/%E2%8C%A8%EF%B8%8F-cuHacking-Design-System?node-id=0-1&t=YTR1ET4Qw1wG1cjz-1',
32+
},
33+
{
34+
name: 'Project Board',
35+
link: 'github.com/orgs/cuhacking/projects/4',
36+
},
37+
{ name: 'GitHub', link: 'github.com/cuhacking/2025' },
38+
],
39+
github: 'github.com/cuhacking/2025',
40+
linkedin: 'www.linkedin.com/company/cuhacking/',
41+
discord: 'discord.gg/h2cQqF9aZf',
42+
instagram: 'instagram.com/cuhacking/',
43+
linktree: 'linktr.ee/cuhacking_',
44+
figma: 'figma.com/design/wc1JOWR48tBNkjcjwY3AzB/%E2%8C%A8%EF%B8%8F-cuHacking-Design-System?node-id=0-1&t=YTR1ET4Qw1wG1cjz-1',
45+
},
46+
{
47+
name: 'Carleton Computer Science Society',
48+
description:
49+
'Student-run society for Computer Science at Carleton University',
50+
symbol:
51+
'https://carleton.ca/brand/wp-content/uploads/brand-logo-800w-1.jpg',
52+
domain: 'ccss.carleton.ca',
53+
},
54+
{
55+
name: 'Software Engineering Student Association',
56+
description:
57+
'University of Ottawa\'s Software Engineering Student Association',
58+
symbol:
59+
'https://www.uottawasesa.ca/static/media/logo.dc06f1167afbfbe0b7cf.png',
60+
domain: 'www.uottawasesa.ca',
61+
},
62+
{
63+
name: 'Carleton University',
64+
description: 'One of Canada\'s leading universities',
65+
symbol:
66+
'https://cdn.carleton.ca/rds/assets/cu-logos/cu-logo-color-right-horiztonal.svg',
67+
domain: 'carleton.ca',
68+
},
69+
{
70+
name: 'University of Ottawa',
71+
description: 'Canada\'s university for bilingual education',
72+
symbol:
73+
'https://images.seeklogo.com/logo-png/35/1/university-of-ottawa-logo-png_seeklogo-356159.png',
74+
domain: 'uottawa.ca/en',
75+
},
76+
{
77+
name: 'BlackBerry QNX',
78+
description: 'A leader in embedded systems and automotive software',
79+
symbol:
80+
'https://blackberry.qnx.com/etc.clientlibs/bbcom/clientlibs/clientlib-etc-legacy/resources/bbcom-aem-project/images/BlackBerry-QNX-logo-white-new.png',
81+
domain: 'blackberry.qnx.com',
82+
},
83+
{
84+
name: 'FullScript',
85+
description:
86+
'A platform for health professionals to support patient wellness',
87+
symbol:
88+
'https://marketing-uploads.fullscript.com/wp-content/uploads/2024/01/24130952/fullscript-logo-r.svg',
89+
domain: 'fullscript.com',
90+
},
91+
{
92+
name: 'Tail\'ed',
93+
description: 'A marketplace for tailors and custom clothing',
94+
symbol: 'https://tailed.ca/tailed-logo-w-name.png',
95+
domain: 'tailed.ca',
96+
},
97+
{
98+
name: 'Google',
99+
description: 'Google',
100+
symbol: 'https://upload.wikimedia.org/wikipedia/commons/c/c1/Google_%22G%22_logo.svg',
101+
domain: 'google.com',
102+
},
103+
{
104+
name: 'LinkedIn',
105+
description: 'LinkedIn',
106+
symbol: 'https://upload.wikimedia.org/wikipedia/commons/8/81/LinkedIn_icon.svg',
107+
domain: 'linkedin.com',
108+
},
109+
{
110+
name: 'Facebook',
111+
description: 'Facebook',
112+
symbol: 'https://upload.wikimedia.org/wikipedia/commons/b/b9/2023_Facebook_icon.svg',
113+
domain: 'facebook.com',
114+
},
115+
{
116+
name: 'Discord',
117+
description: 'Discord',
118+
symbol: 'https://www.svgrepo.com/show/353655/discord-icon.svg',
119+
domain: 'discord.com',
120+
},
121+
{
122+
name: 'Instagram',
123+
description: 'Instagram',
124+
symbol: 'https://upload.wikimedia.org/wikipedia/commons/e/e7/Instagram_logo_2016.svg',
125+
domain: 'instagram.com',
126+
},
127+
{
128+
name: 'Behance',
129+
description: 'Behance',
130+
symbol: 'https://cdn.worldvectorlogo.com/logos/behance-1.svg',
131+
domain: 'behance.net',
132+
},
133+
{
134+
name: 'Figma',
135+
description: 'Figma',
136+
symbol: 'https://upload.wikimedia.org/wikipedia/commons/3/33/Figma-logo.svg',
137+
domain: 'figma.com',
138+
},
139+
{
140+
name: 'LinkTree',
141+
description: 'LinkTree',
142+
symbol: 'https://uxwing.com/wp-content/themes/uxwing/download/brands-and-social-media/linktree-logo-icon.png',
143+
domain: 'linktr.ee',
144+
},
145+
{
146+
name: 'GitHub',
147+
description: 'GitHub',
148+
symbol: 'https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg',
149+
domain: 'github.com',
150+
},
151+
]

libs/db/collections/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
export * from './Brands'
12
export * from './globals'
23
export * from './models'

0 commit comments

Comments
 (0)