Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
eff5d11
feat: update school and level management with new constants and dynam…
BIA3IA Aug 29, 2026
0089d23
feat: add new icons and update card media components to support strin…
BIA3IA Aug 29, 2026
a9a1bed
feat: enhance group and course components with dynamic links and impr…
BIA3IA Aug 30, 2026
1d7d741
feat: refactor group filtering to use cohort labels and update label …
BIA3IA Aug 30, 2026
c8e7f1a
feat: add SITE_LABEL constant and update group filtering to include s…
BIA3IA Aug 31, 2026
03819fc
feat: update group search functionality to use dynamic platform icons…
BIA3IA Aug 31, 2026
06662ee
feat: update @polinetwork/backend dependency to version 0.17.2
BIA3IA Aug 31, 2026
95b9623
feat: add role and aria-label attributes for accessibility in group s…
BIA3IA Aug 31, 2026
6d112b5
feat: add dynamic export to group pages for improved rendering
BIA3IA Aug 31, 2026
314229d
Merge branch 'main' into groups
BIA3IA Aug 31, 2026
76c6a26
feat: enhance header and footer components for logo-only layout support
BIA3IA Aug 31, 2026
b481775
feat: update MobileLayout to support logo-only layout with interactiv…
BIA3IA Aug 31, 2026
aa6c707
feat: refactor card media components and improve group handling logic
BIA3IA Aug 31, 2026
60ff618
feat: update group keys to ensure uniqueness across platforms in grou…
BIA3IA Aug 31, 2026
e210aaa
feat: update @polinetwork/backend dependency to version 0.18.0
BIA3IA Aug 31, 2026
955581c
feat: update Logo component to accept href prop and adjust MobileLayo…
BIA3IA Aug 31, 2026
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"check:fix": "biome check --write --unsafe"
},
"dependencies": {
"@polinetwork/backend": "^0.16.4",
"@polinetwork/backend": "^0.18.0",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added public/icons/architettura.png
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/icons/civile.png
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/icons/design.png
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/icons/ingegneria.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/app/groups/didattica/[school]/[level]/[course]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { GroupsResult } from "@/components/groups/groups-result"

export const dynamic = "force-dynamic"

export default async function DidatticaGroupsResultPage({
params,
}: {
Expand Down
2 changes: 2 additions & 0 deletions src/app/groups/didattica/[school]/[level]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { CourseStep } from "@/components/groups/course-step"
import type { CourseSearchParams } from "@/components/groups/types"

export const dynamic = "force-dynamic"

export default async function DidatticaCoursePage({
params,
searchParams,
Expand Down
2 changes: 2 additions & 0 deletions src/app/groups/extra/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ExtraGroups } from "@/components/groups/extra-groups"

export const dynamic = "force-dynamic"

export default function ExtraGroupsPage() {
return <ExtraGroups />
}
20 changes: 14 additions & 6 deletions src/components/card-course-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@ export const cardCourseGroupVariants = cva(

export function CardCourseGroup({
groupName,
hasWhatsapp = true,
waLink,
iconWhatsApp: IconWhatsApp = FaWhatsapp,
hasTelegram = true,
tgLink,
iconTelegram: IconTelegram = LiaTelegramPlane,
secondary = false,
stacked = false,
}: {
groupName: string
hasWhatsapp?: boolean
waLink?: string
iconWhatsApp?: IconType
hasTelegram?: boolean
tgLink?: string
iconTelegram?: IconType
stacked?: boolean
} & VariantProps<typeof cardCourseGroupVariants>) {
Expand All @@ -48,8 +48,16 @@ export function CardCourseGroup({
{groupName}
</CardTitle>
<div className={cn("flex items-center gap-3", stacked ? "md:contents" : "contents")}>
{hasWhatsapp && <CardAction gradient={false} className={actionClassName} icon={IconWhatsApp} iconSize="sm" />}
{hasTelegram && <CardAction gradient={false} className={actionClassName} icon={IconTelegram} iconSize="sm" />}
{waLink && (
<a href={waLink} target="_blank" rel="noopener noreferrer" aria-label={`${groupName} su WhatsApp`}>
<CardAction gradient={false} className={actionClassName} icon={IconWhatsApp} iconSize="sm" />
</a>
)}
{tgLink && (
<a href={tgLink} target="_blank" rel="noopener noreferrer" aria-label={`${groupName} su Telegram`}>
<CardAction gradient={false} className={actionClassName} icon={IconTelegram} iconSize="sm" />
</a>
)}
</div>
</Card>
)
Expand Down
32 changes: 19 additions & 13 deletions src/components/card-course.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,36 @@ import { Card, CardAction, CardContent } from "./ui/card"
export function CardCourse({
courseName,
iconLocation: IconLocation = FiMapPin,
location = "Milano Leonardo",
location,
iconLanguage: IconLanguage = FiGlobe,
language = "ITA",
language,
iconSelect: IconSelect = FiChevronRight,
}: {
courseName: string
iconLocation?: IconType
location?: "Milano Leonardo" | "Milano Bovisa" | "Cremona" | "Lecco" | "Mantova" | "Piacenza" | "Xi'an" //Magari poi si mette solo string come tipo nel caso si pullino da un DB
location?: string
iconLanguage?: IconType
language?: "ITA" | "ENG" //Idem
language?: string
iconSelect?: IconType
}) {
return (
<Card className="typo-body-large flex h-fit w-full flex-row items-center gap-2 px-5 py-3.75 font-normal leading-6 tracking-[0.03125rem] sm:gap-0">
<div className="flex min-w-0 flex-1 flex-col gap-1 sm:contents">
<CardContent className="min-w-0 truncate sm:basis-1/3">{courseName}</CardContent>
<div className="flex min-w-0 items-center gap-4 sm:contents">
<CardContent className="flex min-w-0 items-center gap-1.25 sm:basis-1/3">
<CardAction icon={IconLocation} iconSize="xs" /> <span className="truncate">{location}</span>
</CardContent>
<CardContent className="flex min-w-0 items-center gap-1.25 sm:basis-1/3">
<CardAction icon={IconLanguage} iconSize="xs" /> <span className="truncate">{language}</span>
</CardContent>
</div>
<CardContent className="min-w-0 truncate sm:flex-1">{courseName}</CardContent>
{(location || language) && (
<div className="flex min-w-0 items-center gap-4 sm:contents">
{location && (
<CardContent className="flex min-w-0 items-center gap-1.25 sm:basis-1/3">
<CardAction icon={IconLocation} iconSize="xs" /> <span className="truncate">{location}</span>
</CardContent>
)}
{language && (
<CardContent className="flex min-w-0 items-center gap-1.25 sm:basis-1/3">
<CardAction icon={IconLanguage} iconSize="xs" /> <span className="truncate">{language}</span>
</CardContent>
)}
</div>
)}
</div>
<CardContent className="flex shrink-0 items-center">
<IconSelect className="h-4 w-4 text-text-primary" strokeWidth={2} />
Expand Down
14 changes: 12 additions & 2 deletions src/components/card-icon/basic-card-media.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import { cn } from "@/lib/utils"
import { GradientIcon, type GradientIconType } from "../gradient-icon"
import { CardMediaImage } from "./card-media-image"
import type { ResponsiveCardSize } from "./types"
import { getIconSizeClasses } from "./utils"

export function BasicCardMedia({ icon: Icon, size }: { icon: GradientIconType; size: ResponsiveCardSize }) {
return <GradientIcon icon={Icon} className={getIconSizeClasses(size)} />
export function BasicCardMedia({ icon, size }: { icon: GradientIconType | string; size: ResponsiveCardSize }) {
if (typeof icon === "string") {
return (
<div className={cn("relative", getIconSizeClasses(size))}>
<CardMediaImage src={icon} fill sizes="180px" className="object-contain" />
</div>
)
}

return <GradientIcon icon={icon} className={getIconSizeClasses(size)} />
}
15 changes: 15 additions & 0 deletions src/components/card-icon/card-media-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Image from "next/image"

type CardMediaImageProps = { src: string; className: string } & (
| { fill: true; sizes: string; width?: never; height?: never }
| { fill?: false; sizes?: never; width: number; height: number }
)

/** Renders a card-media icon that's a plain image URL, as opposed to a `GradientIconType` component. */
export function CardMediaImage(props: CardMediaImageProps) {
const { src, className } = props
if (props.fill) {
return <Image src={src} alt="" fill sizes={props.sizes} className={className} />
}
return <Image src={src} alt="" width={props.width} height={props.height} className={className} />
}
9 changes: 7 additions & 2 deletions src/components/card-icon/description-card-media.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { cn } from "@/lib/utils"
import { GradientIcon, type GradientIconType } from "../gradient-icon"
import { CardMediaImage } from "./card-media-image"
import type { ResponsiveCardSize } from "./types"
import { getIconSizeClasses } from "./utils"

export function DescriptionCardMedia({ icon: Icon, size }: { icon: GradientIconType; size: ResponsiveCardSize }) {
export function DescriptionCardMedia({ icon, size }: { icon: GradientIconType | string; size: ResponsiveCardSize }) {
return (
<div className={cn("relative", getIconSizeClasses(size))}>
<GradientIcon icon={Icon} className="h-full w-full" />
{typeof icon === "string" ? (
<CardMediaImage src={icon} fill sizes="180px" className="object-contain" />
) : (
<GradientIcon icon={icon} className="h-full w-full" />
)}
</div>
)
}
29 changes: 22 additions & 7 deletions src/components/card-icon/inline-card-media.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
import { cn } from "@/lib/utils"
import type { GradientIconType } from "../gradient-icon"
import { CardMediaImage } from "./card-media-image"
import type { ResponsiveCardSize } from "./types"
import { getIconSizeClasses, getInlineContainerClasses } from "./utils"

export function InlineCardMedia({ icon: Icon, size }: { icon: GradientIconType; size: ResponsiveCardSize }) {
export function InlineCardMedia({ icon, size }: { icon: GradientIconType | string; size: ResponsiveCardSize }) {
const wrapperClassName = cn(
"flex shrink-0 items-center justify-center rounded-lg bg-blue-tertiary",
getInlineContainerClasses(size)
)

if (typeof icon === "string") {
return (
<div className={wrapperClassName}>
<CardMediaImage
src={icon}
width={24}
height={24}
className={cn(getIconSizeClasses("inline"), "object-contain")}
/>
</div>
)
}

const Icon = icon
return (
<div
className={cn(
"flex shrink-0 items-center justify-center rounded-lg bg-blue-tertiary",
getInlineContainerClasses(size)
)}
>
<div className={wrapperClassName}>
<Icon className={cn(getIconSizeClasses("inline"), "text-text-primary")} />
</div>
)
Expand Down
2 changes: 1 addition & 1 deletion src/components/card-icon/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export type ResponsiveCardSize = CardSize | ResponsiveCardSizeConfig

export type SharedCardProps = {
title: string
icon: GradientIconType
icon: GradientIconType | string
size?: ResponsiveCardSize
align?: CardAlign
href?: string
Expand Down
7 changes: 5 additions & 2 deletions src/components/conditional-footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

import { usePathname } from "next/navigation"
import { Footer } from "@/components/footer"
import { matchesAnyRoutePrefix } from "@/utils/route-match"

const HIDDEN_ON = ["/matricole/guida"]
const HIDDEN_EXACT = ["/matricole/guida"]
const HIDDEN_PREFIXES = ["/groups"]

export function ConditionalFooter() {
const pathname = usePathname()
if (HIDDEN_ON.includes(pathname)) return null
const hidden = HIDDEN_EXACT.includes(pathname) || matchesAnyRoutePrefix(pathname, HIDDEN_PREFIXES)
if (hidden) return null
return <Footer />
}
37 changes: 11 additions & 26 deletions src/components/groups/constants.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,29 @@
import type { Course, Level, School } from "@/components/groups/types"
import type { Level, School } from "@/components/groups/types"

export const SCHOOLS: School[] = [
{ slug: "architettura", name: "Scuola di Architettura" },
{ slug: "ingegneria-industriale-informazione", name: "Scuola di Ingegneria Industriale e dell'Informazione" },
{ slug: "auic", name: "Scuola di Architettura, Urbanistica, Ingegneria delle Costruzioni" },
{ slug: "ingegneria-civile-ambientale-territoriale", name: "Scuola di Ingegneria Civile, Ambientale e Territoriale" },
{ slug: "design", name: "Scuola di Design" },
{ slug: "ingegneria", name: "Scuole di Ingegneria" },
]

export const LEVELS: Level[] = [
{ slug: "triennale", name: "Triennale" },
{ slug: "magistrale", name: "Magistrale" },
]

export const COURSES: Record<string, Course[]> = {
"design/triennale": [
{ slug: "design-prodotto", name: "Design del Prodotto", location: "Milano Bovisa", language: "ITA" },
{ slug: "design-comunicazione", name: "Design della Comunicazione", location: "Milano Bovisa", language: "ITA" },
{ slug: "design-moda", name: "Design della Moda", location: "Milano Bovisa", language: "ITA" },
],
}
const CICLO_UNICO_LEVEL: Level = { slug: "ciclo-unico", name: "Ciclo Unico" }

const SCHOOLS_WITH_CICLO_UNICO = new Set(["auic"])

export function getSchool(slug: string) {
return SCHOOLS.find((school) => school.slug === slug)
}

export function getLevel(slug: string) {
return LEVELS.find((level) => level.slug === slug)
}

export function getCourses(school: string, level: string) {
return COURSES[`${school}/${level}`] ?? []
}

export function getCourse(school: string, level: string, slug: string) {
return getCourses(school, level).find((course) => course.slug === slug)
export function getLevelsForSchool(schoolSlug: string): Level[] {
return SCHOOLS_WITH_CICLO_UNICO.has(schoolSlug) ? [...LEVELS, CICLO_UNICO_LEVEL] : LEVELS
}

export function getCourseFilters(school: string, level: string) {
const courses = getCourses(school, level)
return {
campuses: [...new Set(courses.map((course) => course.location))],
languages: [...new Set(courses.map((course) => course.language))],
}
export function getLevel(schoolSlug: string, slug: string) {
return getLevelsForSchool(schoolSlug).find((level) => level.slug === slug)
}
10 changes: 6 additions & 4 deletions src/components/groups/course-filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@

const ALL = "all"

export type FilterOption = { value: string; label: string }

type FilterSelectProps = {
label: string
value: string
options: string[]
options: FilterOption[]
onChange: (value: string) => void
}

Expand All @@ -22,16 +24,16 @@ function FilterSelect({ label, value, options, onChange }: FilterSelectProps) {
<SelectContent>
<SelectItem value={ALL}>{label}</SelectItem>
{options.map((option) => (
<SelectItem key={option} value={option}>
{option}
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
)
}

export function CourseFilters({ campuses, languages }: { campuses: string[]; languages: string[] }) {
export function CourseFilters({ campuses, languages }: { campuses: FilterOption[]; languages: FilterOption[] }) {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
Expand Down
Loading
Loading