From 1cbf13de51adbf363abb4978a56f7dbf7a972828 Mon Sep 17 00:00:00 2001 From: Mao Nakamoto <41178744+maonakamoto@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:56:49 +0200 Subject: [PATCH] fix(workshops): the empty state told people to wait for something they could start MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /workshops is where pillar 4 — retraining for a labour market AI is rewriting — sends people, and it answered them with: Aktuell sind keine Workshops geplant Schau bald wieder vorbei! with a working "Workshop vorschlagen" button directly above it and another CTA below. The page was telling you to wait for something you could start yourself. The proposal pipeline is complete, and I traced it end to end before touching anything rather than assuming it was broken: - POST /api/workshops/propose is withAuth and creates a proposal - the propose page checks the session and shows a login prompt, so nobody fills six form sections only to be 401'd on submit - submitting renders a confirmation naming the proposal, with a link back - the admin approve route inserts BOTH a workshops row AND a scheduled workshop_instance, so an approved proposal lands here WITH a date "0 Workshops" therefore means nobody has proposed yet — not that anything is broken. (A previous session had already removed the one real dead end here: /admin/workshops/new was a form that validated, waited 1s and silently discarded everything. It redirects into the proposal flow now.) So the empty state leads with that action: "Das Programm entsteht aus Vorschlägen. Schlag einen Workshop vor — als Thema, das du lernen willst, oder als Kurs, den du selbst geben möchtest." Both directions are named because the form supports both and only one of them was obvious. Nothing here claims a workshop exists. Below it, two cards for what evig can teach TODAY: the 43-entry open-source alternatives registry and the Linux setup guide with its distro matrix. They render only while the calendar is empty — once workshops exist, the workshops are the answer. Structure in a config array, strings under `workshops.browse.meanwhile.`, paired by stable id. npm run verify green: lint, umlauts, chrome, docs, compliance, typecheck, 7755 tests, production build. Co-Authored-By: Claude Opus 5 (1M context) --- messages/de.json | 20 +++- messages/en.json | 20 +++- messages/es.json | 20 +++- messages/fr.json | 20 +++- messages/it.json | 20 +++- messages/ja.json | 20 +++- messages/ko.json | 20 +++- messages/ru.json | 20 +++- .../workshops/WorkshopBrowseClient.tsx | 110 ++++++++++++++---- 9 files changed, 231 insertions(+), 39 deletions(-) diff --git a/messages/de.json b/messages/de.json index 14c3eda42..b05d33fbf 100644 --- a/messages/de.json +++ b/messages/de.json @@ -1832,13 +1832,29 @@ "clearFilters": "Filter zurücksetzen" }, "emptyAll": { - "title": "Aktuell sind keine Workshops geplant", - "subtitle": "Schau bald wieder vorbei!" + "title": "Noch ist kein Termin ausgeschrieben", + "subtitle": "Das Programm entsteht aus Vorschlägen. Schlag einen Workshop vor — als Thema, das du lernen willst, oder als Kurs, den du selbst geben möchtest. Wir schauen ihn an und schreiben ihn aus.", + "cta": "Workshop vorschlagen" }, "cta": { "title": "Workshop vorschlagen?", "subtitle": "Hast du eine Idee für einen Workshop? Wir freuen uns über deine Vorschläge für neue Themen und Inhalte.", "button": "Workshop vorschlagen" + }, + "meanwhile": { + "eyebrow": "SCHON JETZT VERFÜGBAR", + "title": "Kein Termin heisst nicht: nichts zu lernen", + "subtitle": "Zwei Dinge kannst du sofort nutzen — beide von uns gepflegt, beide kostenlos.", + "openSource": { + "title": "Open-Source-Alternativen", + "body": "43 freie Programme, die teure Software ersetzen — mit Einschätzung, wie ausgereift sie sind und wie aufwendig der Umstieg ist.", + "cta": "Alternativen durchsuchen" + }, + "linux": { + "title": "Linux einrichten", + "body": "Welche Distribution zu welchem Gerät passt, und wie du ein älteres Notebook wieder schnell bekommst.", + "cta": "Zur Anleitung" + } } }, "detail": { diff --git a/messages/en.json b/messages/en.json index aa4adcc9d..024a3ef23 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1832,13 +1832,29 @@ "clearFilters": "Reset filters" }, "emptyAll": { - "title": "No workshops scheduled at the moment", - "subtitle": "Check back soon!" + "title": "No date is scheduled yet", + "subtitle": "The programme grows out of proposals. Propose a workshop — either a topic you want to learn, or a course you would like to teach. We review it and put it on the calendar.", + "cta": "Propose a workshop" }, "cta": { "title": "Want to propose a workshop?", "subtitle": "Have an idea for a workshop? We welcome your suggestions for new topics and content.", "button": "Propose a workshop" + }, + "meanwhile": { + "eyebrow": "AVAILABLE ALREADY", + "title": "No date does not mean nothing to learn", + "subtitle": "Two things you can use right now — both maintained by us, both free.", + "openSource": { + "title": "Open-source alternatives", + "body": "43 free programs that replace expensive software, each with how mature it is and how much work the switch takes.", + "cta": "Browse the alternatives" + }, + "linux": { + "title": "Set up Linux", + "body": "Which distribution suits which machine, and how to make an older laptop fast again.", + "cta": "Read the guide" + } } }, "detail": { diff --git a/messages/es.json b/messages/es.json index 0384392cb..72cc96cdb 100644 --- a/messages/es.json +++ b/messages/es.json @@ -4280,13 +4280,29 @@ "clearFilters": "Restablecer filtros" }, "emptyAll": { - "title": "No hay talleres programados en este momento", - "subtitle": "¡Vuelve pronto!" + "title": "Todavía no hay ninguna fecha programada", + "subtitle": "El programa nace de las propuestas. Propón un taller: un tema que quieras aprender o un curso que quieras impartir. Lo revisamos y lo publicamos en el calendario.", + "cta": "Proponer un taller" }, "cta": { "title": "¿Quieres proponer un taller?", "subtitle": "¿Tienes una idea para un taller? Nos encantan las propuestas de nuevos temas y contenidos.", "button": "Proponer taller" + }, + "meanwhile": { + "eyebrow": "YA DISPONIBLE", + "title": "Sin fecha no significa sin nada que aprender", + "subtitle": "Dos cosas que puedes usar ahora mismo, ambas mantenidas por nosotros y gratuitas.", + "openSource": { + "title": "Alternativas de código abierto", + "body": "43 programas libres que sustituyen software caro, cada uno con su madurez y el esfuerzo que exige la migración.", + "cta": "Explorar las alternativas" + }, + "linux": { + "title": "Configurar Linux", + "body": "Qué distribución encaja con cada equipo y cómo volver a hacer rápido un portátil antiguo.", + "cta": "Leer la guía" + } } }, "detail": { diff --git a/messages/fr.json b/messages/fr.json index bcb5d87a0..1ad657a27 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -1831,13 +1831,29 @@ "clearFilters": "Réinitialiser les filtres" }, "emptyAll": { - "title": "Aucun atelier planifié actuellement", - "subtitle": "Revenez bientôt!" + "title": "Aucune date n'est encore programmée", + "subtitle": "Le programme naît des propositions. Proposez un atelier : un sujet que vous voulez apprendre, ou un cours que vous aimeriez donner. Nous l'examinons et le mettons au calendrier.", + "cta": "Proposer un atelier" }, "cta": { "title": "Proposer un atelier?", "subtitle": "Vous avez une idée d'atelier? Nous sommes ravis de recevoir vos suggestions de nouveaux thèmes et contenus.", "button": "Proposer un atelier" + }, + "meanwhile": { + "eyebrow": "DÉJÀ DISPONIBLE", + "title": "Pas de date ne veut pas dire rien à apprendre", + "subtitle": "Deux choses utilisables tout de suite — toutes deux entretenues par nous, toutes deux gratuites.", + "openSource": { + "title": "Alternatives open source", + "body": "43 logiciels libres qui remplacent des logiciels coûteux, avec leur maturité et l'effort que demande la migration.", + "cta": "Parcourir les alternatives" + }, + "linux": { + "title": "Installer Linux", + "body": "Quelle distribution convient à quelle machine, et comment rendre un portable ancien à nouveau rapide.", + "cta": "Lire le guide" + } } }, "detail": { diff --git a/messages/it.json b/messages/it.json index 468e4f75e..a6d93fe55 100644 --- a/messages/it.json +++ b/messages/it.json @@ -4280,13 +4280,29 @@ "clearFilters": "Reimposta filtri" }, "emptyAll": { - "title": "Al momento non ci sono workshop in programma", - "subtitle": "Torna presto!" + "title": "Non è ancora fissata nessuna data", + "subtitle": "Il programma nasce dalle proposte. Proponi un laboratorio: un tema che vuoi imparare, o un corso che vorresti tenere tu. Lo esaminiamo e lo mettiamo in calendario.", + "cta": "Proporre un laboratorio" }, "cta": { "title": "Vuoi proporre un workshop?", "subtitle": "Hai un'idea per un workshop? Siamo felici di ricevere i tuoi suggerimenti su nuovi temi e contenuti.", "button": "Proponi un workshop" + }, + "meanwhile": { + "eyebrow": "GIÀ DISPONIBILE", + "title": "Nessuna data non significa niente da imparare", + "subtitle": "Due cose che puoi usare subito, entrambe curate da noi e gratuite.", + "openSource": { + "title": "Alternative open source", + "body": "43 programmi liberi che sostituiscono software costoso, ognuno con la sua maturità e quanto lavoro richiede il passaggio.", + "cta": "Sfoglia le alternative" + }, + "linux": { + "title": "Configurare Linux", + "body": "Quale distribuzione si adatta a quale macchina e come rendere di nuovo veloce un portatile datato.", + "cta": "Leggi la guida" + } } }, "detail": { diff --git a/messages/ja.json b/messages/ja.json index 36dadddfe..bb8f4b7e3 100644 --- a/messages/ja.json +++ b/messages/ja.json @@ -4280,13 +4280,29 @@ "clearFilters": "フィルターをリセット" }, "emptyAll": { - "title": "現在予定されているワークショップはありません", - "subtitle": "またご確認ください!" + "title": "まだ日程は決まっていません", + "subtitle": "講座は提案から生まれます。学びたいテーマでも、自分が教えたい講座でも構いません。提案してください。こちらで検討して日程を組みます。", + "cta": "ワークショップを提案する" }, "cta": { "title": "ワークショップを提案しませんか?", "subtitle": "ワークショップのアイデアがありますか?新しいテーマや内容のご提案をお待ちしています。", "button": "ワークショップを提案" + }, + "meanwhile": { + "eyebrow": "いますぐ使えるもの", + "title": "日程がないことと、学ぶものがないことは違います", + "subtitle": "すぐに使える二つ。どちらも私たちが手入れしていて、どちらも無料です。", + "openSource": { + "title": "オープンソースの代替", + "body": "高価なソフトを置き換える 43 の自由なプログラム。それぞれの成熟度と、乗り換えにかかる手間つき。", + "cta": "代替を見る" + }, + "linux": { + "title": "Linux を設定する", + "body": "どの端末にどのディストリビューションが合うか、古いノートをまた速くする方法。", + "cta": "手順を読む" + } } }, "detail": { diff --git a/messages/ko.json b/messages/ko.json index 4ae5704e8..4fab10c0e 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -4280,13 +4280,29 @@ "clearFilters": "필터 초기화" }, "emptyAll": { - "title": "현재 예정된 워크숍이 없습니다", - "subtitle": "곧 다시 확인해 주세요!" + "title": "아직 잡힌 일정이 없습니다", + "subtitle": "과정은 제안에서 시작됩니다. 배우고 싶은 주제도 좋고, 직접 가르치고 싶은 강좌도 좋습니다. 제안해 주시면 검토해서 일정을 잡습니다.", + "cta": "워크숍 제안하기" }, "cta": { "title": "워크숍을 제안하시겠어요?", "subtitle": "워크숍 아이디어가 있으신가요? 새로운 주제와 내용에 대한 제안을 환영합니다.", "button": "워크숍 제안하기" + }, + "meanwhile": { + "eyebrow": "지금 바로 쓸 수 있는 것", + "title": "일정이 없다고 배울 것이 없는 건 아닙니다", + "subtitle": "지금 바로 쓸 수 있는 두 가지. 둘 다 저희가 관리하고, 둘 다 무료입니다.", + "openSource": { + "title": "오픈소스 대안", + "body": "비싼 소프트웨어를 대체하는 43개의 자유 프로그램. 각각의 성숙도와 전환에 드는 수고까지.", + "cta": "대안 살펴보기" + }, + "linux": { + "title": "리눅스 설치하기", + "body": "어떤 기기에 어떤 배포판이 맞는지, 오래된 노트북을 다시 빠르게 만드는 방법.", + "cta": "안내 읽기" + } } }, "detail": { diff --git a/messages/ru.json b/messages/ru.json index e4591d2f1..4d46d5247 100644 --- a/messages/ru.json +++ b/messages/ru.json @@ -1835,13 +1835,29 @@ "clearFilters": "Сбросить фильтры" }, "emptyAll": { - "title": "На данный момент воркшопы не запланированы", - "subtitle": "Загляните позже!" + "title": "Дата пока не назначена", + "subtitle": "Программа рождается из предложений. Предложите мастерскую — тему, которой хотите научиться, или курс, который хотели бы вести сами. Мы рассмотрим и поставим в календарь.", + "cta": "Предложить мастерскую" }, "cta": { "title": "Предложить воркшоп?", "subtitle": "У вас есть идея для воркшопа? Будем рады вашим предложениям новых тем и материалов.", "button": "Предложить воркшоп" + }, + "meanwhile": { + "eyebrow": "ДОСТУПНО УЖЕ СЕЙЧАС", + "title": "Нет даты — не значит, что нечему учиться", + "subtitle": "Две вещи, которыми можно пользоваться прямо сейчас: обе ведём мы, обе бесплатны.", + "openSource": { + "title": "Открытые альтернативы", + "body": "43 свободные программы, заменяющие дорогой софт, — с оценкой зрелости и того, насколько труден переход.", + "cta": "Посмотреть альтернативы" + }, + "linux": { + "title": "Настроить Linux", + "body": "Какой дистрибутив подходит какой машине и как снова сделать старый ноутбук быстрым.", + "cta": "Читать руководство" + } } }, "detail": { diff --git a/src/app/[locale]/workshops/WorkshopBrowseClient.tsx b/src/app/[locale]/workshops/WorkshopBrowseClient.tsx index ea4fe041e..9e7a0ef21 100644 --- a/src/app/[locale]/workshops/WorkshopBrowseClient.tsx +++ b/src/app/[locale]/workshops/WorkshopBrowseClient.tsx @@ -37,6 +37,19 @@ interface WorkshopBrowseClientProps { workshops: WorkshopWithInstances[] } +/** + * What evig can teach you TODAY, shown when nothing is scheduled. + * + * Structure here, strings in messages under `workshops.browse.meanwhile.`, + * paired by the stable id — the same rule the divisions and pillars follow. + * These are the only two teaching assets evig actually owns right now; do not + * add a third here unless the page behind it is real. + */ +const MEANWHILE_LINKS = [ + { id: 'openSource', href: '/services/open-source-solutions' }, + { id: 'linux', href: '/services/linux-open-source' }, +] as const + export default function WorkshopBrowseClient({ workshops }: WorkshopBrowseClientProps) { const t = useTranslations('workshops.browse') const tCat = useTranslations('workshops.categories') @@ -282,29 +295,80 @@ export default function WorkshopBrowseClient({ workshops }: WorkshopBrowseClient })} ) : ( -
- - - {categoryFilter !== 'all' || levelFilter !== 'all' - ? t('emptyFiltered.title') - : t('emptyAll.title')} - -

