Skip to content
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview",
"report:pdf": "node scripts/build-report-pdf.mjs",
"deploy": "npm run build && firebase deploy",
"deploy:preview": "npm run build && firebase hosting:channel:deploy preview"
},
Expand Down
Binary file added public/images/report/day1-01.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day1-02.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day1-03.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day1-04.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day1-05.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day1-06.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day1-07.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day1-08.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day1-09.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day2-01.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day2-02.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day2-03.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day2-04.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day2-05.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day2-06.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day2-07.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day2-08.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day2-09.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day3-01.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day3-02.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day3-03.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day3-04.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day3-05.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day3-06.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day3-07.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/images/report/day3-08.webp
Binary file added public/images/report/day3-09.webp
Binary file added public/reports/pycon-cameroon-2026-report-en.pdf
Binary file not shown.
Binary file added public/reports/pycon-cameroon-2026-report-fr.pdf
Binary file not shown.
38 changes: 38 additions & 0 deletions public/sponsors/aws.svg
336 changes: 336 additions & 0 deletions scripts/build-report-pdf.mjs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const TouristSites = lazy(() => import('./pages/TouristSites'));
const Agenda = lazy(() => import('./pages/Agenda'));
const SpeakerDetail = lazy(() => import('./pages/SpeakerDetail'));
const SessionDetail = lazy(() => import('./pages/SessionDetail'));
const Report = lazy(() => import('./pages/Report'));

