diff --git a/src/app/groups/didattica/[school]/[level]/[course]/page.tsx b/src/app/groups/didattica/[school]/[level]/[course]/page.tsx
new file mode 100644
index 0000000..ac593d4
--- /dev/null
+++ b/src/app/groups/didattica/[school]/[level]/[course]/page.tsx
@@ -0,0 +1,10 @@
+import { GroupsResult } from "@/components/groups/groups-result"
+
+export default async function DidatticaGroupsResultPage({
+ params,
+}: {
+ params: Promise<{ school: string; level: string; course: string }>
+}) {
+ const { school, level, course } = await params
+ return
+}
diff --git a/src/app/groups/didattica/[school]/[level]/page.tsx b/src/app/groups/didattica/[school]/[level]/page.tsx
new file mode 100644
index 0000000..251d1a8
--- /dev/null
+++ b/src/app/groups/didattica/[school]/[level]/page.tsx
@@ -0,0 +1,14 @@
+import { CourseStep } from "@/components/groups/course-step"
+import type { CourseSearchParams } from "@/components/groups/types"
+
+export default async function DidatticaCoursePage({
+ params,
+ searchParams,
+}: {
+ params: Promise<{ school: string; level: string }>
+ searchParams: Promise
+}) {
+ const { school, level } = await params
+ const { campus, lang } = await searchParams
+ return
+}
diff --git a/src/app/groups/didattica/[school]/page.tsx b/src/app/groups/didattica/[school]/page.tsx
new file mode 100644
index 0000000..28beb6b
--- /dev/null
+++ b/src/app/groups/didattica/[school]/page.tsx
@@ -0,0 +1,6 @@
+import { LevelStep } from "@/components/groups/level-step"
+
+export default async function DidatticaLevelPage({ params }: { params: Promise<{ school: string }> }) {
+ const { school } = await params
+ return
+}
diff --git a/src/app/groups/didattica/page.tsx b/src/app/groups/didattica/page.tsx
new file mode 100644
index 0000000..e944d36
--- /dev/null
+++ b/src/app/groups/didattica/page.tsx
@@ -0,0 +1,5 @@
+import { SchoolStep } from "@/components/groups/school-step"
+
+export default function DidatticaWizard() {
+ return
+}
diff --git a/src/app/groups/extra/page.tsx b/src/app/groups/extra/page.tsx
new file mode 100644
index 0000000..532abf0
--- /dev/null
+++ b/src/app/groups/extra/page.tsx
@@ -0,0 +1,5 @@
+import { ExtraGroups } from "@/components/groups/extra-groups"
+
+export default function ExtraGroupsPage() {
+ return
+}
diff --git a/src/app/groups/page.tsx b/src/app/groups/page.tsx
new file mode 100644
index 0000000..941e5be
--- /dev/null
+++ b/src/app/groups/page.tsx
@@ -0,0 +1,51 @@
+import { FiBook, FiStar } from "react-icons/fi"
+import { CardIcon } from "@/components/card-icon"
+import { GroupSearch } from "@/components/home/group-search"
+
+const groups = [
+ {
+ title: "Gruppi Didattici",
+ caption: "Ingegneria, Architettura, Design, e tutti i gruppi del tuo corso di studi.",
+ href: "/groups/didattica",
+ icon: FiBook,
+ cta: "Inizia",
+ },
+ {
+ title: "Gruppi Extra",
+ caption: "Affitti, mercatino, eventi, hobby e tutto ciò che riguarda la vita studentesca.",
+ href: "/groups/extra",
+ icon: FiStar,
+ cta: "Esplora",
+ },
+] as const
+
+export default function Home() {
+ return (
+
+
+
+
+ Groups
+
+
+
+
+
+ {groups.map((group) => (
+
+ ))}
+
+
+
+ )
+}
diff --git a/src/components/button-icon.tsx b/src/components/button-icon.tsx
index 62ef264..052fe41 100644
--- a/src/components/button-icon.tsx
+++ b/src/components/button-icon.tsx
@@ -1,25 +1,27 @@
-import type { ReactNode } from "react"
+import type { ComponentProps } from "react"
import type { IconType } from "react-icons"
-import { Button, type ButtonSizes } from "./ui/button"
+import { Button } from "./ui/button"
+
+type ButtonIconProps = ComponentProps & {
+ icon: IconType
+ text?: string
+ iconPosition?: "left" | "right"
+ iconClassName?: string
+}
export function ButtonIcon({
- variant = "primary",
icon: Icon,
- children,
+ text,
iconPosition = "left",
+ iconClassName,
size,
-}: {
- variant?: "primary" | "tertiary" | "tertiaryBlur"
- icon: IconType
- children: ReactNode
- iconPosition?: "left" | "right"
- size: ButtonSizes
-}) {
+ ...props
+}: ButtonIconProps) {
return (
-
)}
+
+ {cta && (
+
+ {cta}
+
+ )}
)}
diff --git a/src/components/card-icon/types.ts b/src/components/card-icon/types.ts
index f4ff7b3..3f0f9b4 100644
--- a/src/components/card-icon/types.ts
+++ b/src/components/card-icon/types.ts
@@ -27,5 +27,6 @@ export type SharedCardProps = {
}
export type CardIconProps = SharedCardProps & {
+ cta?: string
description?: ReactNode
}
diff --git a/src/components/card-path-selection.tsx b/src/components/card-path-selection.tsx
index f615c3e..41356b5 100644
--- a/src/components/card-path-selection.tsx
+++ b/src/components/card-path-selection.tsx
@@ -2,9 +2,17 @@ import type { IconType } from "react-icons"
import { BsBook } from "react-icons/bs"
import { Card, CardAction, CardContent } from "./ui/card"
-export function CardPathSelection({ caption, icon: Icon = BsBook }: { caption: string; icon?: IconType }) {
+export function CardPathSelection({
+ caption,
+ icon: Icon = BsBook,
+ className,
+}: {
+ caption: string
+ icon?: IconType
+ className?: string
+}) {
return (
-
+
{caption}
diff --git a/src/components/groups/constants.ts b/src/components/groups/constants.ts
new file mode 100644
index 0000000..38f8ad6
--- /dev/null
+++ b/src/components/groups/constants.ts
@@ -0,0 +1,44 @@
+import type { Course, Level, School } from "@/components/groups/types"
+
+export const SCHOOLS: School[] = [
+ { slug: "architettura", name: "Scuola di Architettura" },
+ { 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 = {
+ "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" },
+ ],
+}
+
+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 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))],
+ }
+}
diff --git a/src/components/groups/course-filters.tsx b/src/components/groups/course-filters.tsx
new file mode 100644
index 0000000..123a197
--- /dev/null
+++ b/src/components/groups/course-filters.tsx
@@ -0,0 +1,67 @@
+"use client"
+
+import { usePathname, useRouter, useSearchParams } from "next/navigation"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+
+const ALL = "all"
+
+type FilterSelectProps = {
+ label: string
+ value: string
+ options: string[]
+ onChange: (value: string) => void
+}
+
+function FilterSelect({ label, value, options, onChange }: FilterSelectProps) {
+ return (
+
+ )
+}
+
+export function CourseFilters({ campuses, languages }: { campuses: string[]; languages: string[] }) {
+ const router = useRouter()
+ const pathname = usePathname()
+ const searchParams = useSearchParams()
+
+ const update = (key: string, value: string) => {
+ const params = new URLSearchParams(searchParams.toString())
+ if (value === ALL) params.delete(key)
+ else params.set(key, value)
+ const query = params.toString()
+ router.replace(query ? `${pathname}?${query}` : pathname)
+ }
+
+ return (
+
+
+ update("campus", value)}
+ />
+
+
+ update("lang", value)}
+ />
+
+
+ )
+}
diff --git a/src/components/groups/course-step.tsx b/src/components/groups/course-step.tsx
new file mode 100644
index 0000000..7f4df44
--- /dev/null
+++ b/src/components/groups/course-step.tsx
@@ -0,0 +1,56 @@
+import Link from "next/link"
+import { notFound } from "next/navigation"
+import { FiChevronRight } from "react-icons/fi"
+import { CardCourse } from "@/components/card-course"
+import { getCourseFilters, getCourses, getLevel, getSchool } from "@/components/groups/constants"
+import { CourseFilters } from "@/components/groups/course-filters"
+import { WizardShell } from "@/components/groups/wizard-shell"
+import { stepHref } from "../../utils/step-href"
+
+export function CourseStep({
+ school: schoolSlug,
+ level,
+ campus,
+ lang,
+}: {
+ school: string
+ level: string
+ campus?: string
+ lang?: string
+}) {
+ const school = getSchool(schoolSlug)
+ const currentLevel = getLevel(level)
+ if (!school || !currentLevel) notFound()
+
+ const { campuses, languages } = getCourseFilters(schoolSlug, level)
+ const courses = getCourses(schoolSlug, level).filter(
+ (course) => (!campus || course.location === campus) && (!lang || course.language === lang)
+ )
+
+ return (
+ }
+ >
+
+ {courses.length === 0 && (
+
Nessun corso corrisponde ai filtri.
+ )}
+ {courses.map((course) => (
+
+
+
+ ))}
+
+
+ )
+}
diff --git a/src/components/groups/extra-groups.tsx b/src/components/groups/extra-groups.tsx
new file mode 100644
index 0000000..08b7ecc
--- /dev/null
+++ b/src/components/groups/extra-groups.tsx
@@ -0,0 +1,31 @@
+import Link from "next/link"
+import { FiArrowLeft } from "react-icons/fi"
+import { CardCourseGroup } from "@/components/card-course-group"
+
+// TODO: placeholder da cambiare
+const EXTRA_GROUPS_PLACEHOLDER = ["Affitti", "Mercatino", "Eventi", "Hobby"]
+
+export function ExtraGroups() {
+ return (
+
+
+
+
+
+
+
Gruppi Extra
+
+
+
+
+ {EXTRA_GROUPS_PLACEHOLDER.map((name) => (
+
+ ))}
+
+
+ )
+}
diff --git a/src/components/groups/groups-result.tsx b/src/components/groups/groups-result.tsx
new file mode 100644
index 0000000..984c248
--- /dev/null
+++ b/src/components/groups/groups-result.tsx
@@ -0,0 +1,65 @@
+import Link from "next/link"
+import { notFound } from "next/navigation"
+import { FiArrowLeft, FiX } from "react-icons/fi"
+import { CardCourseGroup } from "@/components/card-course-group"
+import { getCourse, getLevel, getSchool } from "@/components/groups/constants"
+import { stepHref } from "../../utils/step-href"
+
+// TODO: placeholder da cambiare
+const FIRST_YEAR_GROUPS_PLACEHOLDER = Array.from({ length: 7 }, (_, i) => `Sezione P${i + 1}`)
+
+export function GroupsResult({ school: schoolSlug, level, course }: { school: string; level: string; course: string }) {
+ const school = getSchool(schoolSlug)
+ const currentCourse = getCourse(schoolSlug, level, course)
+ const currentLevel = getLevel(level)
+ if (!school || !currentCourse || !currentLevel) notFound()
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ Gruppi del Primo Anno
+
+
+ {FIRST_YEAR_GROUPS_PLACEHOLDER.length} Gruppi
+
+
+
+ {FIRST_YEAR_GROUPS_PLACEHOLDER.map((name) => (
+
+ ))}
+
+
+
+ )
+}
diff --git a/src/components/groups/level-step.tsx b/src/components/groups/level-step.tsx
new file mode 100644
index 0000000..28d96d8
--- /dev/null
+++ b/src/components/groups/level-step.tsx
@@ -0,0 +1,30 @@
+import Link from "next/link"
+import { notFound } from "next/navigation"
+import { FiBook } from "react-icons/fi"
+import { CardPathSelection } from "@/components/card-path-selection"
+import { getSchool, LEVELS } from "@/components/groups/constants"
+import { WizardShell } from "@/components/groups/wizard-shell"
+import { stepHref } from "../../utils/step-href"
+
+export function LevelStep({ school: schoolSlug }: { school: string }) {
+ const school = getSchool(schoolSlug)
+ if (!school) notFound()
+
+ return (
+
+
+ {LEVELS.map((level) => (
+
+
+
+ ))}
+
+
+ )
+}
diff --git a/src/components/groups/school-step.tsx b/src/components/groups/school-step.tsx
new file mode 100644
index 0000000..4c053fb
--- /dev/null
+++ b/src/components/groups/school-step.tsx
@@ -0,0 +1,35 @@
+import type { IconType } from "react-icons"
+import { FiBox, FiFeather, FiPenTool } from "react-icons/fi"
+import { CardIcon } from "@/components/card-icon"
+import { SCHOOLS } from "@/components/groups/constants"
+import { WizardShell } from "@/components/groups/wizard-shell"
+import { stepHref } from "../../utils/step-href"
+
+const SCHOOL_ICONS: Record = {
+ architettura: FiPenTool,
+ design: FiFeather,
+ ingegneria: FiBox,
+}
+
+export function SchoolStep() {
+ return (
+
+
+ {SCHOOLS.map((school) => (
+
+ ))}
+
+
+ )
+}
diff --git a/src/components/groups/types.ts b/src/components/groups/types.ts
new file mode 100644
index 0000000..184edfb
--- /dev/null
+++ b/src/components/groups/types.ts
@@ -0,0 +1,11 @@
+export type Campus = "Milano Leonardo" | "Milano Bovisa" | "Cremona" | "Lecco" | "Mantova" | "Piacenza" | "Xi'an"
+export type Language = "ITA" | "ENG"
+
+export type School = { slug: string; name: string }
+export type Level = { slug: string; name: string }
+export type Course = { slug: string; name: string; location: Campus; language: Language }
+
+export type CourseSearchParams = {
+ campus?: string
+ lang?: string
+}
diff --git a/src/components/groups/wizard-shell.tsx b/src/components/groups/wizard-shell.tsx
new file mode 100644
index 0000000..c0a7e75
--- /dev/null
+++ b/src/components/groups/wizard-shell.tsx
@@ -0,0 +1,96 @@
+"use client"
+
+import { useRouter } from "next/navigation"
+import type { ReactNode } from "react"
+import { FiArrowLeft, FiX } from "react-icons/fi"
+import { ButtonIcon } from "@/components/button-icon"
+import { Glass } from "@/components/glass"
+import { cn } from "@/lib/utils"
+
+const STEPS = ["Scuola", "Triennale/​Magistrale", "Corso"] as const
+
+type WizardShellProps = {
+ activeStep: number
+ title: string
+ caption?: string
+ captionPosition?: "above" | "below"
+ backHref?: string
+ action?: ReactNode
+ children: ReactNode
+}
+
+export function WizardShell({
+ activeStep,
+ title,
+ caption,
+ captionPosition = "below",
+ backHref,
+ action,
+ children,
+}: WizardShellProps) {
+ const router = useRouter()
+
+ return (
+
+
+
+
(backHref ? router.replace(backHref) : router.back())}
+ />
+
+
+ {STEPS.map((label, index) => (
+ -
+ {label}
+
+ ))}
+
+
+ router.push("/groups")}
+ />
+
+
+
+
+
+ {captionPosition === "above" && caption && (
+
{caption}
+ )}
+
+ {title}
+
+ {captionPosition === "below" && caption && (
+
{caption}
+ )}
+
+ {action &&
{action}
}
+
+
{children}
+
+
+
+ )
+}
diff --git a/src/components/home/group-search.tsx b/src/components/home/group-search.tsx
index 53d3f41..a8f21f9 100644
--- a/src/components/home/group-search.tsx
+++ b/src/components/home/group-search.tsx
@@ -37,8 +37,8 @@ export function GroupSearch() {
}
type="text"
- placeholder="Find your group"
- aria-label="Find your group"
+ placeholder="Corso, Scuola, Interessi..."
+ aria-label="Corso, Scuola, Interessi..."
containerClassName="w-full"
className="typo-body-medium"
value={query}
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
index c6cdca9..6f81785 100644
--- a/src/components/ui/button.tsx
+++ b/src/components/ui/button.tsx
@@ -28,6 +28,7 @@ const buttonVariants = cva(
"bg-button-tertiary hover:bg-button-tertiary/90 text-text-accent-lightbg typo-label-large rounded-buttonsM",
tertiaryBlur:
"bg-blue-tertiary-blur hover:bg-blue-tertiary-blur/90 text-text-accent-lightbg typo-headline-small rounded-buttonsM",
+ glass: "bg-background-blur backdrop-blur-md rounded-buttonsL",
outline:
"border bg-white shadow-xs hover:bg-slate-100 hover:text-slate-900 dark:bg-slate-200/30 dark:border-slate-200 dark:hover:bg-slate-200/50 dark:bg-slate-950 dark:hover:bg-slate-800 dark:hover:text-slate-50 dark:dark:bg-slate-800/30 dark:dark:border-slate-800 dark:dark:hover:bg-slate-800/50",
link: "text-slate-900 underline-offset-4 underline dark:text-slate-50",
diff --git a/src/styles/typography.css b/src/styles/typography.css
index 3724904..0101893 100644
--- a/src/styles/typography.css
+++ b/src/styles/typography.css
@@ -68,6 +68,10 @@
@apply font-red-hat font-medium text-[0.6875rem]/[16px];
}
+@utility typo-label-tiny {
+ @apply font-red-hat font-medium text-[0.59rem]/[15px];
+}
+
/* Title */
@utility typo-title-large {
@apply font-poppins font-normal text-[1.375rem]/[28px];
diff --git a/src/utils/step-href.ts b/src/utils/step-href.ts
new file mode 100644
index 0000000..bcb6949
--- /dev/null
+++ b/src/utils/step-href.ts
@@ -0,0 +1,4 @@
+export function stepHref(params: { school?: string; level?: string; course?: string }) {
+ const segments = [params.school, params.level, params.course].filter(Boolean)
+ return ["/groups/didattica", ...segments].join("/")
+}