diff --git a/src/lib/config.ts b/src/lib/config.ts index 778b63fd..651bb395 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -6,3 +6,4 @@ export const description = export const url = dev ? 'http://localhost:37572' : 'https://pauseai.info' export const botName = 'RogueGPT' export const verificationParameter = 'verificationKey' +export const defaultTitle = 'Volunteer' diff --git a/src/lib/types.ts b/src/lib/types.ts index cf8b0034..a697615e 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -34,6 +34,19 @@ export type AirtableSignatory = { duplicate?: boolean } +export type Person = { + id: string + name: string + /** URL to image file */ + image?: string + bio: string + title?: string + /** Doesn't want to be visible on the /people page */ + privacy?: boolean + checked?: boolean + duplicate?: boolean +} + export type Team = { id: string name: string diff --git a/src/routes/api/people/+server.ts b/src/routes/api/people/+server.ts new file mode 100644 index 00000000..09b10893 --- /dev/null +++ b/src/routes/api/people/+server.ts @@ -0,0 +1,87 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import type { Person } from '$lib/types' +import { defaultTitle } from '$lib/config' +import { json } from '@sveltejs/kit' +import { fetchAllPages } from '$lib/airtable' + +/** + * Fallback people data to use in development if Airtable fetch fails + */ +const fallbackPeople: Person[] = [ + { + id: 'fallback-stub1', + name: '[FALLBACK DATA] Example Person', + bio: 'I hold places when Airtable API is unavailable.', + title: 'Placeholder', + image: 'https://api.dicebear.com/7.x/bottts/svg?seed=fallback1', + privacy: false, + checked: true + }, + { + id: 'fallback-stub2', + name: '[FALLBACK DATA] Holdor', + bio: 'Thrown at games', + title: 'of Plays', + image: 'https://api.dicebear.com/7.x/bottts/svg?seed=fallback2', + privacy: false, + checked: true + } +] + +function recordToPerson(record: any): Person { + return { + id: record.id || 'noId', + name: record.fields['Full name'], + bio: record.fields.Bio2, + title: record.fields.Title || defaultTitle, + image: record.fields.Photo && record.fields.Photo[0].thumbnails.large.url, + privacy: record.fields.Privacy, + checked: record.fields.About, + duplicate: record.fields.duplicate + } +} + +const filter = (p: Person) => { + return ( + p.image && + !p.privacy && + p.checked && + p.title?.trim() !== '' && + p.title !== defaultTitle && + !p.duplicate + ) +} + +export async function GET({ fetch, setHeaders }) { + const url = `https://api.airtable.com/v0/appWPTGqZmUcs3NWu/tblL1icZBhTV1gQ9o` + setHeaders({ + 'cache-control': 'public, max-age=3600' // 1 hour in seconds + }) + + try { + // Create fallback records in the expected Airtable format + const fallbackRecords = fallbackPeople.map((person) => ({ + id: person.id, + fields: { + Name: person.name, + bio: person.bio, + title: person.title, + image: [{ thumbnails: { large: { url: person.image } } }], + privacy: person.privacy, + checked: person.checked + } + })) + + const records = await fetchAllPages(fetch, url, fallbackRecords) + const out: Person[] = records + .map(recordToPerson) + .filter(filter) + // Shuffle the array, although not truly random + .sort(() => 0.5 - Math.random()) + return json(out) + } catch (e) { + console.error('Error fetching people:', e) + // Return fallback data instead of error + return json(fallbackPeople.filter(filter)) + } +} diff --git a/src/routes/api/posts/+server.ts b/src/routes/api/posts/+server.ts index 64daf638..f4e75e8c 100644 --- a/src/routes/api/posts/+server.ts +++ b/src/routes/api/posts/+server.ts @@ -5,6 +5,7 @@ import { communitiesMeta } from '../../communities/communities' import { meta as pdoomMeta } from '../../pdoom/meta' import { meta as quotesMeta } from '../../quotes/meta' import { meta as emailBuilderMeta } from '../../email-builder/meta' +import { meta as peopleMeta } from '../../people/meta' import { meta as teamsMeta } from '../../teams/meta' import { meta as statementMeta } from '../../statement/meta' import { meta as dearSirDemisMeta } from '../../dear-sir-demis-2025/meta' @@ -16,6 +17,7 @@ const hardCodedPages: Post[] = [ pdoomMeta, quotesMeta, emailBuilderMeta, + peopleMeta, teamsMeta, statementMeta, dearSirDemisMeta diff --git a/src/routes/people/+page.svelte b/src/routes/people/+page.svelte new file mode 100644 index 00000000..9b1d177d --- /dev/null +++ b/src/routes/people/+page.svelte @@ -0,0 +1,30 @@ + + + + +

{title}

+ +
+ {#if people.length === 0} +

No team members found

+ {/if} + +
+ + \ No newline at end of file diff --git a/src/routes/people/+page.ts b/src/routes/people/+page.ts new file mode 100644 index 00000000..acc08e42 --- /dev/null +++ b/src/routes/people/+page.ts @@ -0,0 +1,21 @@ +import type { Person } from '$lib/types' +import { defaultTitle } from '$lib/config' + +export const prerender = false + +export const load = async ({ fetch, setHeaders }) => { + const response = await fetch('api/people') + const people: Person[] = await response.json() + setHeaders({ + 'cache-control': 'public, max-age=3600' // 1 hour in seconds + }) + // sort people, those who dont have "Volunteer" as title should be at the top + people.sort((a, b) => { + if (a.title === defaultTitle && b.title !== defaultTitle) return 1 + if (a.title !== defaultTitle && b.title === defaultTitle) return -1 + return 0 + }) + return { + people: people + } +} diff --git a/src/routes/people/meta.ts b/src/routes/people/meta.ts new file mode 100644 index 00000000..b55fc8ad --- /dev/null +++ b/src/routes/people/meta.ts @@ -0,0 +1,9 @@ +import type { Post } from '$lib/types' + +export const meta: Post = { + title: 'People of PauseAI', + description: 'Staff and contributors behind PauseAI', + date: '2025-11-06', + slug: 'people', + categories: [] +} diff --git a/src/routes/people/person.svelte b/src/routes/people/person.svelte new file mode 100644 index 00000000..0f4c010d --- /dev/null +++ b/src/routes/people/person.svelte @@ -0,0 +1,114 @@ + + +
  • + {#if image} +
    + {/if} +
    +
    +
    + {name} +
    + {#if title} +
    {title}
    + {/if} +
    + {#if bio} +
    + {bioOpen ? bio : bioTruncated} + {#if !bioOpen && bio.length > len} + + {/if} +
    + {/if} +
    +
  • + + \ No newline at end of file