class ErrorBoundary extends Component {
constructor(props) {
Expand Down Expand Up @@ -94,6 +95,7 @@ function App() {
<Route path="financial-aid" element={<LazyPage><FinancialAid /></LazyPage>} />
<Route path="tourist-sites" element={<LazyPage><TouristSites /></LazyPage>} />
<Route path="agenda" element={<LazyPage><Agenda /></LazyPage>} />
<Route path="report" element={<LazyPage><Report /></LazyPage>} />
<Route path="speakers/:speakerId" element={<LazyPage><SpeakerDetail /></LazyPage>} />
<Route path="sessions/:sessionId" element={<LazyPage><SessionDetail /></LazyPage>} />
<Route path="*" element={<LegacyRedirect />} />
Expand Down
1 change: 1 addition & 0 deletions src/components/Footer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const Footer = () => {
<h4 className="footer-title">{t('footer.program')}</h4>
<div className="footer-links">
<Link to={l('/agenda')}>{t('footer.agenda')}</Link>
<Link to={l('/report')}>{t('footer.report')}</Link>
<Link to={l('/speakers')}>{t('footer.speakers')}</Link>
<Link to={l('/speakers#guidelines')}>{t('footer.proposalGuidelines')}</Link>
<Link to={l('/venue')}>{t('footer.venue')}</Link>
Expand Down
23 changes: 3 additions & 20 deletions src/components/SponsorShowcase.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const SilverCard = ({ sponsor, lang }) => (
</div>
);

// Shared logo-tile layout for the "bronze", "community", and "deployment" tiers.
// Shared logo-tile layout for the "bronze", "community", and "hosting" tiers.
// The description lives in a floating popover shown on hover/focus
// (desktop) or tap (touch devices) rather than inline.
const LogoTile = ({ sponsor, tier, lang, t }) => {
Expand Down Expand Up @@ -237,27 +237,10 @@ const SponsorShowcase = ({ sponsors = defaultSponsorsList }) => {
))}
</div>
)}

{tier === 'bronze' && (
<div className="grid grid-4 stagger">
{tierSponsors.map((sponsor) => (
<LogoTile key={sponsor.id} sponsor={sponsor} tier="bronze" lang={i18n.language} t={t} />
))}
</div>
)}

{tier === 'community' && (
<div className="grid grid-4 stagger">
{tierSponsors.map((sponsor) => (
<LogoTile key={sponsor.id} sponsor={sponsor} tier="community" lang={i18n.language} t={t} />
))}
</div>
)}

{tier === 'deployment' && (
{!['diamond', 'gold', 'silver'].includes(tier) && (
<div className="grid grid-4 stagger">
{tierSponsors.map((sponsor) => (
<LogoTile key={sponsor.id} sponsor={sponsor} tier="deployment" lang={i18n.language} t={t} />
<LogoTile key={sponsor.id} sponsor={sponsor} tier={tier} lang={i18n.language} t={t} />
))}
</div>
)}
Expand Down
30 changes: 30 additions & 0 deletions src/data/reportStats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { agenda, DAYS } from './agenda.js';
import { speakers } from './speakers.js';
import { sponsorsList } from './sponsorsList.js';
import { partnerCommunities } from './partners.js';

export const attendeeTotal = '457+';

export const reportSessions = Object.values(agenda).flat().filter((s) => s.id);

export const countBy = (items, key) =>
Object.entries(items.reduce((acc, item) => ({ ...acc, [key(item)]: (acc[key(item)] || 0) + 1 }), {}))
.sort((a, b) => b[1] - a[1]);

export const sessionFormats = countBy(reportSessions, (s) => s.type);
export const sessionTopics = countBy(reportSessions, (s) => s.category);
export const sessionLanguages = countBy(reportSessions, (s) => (s.lang === 'en' || s.lang === 'fr' ? s.lang : 'bilingual'));
export const speakerCountries = countBy(speakers, (s) => s.country || '');

export const headlineStats = [
{ key: 'attendees', value: attendeeTotal },
{ key: 'speakers', value: speakers.length },
{ key: 'sessions', value: reportSessions.length },
{ key: 'days', value: DAYS.length },
{ key: 'countries', value: speakerCountries.filter(([country]) => country).length },
{ key: 'sponsors', value: sponsorsList.length },
{ key: 'partners', value: partnerCommunities.length },
];

export const dayBadge = (day, dayWord, lang) =>
`${dayWord} ${DAYS.indexOf(day) + 1} · ${new Date(`${day.date} 12:00`).toLocaleDateString(lang, { day: 'numeric', month: 'long', year: 'numeric' })}`;
1 change: 1 addition & 0 deletions src/data/speakers.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ export const speakers = [
name: 'Veliswa Boya',
photo: '/speakers/Veliswa_Boya.webp',
title: 'Developer Advocate at AWS',
country: 'South Africa',
keynote: true,
bio: 'Senior Developer Advocate at AWS, working especially with (but not limited to) developers in the sub-Saharan Africa region. Formerly an AWS Community Hero, she was the first woman out of Africa to be named an AWS Hero. Her background spans software development, business and systems analysis, solutions architecture, and cloud engineering, and she loves working with people who are new to tech.',
linkedin: 'https://www.linkedin.com/in/veliswa-boya/',
Expand Down
19 changes: 15 additions & 4 deletions src/data/sponsorsList.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
export const TIERS = ['diamond', 'gold', 'silver', 'bronze', 'community', 'deployment'];
export const TIERS = ['diamond', 'gold', 'silver', 'dinner', 'bronze', 'community', 'hosting'];

/**
* @typedef {Object} Sponsor
* @property {string} id - unique, slug
* @property {string} name
* @property {'diamond'|'gold'|'silver'|'bronze'|'community'|'deployment'} tier
* @property {'diamond'|'gold'|'silver'|'dinner'|'bronze'|'community'|'hosting'} tier
* @property {string} logo - square, ~400x400, transparent or white background
* @property {string} [website] - optional
* @property {{ fr: string, en: string }} description - 1 to 3 sentences
Expand All @@ -13,7 +13,7 @@ export const TIERS = ['diamond', 'gold', 'silver', 'bronze', 'community', 'deplo
// {
// id: 'acme', // unique, slug
// name: 'ACME Corp',
// tier: 'gold', // 'diamond' | 'gold' | 'silver' | 'bronze' | 'community' | 'deployment'
// tier: 'gold', // 'diamond' | 'gold' | 'silver' | 'bronze' | 'community' | 'hosting'
// logo: '/sponsors/acme.webp', // carré, ~400×400, fond transparent ou blanc
// website: 'https://acme.example', // optionnel
// description: { fr: '…', en: '…' } // 1 à 3 phrases
Expand All @@ -32,6 +32,17 @@ export const sponsorsList = [
en: "Canonical delivers secure, enterprise-grade open-source solutions used by millions of developers and organizations worldwide. Its flagship product, Ubuntu, is the world's most used open-source OS and a go-to platform for cloud infrastructure and Python development. Canonical also offers MicroK8s, LXD, and Ubuntu Pro.",
},
},
{
id: 'aws',
name: 'AWS',
tier: 'dinner',
logo: '/sponsors/aws.svg',
website: 'https://aws.amazon.com',
description: {
fr: "Amazon Web Services (AWS) est la plateforme cloud d'Amazon, qui propose plus de 200 services — calcul, stockage, bases de données, IA et machine learning — utilisés par des millions de clients dans le monde. AWS soutient les développeurs Python avec son SDK Boto3 et AWS Lambda. Pour PyCon Cameroon 2026, AWS a offert le dîner des speakers.",
en: "Amazon Web Services (AWS) is Amazon's cloud platform, offering over 200 services — compute, storage, databases, AI and machine learning — used by millions of customers worldwide. AWS supports Python developers with its Boto3 SDK and AWS Lambda. For PyCon Cameroon 2026, AWS hosted the speaker dinner.",
},
},
{
id: 'apify',
name: 'Apify',
Expand Down Expand Up @@ -134,7 +145,7 @@ export const sponsorsList = [
{
id: 'pythonanywhere',
name: 'PythonAnywhere',
tier: 'deployment',
tier: 'hosting',
logo: '/sponsors/pythonanywhere.svg',
website: 'https://www.pythonanywhere.com',
description: {
Expand Down
Loading
Loading