diff --git a/README.md b/README.md index d16f92d..17abc01 100644 --- a/README.md +++ b/README.md @@ -21,3 +21,17 @@ backend accepts only while it allows legacy anonymous calls. Set all of these or ## Contributing If you are contributing, you may check [GUIDES.md](./GUIDES.md) + +## Freshman content + +`/matricole` reads resources, Telegram links and the five selected FAQs from `web.freshman.getContent` +through the existing server-only website client. The route renders at request time so admin changes are +visible on subsequent page loads. Titles, introductory text, group headings and responsive layout remain fixed. +Cards resolve their icons from the backend's allowlist; empty destinations remain non-clickable placeholders. +FAQ answers are rendered as text in the existing accordion. If content cannot be loaded, the page keeps its +intro and displays an unavailable message instead of static fallback data. + +This integration requires a running backend with the Freshman router and migration. The pinned +`@polinetwork/backend@1.4.0` does not yet include the Freshman type contract: once the updated package is +published, update the dependency and lockfile, then run `pnpm check`, `pnpm typecheck` and `pnpm build`. +Until then, typecheck reports the missing contract. No temporary declaration patches are included. diff --git a/package.json b/package.json index 9912bfa..fd03ad5 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ }, "dependencies": { "@polinetwork/auth-kit": "^1.1.0", - "@polinetwork/backend": "^2.3.0", + "@polinetwork/backend": "^4.0.0", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d3ca832..bf77cb0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^1.1.0 version: 1.1.0 '@polinetwork/backend': - specifier: ^2.3.0 - version: 2.3.0 + specifier: ^4.0.0 + version: 4.0.0 '@radix-ui/react-accordion': specifier: ^1.2.12 version: 1.2.12(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) @@ -460,8 +460,8 @@ packages: resolution: {integrity: sha512-SAf/cVSF4wW7UqP3EP/3IFPAJgYTL9po9qOXcJ9OvbRPCi9X4bmMTDDHYzuPCKMAQkHybuPbI6tzcKFBekw0Ag==} engines: {node: '>=20.19'} - '@polinetwork/backend@2.3.0': - resolution: {integrity: sha512-FqoJ0qL5OHP6UZ3Hrm9DfwgQaAWjHWwsThF9Mu18oO265OL41+ZS9JqN+ILejTE3P8ohOutXj/0NcV4VP4IuJA==} + '@polinetwork/backend@4.0.0': + resolution: {integrity: sha512-hKvjzwMMm8qZJojEhIGOA9sHg29xlw2Tu/XH2Wlq9xdkyucEFHp5QNLzGWccjzjHd8fja4qF20OQJppaFrEVtA==} '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -2132,7 +2132,7 @@ snapshots: jose: 6.2.12 zod: 4.2.1 - '@polinetwork/backend@2.3.0': {} + '@polinetwork/backend@4.0.0': {} '@radix-ui/number@1.1.1': {} diff --git a/src/app/matricole/page.tsx b/src/app/matricole/page.tsx index 9d1e36d..1ac2712 100644 --- a/src/app/matricole/page.tsx +++ b/src/app/matricole/page.tsx @@ -2,18 +2,30 @@ import type { Metadata } from "next" import { FAQsPage } from "@/components/matricole/faqs" import { MatricoleGuides } from "@/components/matricole/guides" import { MatricoleIntro } from "@/components/matricole/intro" +import { getFreshmanContent } from "@/queries/freshman" export const metadata: Metadata = { title: "Matricole", description: "Risorse utili, guide e strumenti per le matricole del Politecnico di Milano.", } -export default function MatricolePage() { +export const dynamic = "force-dynamic" + +export default async function MatricolePage() { + const content = await getFreshmanContent() return (
- - - + + {content ? ( + <> + + + + ) : ( +

+ Le risorse per le matricole non sono disponibili al momento. Riprova più tardi. +

+ )}
) } diff --git a/src/components/matricole/cards.ts b/src/components/matricole/cards.ts new file mode 100644 index 0000000..20889ae --- /dev/null +++ b/src/components/matricole/cards.ts @@ -0,0 +1,45 @@ +import type { IconType } from "react-icons" +import { + FiBarChart2, + FiBell, + FiBook, + FiDollarSign, + FiFolder, + FiHome, + FiMap, + FiMonitor, + FiRadio, + FiRefreshCw, + FiRepeat, + FiShoppingCart, + FiUserCheck, + FiUsers, +} from "react-icons/fi" +import type { CardIconProps } from "@/components/card-icon/types" +import type { FreshmanCard } from "@/queries/types" + +const icons: Record = { + chart: FiBarChart2, + monitor: FiMonitor, + book: FiBook, + map: FiMap, + news: FiRadio, + money: FiDollarSign, + folder: FiFolder, + repeat: FiRepeat, + refresh: FiRefreshCw, + bell: FiBell, + user: FiUserCheck, + users: FiUsers, + cart: FiShoppingCart, + home: FiHome, +} + +export function freshmanCardProps(card: FreshmanCard): CardIconProps { + return { + title: card.title, + description: card.description, + icon: icons[card.icon], + href: card.href || undefined, + } +} diff --git a/src/components/matricole/constants.ts b/src/components/matricole/constants.ts index bc30d15..a5abaa1 100644 --- a/src/components/matricole/constants.ts +++ b/src/components/matricole/constants.ts @@ -1,169 +1,13 @@ -import { - FiBarChart2, - FiBell, - FiBook, - FiCpu, - FiDollarSign, - FiFolder, - FiHome, - FiMap, - FiMonitor, - FiRadio, - FiRefreshCw, - FiRepeat, - FiShoppingCart, - FiUserCheck, - FiUsers, -} from "react-icons/fi" -import type { AccordionListItem } from "../accordion-list/types" - -export const resources = [ - { - title: "Graduatorie", - description: "Risultati storici e soglie di accesso per i vari corsi di laurea.", - icon: FiBarChart2, - href: "https://rankings.polinetwork.org/", - }, - { - title: "Progetto TOL", - description: "Informazioni sul test di ingresso (TOL) e su come prepararsi al meglio.", - icon: FiMonitor, - href: "https://tol.polinetwork.org/", - }, -] as const - -export const guides = [ - { - title: "Guida della matricola", - description: "Dalla prima iscrizione alla vita in campus: il manuale completo per ogni nuovo studente.", - icon: FiBook, - href: "/matricole/guida", - }, - { - title: "Mappa Microonde", - description: "Dove scaldare il pranzo nei vari campus.", - icon: FiMap, - }, - { - title: "Corsi a Scelta", - description: "Consigli su come comporre il Piano di Studi.", - icon: FiBook, - }, - { - title: "News", - description: "Le ultime novità dagli studenti del Politecnico.", - icon: FiRadio, - }, - { - title: "Borsa di Studio", - description: "Requisiti e scadenze per le agevolazioni.", - icon: FiDollarSign, - }, -] as const +import { FiCpu } from "react-icons/fi" export const hub = { title: "Telegram Hub", description: "Unisciti alla più grande rete di studenti del Poli su Telegram. Gruppi specifici per ogni esigenza, dai libri alla ricerca della casa.", - cards: [ - { - title: "Generale", - icon: FiBell, - href: "https://t.me/PoliGruppo", - }, - { - title: "Matricole", - icon: FiUserCheck, - href: "https://t.me/polimatricole", - }, - { - title: "Ripetizioni", - icon: FiUsers, - href: "https://t.me/joinchat/l10OF3vF6fEzOTRk", - }, - { - title: "PoliBook", - icon: FiBook, - href: "https://t.me/joinchat/6nmuXXhAOr1mYTJk", - }, - { - title: "DSU", - icon: FiDollarSign, - href: "https://t.me/joinchat/4kO9DtAiTVM0NTU0", - }, - { - title: "Mercatino", - icon: FiShoppingCart, - href: "https://t.me/mercatinopolimi", - }, - { - title: "Case", - icon: FiHome, - href: "https://t.me/joinchat/jmpNOitFNFw1MzJk", - }, - { - title: "Consigli PC", - icon: FiMonitor, - href: "https://t.me/joinchat/oD7NGpl0W6VjYmI0", - }, - ], -} - -export const materiali = { - title: "Materiali", - description: - "Il più grande archivio didattico creato dagli studenti per gli studenti del Politecnico di Milano. Migliaia di appunti e temi d’esame.", - icon: FiFolder, - href: "https://polinetwork.org/it/materials/view/", } export const techTools = { title: "Tech Tools", description: "Una serie di Tools sviluppati dagli studenti del Politecnico di Milano.", icon: FiCpu, - cards: [ - { - title: "Sync Orario", - icon: FiRepeat, - href: "https://bebora.github.io/polimi-schedule-js/", - }, - { - title: "WeBeep Sync", - icon: FiRefreshCw, - href: "https://github.com/toto04/webeep-sync", - }, - ], } - -export const accordionItems: AccordionListItem[] = [ - { - value: "item-1", - trigger: "Le lezioni della prima settimana si tengono regolarmente?", - content: - "Spesso, la prima settimana, i Professori utilizzano le ore destinate alle esercitazioni per qualche ora di lezione in più: saranno quindi i docenti a specificare come verranno utilizzate queste ore. Dunque, le esercitazioni non sono da considerarsi annullate, salvo diversa comunicazione da parte del docente o dell'esercitatore.", - }, - { - value: "item-2", - trigger: "Come faccio ad accedere ai materiali del corso?", - content: - "I materiali dei corsi sono disponibili su Beep (il portale e-learning del Politecnico). Puoi accedervi con le tue credenziali di Ateneo (codice persona e password). Ogni docente carica le proprie slide, esercizi e risorse direttamente sulla pagina del corso.", - }, - { - value: "item-3", - trigger: "Come funziona la prenotazione agli esami?", - content: - "La prenotazione agli esami avviene tramite il portale Online Services (servizionline.polimi.it). Devi prenotarti entro la scadenza indicata per ogni appello. Ricordati di verificare i prerequisiti necessari prima di iscriverti a un esame.", - }, - { - value: "item-4", - trigger: "Cos'è il codice persona e dove lo trovo?", - content: - "Il codice persona è il tuo identificativo univoco al Politecnico, composto da 6 cifre. Lo trovi nella lettera di ammissione o nella mail di benvenuto ricevuta dopo l'immatricolazione. Serve per accedere a tutti i servizi online dell'Ateneo.", - }, - { - value: "item-5", - trigger: "Come posso contattare PoliNetwork?", - content: - "Puoi contattare PoliNetwork tramite i nostri canali Telegram o attraverso il sito ufficiale. Siamo un'associazione studentesca e offriamo supporto, risorse e una community per tutti gli studenti del Politecnico.", - }, -] diff --git a/src/components/matricole/faqs.tsx b/src/components/matricole/faqs.tsx index 9643cab..3b6b432 100644 --- a/src/components/matricole/faqs.tsx +++ b/src/components/matricole/faqs.tsx @@ -1,8 +1,12 @@ -"use client" import AccordionList from "@/components/accordion-list" -import { accordionItems } from "./constants" +import type { FreshmanContent } from "@/queries/types" -export function FAQsPage() { +export function FAQsPage({ faqs }: { faqs: FreshmanContent["faqs"] }) { + const accordionItems = faqs.map((faq) => ({ + value: String(faq.faqId), + trigger: faq.titleIt, + content: faq.descriptionIt, + })) return (

diff --git a/src/components/matricole/guides.tsx b/src/components/matricole/guides.tsx index d952b32..73ad750 100644 --- a/src/components/matricole/guides.tsx +++ b/src/components/matricole/guides.tsx @@ -1,8 +1,13 @@ import { CardIcon } from "@/components/card-icon" +import type { FreshmanContent } from "@/queries/types" import { CardGroup } from "../card-group" -import { guides, hub, materiali, techTools } from "./constants" +import { freshmanCardProps } from "./cards" +import { hub, techTools } from "./constants" -export function MatricoleGuides() { +export function MatricoleGuides({ content }: { content: FreshmanContent }) { + const guides = content.resources.filter((card) => card.group === "guides") + const materials = content.resources.filter((card) => card.group === "materials") + const tools = content.resources.filter((card) => card.group === "tools") return (
@@ -10,37 +15,41 @@ export function MatricoleGuides() {
{/* Hidden in mobile */} - + {guides.slice(0, 1).map((guide) => ( + + ))}
{guides.slice(1).map((guide) => ( - + ))}
- {hub.cards.map((card) => ( - + {content.telegram.map((card) => ( + ))}
{/* hidden in mobile */} - + {materials.map((material) => ( + + ))}
- {techTools.cards.map((card) => ( - + {tools.map((card) => ( + ))}
diff --git a/src/components/matricole/intro.tsx b/src/components/matricole/intro.tsx index a0c7363..6152cdb 100644 --- a/src/components/matricole/intro.tsx +++ b/src/components/matricole/intro.tsx @@ -1,7 +1,8 @@ import { CardIcon } from "@/components/card-icon" -import { resources } from "./constants" +import type { FreshmanContent } from "@/queries/types" +import { freshmanCardProps } from "./cards" -export function MatricoleIntro() { +export function MatricoleIntro({ resources }: { resources: FreshmanContent["resources"] }) { return (
@@ -15,9 +16,17 @@ export function MatricoleIntro() {
- {resources.map((resource) => ( - - ))} + {resources + .filter((resource) => resource.group === "featured") + .map((resource) => ( + + ))}
diff --git a/src/queries/freshman.ts b/src/queries/freshman.ts new file mode 100644 index 0000000..cdc9244 --- /dev/null +++ b/src/queries/freshman.ts @@ -0,0 +1,12 @@ +"use server" + +import { trpc } from "@/lib/backend" + +export async function getFreshmanContent() { + try { + return await trpc.web.freshman.getContent.query() + } catch (error) { + console.error("Failed to fetch freshman content", error) + return null + } +} diff --git a/src/queries/types.ts b/src/queries/types.ts index 0c23918..e4f99b2 100644 --- a/src/queries/types.ts +++ b/src/queries/types.ts @@ -7,3 +7,6 @@ export type BrokenGroupLinkReportInput = Extract export type GroupLinkReportResult = { ok: true } | { ok: false; error: "UNKNOWN" } + +export type FreshmanContent = ApiOutput["web"]["freshman"]["getContent"] +export type FreshmanCard = FreshmanContent["resources"][number]