- {categoryFilter !== 'all' || levelFilter !== 'all' - ? t('emptyFiltered.subtitle') - : t('emptyAll.subtitle')} -

- {(categoryFilter !== 'all' || levelFilter !== 'all') && ( - +
+
+ + + {categoryFilter !== 'all' || levelFilter !== 'all' + ? t('emptyFiltered.title') + : t('emptyAll.title')} + +

+ {categoryFilter !== 'all' || levelFilter !== 'all' + ? t('emptyFiltered.subtitle') + : t('emptyAll.subtitle')} +

+ {categoryFilter !== 'all' || levelFilter !== 'all' ? ( + + ) : ( + // The proposal pipeline is real and complete — propose, an + // admin approves, and the approve route inserts the workshop + // AND a scheduled instance, which lands on this page. Saying + // "Schau bald wieder vorbei" here told people to wait for + // something they could start themselves, with a working + // "Workshop vorschlagen" button directly above it. + + {t('emptyAll.cta')} + + + )} +
+ + {/* Nothing scheduled is not the same as nothing to learn. + This page is where pillar 4 (retraining) sends people, and it + answered them with "Schau bald wieder vorbei!" — a dead end on + a core promise. These are the two teaching assets evig owns + today and can hand over immediately: a 43-entry open-source + alternatives registry and a Linux setup guide with a distro + matrix. Shown only when the calendar is empty; once workshops + exist, the workshops are the answer. */} + {categoryFilter === 'all' && levelFilter === 'all' && ( +
+
+ {t('meanwhile.eyebrow')} + + {t('meanwhile.title')} + +

+ {t('meanwhile.subtitle')} +

+
+ +
+ {MEANWHILE_LINKS.map((item) => ( + + + {t(`meanwhile.${item.id}.title` as never)} + +

+ {t(`meanwhile.${item.id}.body` as never)} +

+ + {t(`meanwhile.${item.id}.cta` as never)} → + + + ))} +
+
)}
)}