diff --git a/.env.example b/.env.example index eceaa2d..10a4ba3 100644 --- a/.env.example +++ b/.env.example @@ -9,3 +9,4 @@ # When adding additional environment variables, the schema in "/src/env.js" # should be updated accordingly. +BACKEND_URL=http://localhost:3000 diff --git a/package.json b/package.json index 56af356..dfc40cd 100644 --- a/package.json +++ b/package.json @@ -14,13 +14,17 @@ "check:fix": "biome check --write --unsafe" }, "dependencies": { + "@polinetwork/backend": "^0.15.18", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-tabs": "^1.1.13", "@t3-oss/env-nextjs": "^0.13.10", + "@tanstack/react-pacer": "^0.22.1", "@tanstack/react-table": "^8.21.3", + "@trpc/client": "11.5.1", + "@trpc/next": "11.5.1", "babel-plugin-react-compiler": "^1.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -34,6 +38,7 @@ "react-dom": "^19.2.3", "react-hook-form": "^7.68.0", "react-icons": "^5.6.0", + "superjson": "^2.2.6", "tailwind-merge": "^3.4.0", "tailwindcss-animate": "^1.0.7", "zod": "^4.2.1" @@ -41,6 +46,7 @@ "devDependencies": { "@biomejs/biome": "2.2.5", "@tailwindcss/postcss": "^4.1.18", + "@trpc/server": "11.5.1", "@types/node": "^24.10.4", "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 69236ac..278f936 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@polinetwork/backend': + specifier: ^0.15.18 + version: 0.15.18 '@radix-ui/react-accordion': specifier: ^1.2.12 version: 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -26,9 +29,18 @@ importers: '@t3-oss/env-nextjs': specifier: ^0.13.10 version: 0.13.10(typescript@5.9.3)(zod@4.2.1) + '@tanstack/react-pacer': + specifier: ^0.22.1 + version: 0.22.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-table': specifier: ^8.21.3 version: 8.21.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@trpc/client': + specifier: 11.5.1 + version: 11.5.1(@trpc/server@11.5.1(typescript@5.9.3))(typescript@5.9.3) + '@trpc/next': + specifier: 11.5.1 + version: 11.5.1(@trpc/client@11.5.1(@trpc/server@11.5.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.5.1(typescript@5.9.3))(next@15.5.18(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3) babel-plugin-react-compiler: specifier: ^1.0.0 version: 1.0.0 @@ -68,6 +80,9 @@ importers: react-icons: specifier: ^5.6.0 version: 5.6.0(react@19.2.3) + superjson: + specifier: ^2.2.6 + version: 2.2.6 tailwind-merge: specifier: ^3.4.0 version: 3.4.0 @@ -84,6 +99,9 @@ importers: '@tailwindcss/postcss': specifier: ^4.1.18 version: 4.1.18 + '@trpc/server': + specifier: 11.5.1 + version: 11.5.1(typescript@5.9.3) '@types/node': specifier: ^24.10.4 version: 24.10.4 @@ -429,6 +447,9 @@ packages: cpu: [x64] os: [win32] + '@polinetwork/backend@0.15.18': + resolution: {integrity: sha512-gT4TRMWIFNhwQi2mOx1OiH9HTP9BoorZHSvTti8vTCpk0qgKlL39ntAHQvFtDq1RbJ7rvS+DRd8fRBZkLVtOOw==} + '@radix-ui/number@1.1.1': resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} @@ -1283,6 +1304,28 @@ packages: '@tailwindcss/postcss@4.1.18': resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==} + '@tanstack/devtools-event-client@0.4.3': + resolution: {integrity: sha512-OZI6QyULw0FI0wjgmeYzCIfbgPsOEzwJtCpa69XrfLMtNXLGnz3d/dIabk7frg0TmHo+Ah49w5I4KC7Tufwsvw==} + engines: {node: '>=18'} + hasBin: true + + '@tanstack/pacer@0.21.1': + resolution: {integrity: sha512-hB01dd4rlsYcTCNP7wK186jgAe6K5qimgM1Y5Jtvz+9PUaILvpmeLLjmQNUNSO1l23lIt+CeQR6mO1mjlPvRtQ==} + engines: {node: '>=18'} + + '@tanstack/react-pacer@0.22.1': + resolution: {integrity: sha512-CenQqK0GluSPIrnsG1yuD7w5uMSQ/4lI9AcGEFxBrRd66r260boWcYRIsS5+eHtXb238FoZYhKmJPGlhRzmHRw==} + engines: {node: '>=18'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + '@tanstack/react-store@0.11.0': + resolution: {integrity: sha512-tX4YXh3PDkmpvGQWkWqKpzs/MSqbtuwY9dWdWhtV9Q50PmO+jOkUKIWIX4G85dwt7lxdHLXsiaEKPdKmC8F41w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@tanstack/react-table@8.21.3': resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} engines: {node: '>=12'} @@ -1290,10 +1333,41 @@ packages: react: '>=16.8' react-dom: '>=16.8' + '@tanstack/store@0.11.0': + resolution: {integrity: sha512-WlzzCt3xi0G6pCAJu1U+2jiECwabETDpQDi3hfkFZvJii9AuZqEKbOiVarX1/bWhTNjU486yQtJCCasi/0q+Cw==} + '@tanstack/table-core@8.21.3': resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} engines: {node: '>=12'} + '@trpc/client@11.5.1': + resolution: {integrity: sha512-7I6JJ1I1lxv3S87ht3FAIZi0XxQa7hnQ9K+Oo5BH7cGO8ZtWe9Ftq6ItdkuDfpsnsRPcR2h158AMWbNs/iptqg==} + peerDependencies: + '@trpc/server': 11.5.1 + typescript: '>=5.7.2' + + '@trpc/next@11.5.1': + resolution: {integrity: sha512-MWb7Jqa0XYx0z2he7Eq7HSILc2z9oDqUtsbWz+S6LWCemewRSRzmG8UuoiZm+ICEoRa452RmqeQH1EYiPCWTUg==} + peerDependencies: + '@tanstack/react-query': ^5.59.15 + '@trpc/client': 11.5.1 + '@trpc/react-query': 11.5.1 + '@trpc/server': 11.5.1 + next: '*' + react: '>=16.8.0' + react-dom: '>=16.8.0' + typescript: '>=5.7.2' + peerDependenciesMeta: + '@tanstack/react-query': + optional: true + '@trpc/react-query': + optional: true + + '@trpc/server@11.5.1': + resolution: {integrity: sha512-KIDzHRS5m8U1ncPwjgtOtPWK9lNO0kYL7b+lnvKXRqowSAQIEC/z6y7g/dkt4Aqv3DKI/STLydt2/afrP1QrxQ==} + peerDependencies: + typescript: '>=5.7.2' + '@types/node@24.10.4': resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==} @@ -1331,6 +1405,10 @@ packages: react: ^18 || ^19 || ^19.0.0-rc react-dom: ^18 || ^19 || ^19.0.0-rc + copy-anything@4.0.5: + resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} + engines: {node: '>=18'} + cross-env@10.1.0: resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==} engines: {node: '>=20'} @@ -1379,6 +1457,10 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + is-what@5.5.0: + resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} + engines: {node: '>=18'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1610,6 +1692,10 @@ packages: babel-plugin-macros: optional: true + superjson@2.2.6: + resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} + engines: {node: '>=16'} + tailwind-merge@3.4.0: resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} @@ -1886,6 +1972,8 @@ snapshots: '@next/swc-win32-x64-msvc@15.5.18': optional: true + '@polinetwork/backend@0.15.18': {} + '@radix-ui/number@1.1.1': {} '@radix-ui/primitive@1.1.3': {} @@ -2743,14 +2831,55 @@ snapshots: postcss: 8.5.10 tailwindcss: 4.1.18 + '@tanstack/devtools-event-client@0.4.3': {} + + '@tanstack/pacer@0.21.1': + dependencies: + '@tanstack/devtools-event-client': 0.4.3 + '@tanstack/store': 0.11.0 + + '@tanstack/react-pacer@0.22.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@tanstack/pacer': 0.21.1 + '@tanstack/react-store': 0.11.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + + '@tanstack/react-store@0.11.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@tanstack/store': 0.11.0 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + use-sync-external-store: 1.6.0(react@19.2.3) + '@tanstack/react-table@8.21.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@tanstack/table-core': 8.21.3 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) + '@tanstack/store@0.11.0': {} + '@tanstack/table-core@8.21.3': {} + '@trpc/client@11.5.1(@trpc/server@11.5.1(typescript@5.9.3))(typescript@5.9.3)': + dependencies: + '@trpc/server': 11.5.1(typescript@5.9.3) + typescript: 5.9.3 + + '@trpc/next@11.5.1(@trpc/client@11.5.1(@trpc/server@11.5.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.5.1(typescript@5.9.3))(next@15.5.18(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@5.9.3)': + dependencies: + '@trpc/client': 11.5.1(@trpc/server@11.5.1(typescript@5.9.3))(typescript@5.9.3) + '@trpc/server': 11.5.1(typescript@5.9.3) + next: 15.5.18(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + typescript: 5.9.3 + + '@trpc/server@11.5.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + '@types/node@24.10.4': dependencies: undici-types: 7.16.0 @@ -2793,6 +2922,10 @@ snapshots: - '@types/react' - '@types/react-dom' + copy-anything@4.0.5: + dependencies: + is-what: 5.5.0 + cross-env@10.1.0: dependencies: '@epic-web/invariant': 1.0.0 @@ -2835,6 +2968,8 @@ snapshots: graceful-fs@4.2.11: {} + is-what@5.5.0: {} + isexe@2.0.0: {} jiti@2.6.1: {} @@ -3094,6 +3229,10 @@ snapshots: client-only: 0.0.1 react: 19.2.3 + superjson@2.2.6: + dependencies: + copy-anything: 4.0.5 + tailwind-merge@3.4.0: {} tailwindcss-animate@1.0.7(tailwindcss@4.1.18): diff --git a/src/app/associations/page.tsx b/src/app/associations/page.tsx index e0553bb..34d89d0 100644 --- a/src/app/associations/page.tsx +++ b/src/app/associations/page.tsx @@ -1,176 +1,19 @@ -"use client" +import type { Metadata } from "next" +import { AssociationsList } from "@/components/associations/associations-list" +import { Hero } from "@/components/ui/hero" -import { - FiFacebook, - FiGithub, - FiGlobe, - FiInstagram, - FiLinkedin, - FiMail, - FiTwitch, - FiX, - FiYoutube, -} from "react-icons/fi" -import AccordionAssociation from "@/components/accordion-association" -import esnLogo from "../../../public/logos/esn.svg" - -const accordionItems = [ - { - value: "ESN", - name: "ESN", - logo: esnLogo, - content: - "Lorem ipsum dolor sit amet consectetur. Velit integer diam in id proin blandit fames id. Volutpat at vel risus non amet tortor. Potenti sit gravida donec lacinia et posuere faucibus. Elementum libero diam nullam ultricies mauris mauris erat porttitor. At morbi commodo nunc vulputate id odio pellentesque ipsum. Adipiscing at dictumst pulvinar mattis faucibus quisque donec convallis commodo. ", - links: [ - { - key: "Facebook", - href: "https://www.google.com", - icon: FiFacebook, - }, - { - key: "Instagram", - href: "https://www.google.com", - icon: FiInstagram, - }, - { - key: "Youtube", - href: "https://www.google.com", - icon: FiYoutube, - }, - // { - // key: "Discord", - // href: "https://www.google.com", - // icon: FiDiscord - // }, - { - key: "GitHub", - href: "https://www.google.com", - icon: FiGithub, - }, - // { - // key: "Telegram", - // href: "https://www.google.com", - // icon: FiTelegram - // }, - { - key: "Email", - href: "mailto:example@email.com", - icon: FiMail, - }, - { - key: "LinkedIn", - href: "https://www.google.com", - icon: FiLinkedin, - }, - { - key: "Twitch", - href: "https://www.google.com", - icon: FiTwitch, - }, - // { - // key: "Spotify", - // href: "https://www.google.com", - // icon: FiSpotify - // }, - { - key: "X", - href: "https://www.google.com", - icon: FiX, - }, - { - key: "Web", - href: "https://www.google.com", - icon: FiGlobe, - }, - ], - }, - { - value: "Lista Aperta", - name: "Lista Aperta", - logo: esnLogo, - content: - "Lorem ipsum dolor sit amet consectetur. Velit integer diam in id proin blandit fames id. Volutpat at vel risus non amet tortor. Potenti sit gravida donec lacinia et posuere faucibus. Elementum libero diam nullam ultricies mauris mauris erat porttitor. At morbi commodo nunc vulputate id odio pellentesque ipsum. Adipiscing at dictumst pulvinar mattis faucibus quisque donec convallis commodo. ", - links: [ - { - key: "Facebook", - href: "https://www.google.com", - icon: FiFacebook, - }, - ], - }, - { - value: "MESA", - name: "MESA", - logo: esnLogo, - content: - "Lorem ipsum dolor sit amet consectetur. Velit integer diam in id proin blandit fames id. Volutpat at vel risus non amet tortor. Potenti sit gravida donec lacinia et posuere faucibus. Elementum libero diam nullam ultricies mauris mauris erat porttitor. At morbi commodo nunc vulputate id odio pellentesque ipsum. Adipiscing at dictumst pulvinar mattis faucibus quisque donec convallis commodo. ", - links: [ - { - key: "Facebook", - href: "https://www.google.com", - icon: FiFacebook, - }, - ], - }, - { - value: "Polifonia", - name: "Polifonia", - logo: esnLogo, - content: - "Lorem ipsum dolor sit amet consectetur. Velit integer diam in id proin blandit fames id. Volutpat at vel risus non amet tortor. Potenti sit gravida donec lacinia et posuere faucibus. Elementum libero diam nullam ultricies mauris mauris erat porttitor. At morbi commodo nunc vulputate id odio pellentesque ipsum. Adipiscing at dictumst pulvinar mattis faucibus quisque donec convallis commodo. ", - links: [ - { - key: "Facebook", - href: "https://www.google.com", - icon: FiFacebook, - }, - ], - }, - { - value: "POLI.RADIO", - name: "POLI.RADIO", - logo: esnLogo, - content: - "Lorem ipsum dolor sit amet consectetur. Velit integer diam in id proin blandit fames id. Volutpat at vel risus non amet tortor. Potenti sit gravida donec lacinia et posuere faucibus. Elementum libero diam nullam ultricies mauris mauris erat porttitor. At morbi commodo nunc vulputate id odio pellentesque ipsum. Adipiscing at dictumst pulvinar mattis faucibus quisque donec convallis commodo. ", - links: [ - { - key: "Facebook", - href: "https://www.google.com", - icon: FiFacebook, - }, - ], - }, - { - value: "Studenti Indipendenti", - name: "Studenti Indipendenti", - logo: esnLogo, - content: - "Lorem ipsum dolor sit amet consectetur. Velit integer diam in id proin blandit fames id. Volutpat at vel risus non amet tortor. Potenti sit gravida donec lacinia et posuere faucibus. Elementum libero diam nullam ultricies mauris mauris erat porttitor. At morbi commodo nunc vulputate id odio pellentesque ipsum. Adipiscing at dictumst pulvinar mattis faucibus quisque donec convallis commodo. ", - links: [ - { - key: "Facebook", - href: "https://www.google.com", - icon: FiFacebook, - }, - ], - }, -] +export const metadata: Metadata = { + title: "Associazioni", + description: "Scopri le associazioni studentesche del Politecnico", +} export default function AssociationsPage() { return ( -
-
-
-

- Associazioni -

-

- Scopri le associazioni studentesche del Politecnico -

-
-
- -
+
+ + +
+
) diff --git a/src/app/faqs/page.tsx b/src/app/faqs/page.tsx index e3dbc0f..e05776f 100644 --- a/src/app/faqs/page.tsx +++ b/src/app/faqs/page.tsx @@ -1,110 +1,17 @@ -"use client" +import type { Metadata } from "next" +import { FaqsTabs } from "@/components/faqs/faqs-tabs" +import { Hero } from "@/components/ui/hero" -import { FiBookOpen, FiDollarSign, FiEdit, FiFileText, FiSend } from "react-icons/fi" -import AccordionList from "@/components/accordion-list" -import CalloutItem from "@/components/callout-item" -import TabsNavigation from "@/components/tabs" -import { Tabs } from "@/components/tabs/tabs" -import { TabsContent } from "@/components/tabs/tabs-content" - -const faqItems = [ - { - value: "tab1", - label: "Lezioni", - content: "Content for Tab 1", - icon: FiBookOpen, - }, - { - value: "tab2", - label: "Tasse", - content: "Content for Tab 2", - icon: FiDollarSign, - }, - { - value: "tab3", - label: "Esami", - content: "Content for Tab 3", - icon: FiEdit, - }, - { - value: "tab4", - label: "Piano di Studi", - content: "Content for Tab 4", - icon: FiFileText, - }, - { - value: "tab5", - label: "Mobilitá Internazionale", - content: ( -
-

Content for Tab 5

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore - magna aliqua. -

-
- ), - icon: FiSend, - }, -] - -const accordionItems = [ - { - value: "item-1", - trigger: "Per le lauree le lezioni sono sospese? ", - 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: "Per le lauree le lezioni sono sospese? ", - 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-3", - trigger: "Per le lauree le lezioni sono sospese? ", - 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-4", - trigger: "Per le lauree le lezioni sono sospese? ", - 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-5", - trigger: "Per le lauree le lezioni sono sospese? ", - 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.", - }, -] +export const metadata: Metadata = { + title: "FAQs", + description: "Risposte chiare per vivere al meglio la community", +} export default function FAQsPage() { return ( -
-
- -
-
- - - {faqItems.map((item) => ( - - {item.content} - - ))} - -
-
- -
+
+ +
) } diff --git a/src/app/matricole/page.tsx b/src/app/matricole/page.tsx index 38f6395..9d1e36d 100644 --- a/src/app/matricole/page.tsx +++ b/src/app/matricole/page.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next" import { FAQsPage } from "@/components/matricole/faqs" +import { MatricoleGuides } from "@/components/matricole/guides" import { MatricoleIntro } from "@/components/matricole/intro" export const metadata: Metadata = { @@ -11,6 +12,7 @@ export default function MatricolePage() { return (
+
) diff --git a/src/app/projects/page.tsx b/src/app/projects/page.tsx index c0a666f..f97afe0 100644 --- a/src/app/projects/page.tsx +++ b/src/app/projects/page.tsx @@ -3,6 +3,11 @@ import { CommunityNews } from "@/components/projects/community-news" import { Deprecated } from "@/components/projects/deprecated" import { Upload } from "@/components/projects/upload" +export const metadata = { + title: "Progetti", + description: "Esplora e contribuisci ai progetti degli studenti", +} + export default function Home() { return (
diff --git a/src/assets/icons/telegram-fill.svg b/src/assets/icons/telegram-fill.svg new file mode 100644 index 0000000..39f0e0d --- /dev/null +++ b/src/assets/icons/telegram-fill.svg @@ -0,0 +1 @@ + diff --git a/src/components/accordion-association/index.tsx b/src/components/accordion-association/index.tsx index 17a7446..470f3cb 100644 --- a/src/components/accordion-association/index.tsx +++ b/src/components/accordion-association/index.tsx @@ -1,5 +1,3 @@ -"use client" - import Image from "next/image" import { GradientIcon } from "../gradient-icon" import { Accordion } from "./accordion" diff --git a/src/components/associations/associations-list.tsx b/src/components/associations/associations-list.tsx new file mode 100644 index 0000000..e0f51ae --- /dev/null +++ b/src/components/associations/associations-list.tsx @@ -0,0 +1,84 @@ +"use client" + +import { + FiFacebook, + FiGithub, + FiGlobe, + FiInstagram, + FiLinkedin, + FiMail, + FiTwitch, + FiX, + FiYoutube, +} from "react-icons/fi" +import AccordionAssociation from "@/components/accordion-association" +import esnLogo from "../../../public/logos/esn.svg" + +const accordionItems = [ + { + value: "ESN", + name: "ESN", + logo: esnLogo, + content: + "Lorem ipsum dolor sit amet consectetur. Velit integer diam in id proin blandit fames id. Volutpat at vel risus non amet tortor. Potenti sit gravida donec lacinia et posuere faucibus. Elementum libero diam nullam ultricies mauris mauris erat porttitor. At morbi commodo nunc vulputate id odio pellentesque ipsum. Adipiscing at dictumst pulvinar mattis faucibus quisque donec convallis commodo. ", + links: [ + { key: "Facebook", href: "https://www.google.com", icon: FiFacebook }, + { key: "Instagram", href: "https://www.google.com", icon: FiInstagram }, + { key: "Youtube", href: "https://www.google.com", icon: FiYoutube }, + { key: "GitHub", href: "https://www.google.com", icon: FiGithub }, + { key: "Email", href: "mailto:example@email.com", icon: FiMail }, + { key: "LinkedIn", href: "https://www.google.com", icon: FiLinkedin }, + { key: "Twitch", href: "https://www.google.com", icon: FiTwitch }, + { key: "X", href: "https://www.google.com", icon: FiX }, + { key: "Web", href: "https://www.google.com", icon: FiGlobe }, + ], + }, + { + value: "Lista Aperta", + name: "Lista Aperta", + logo: esnLogo, + content: + "Lorem ipsum dolor sit amet consectetur. Velit integer diam in id proin blandit fames id. Volutpat at vel risus non amet tortor. Potenti sit gravida donec lacinia et posuere faucibus. Elementum libero diam nullam ultricies mauris mauris erat porttitor. At morbi commodo nunc vulputate id odio pellentesque ipsum. Adipiscing at dictumst pulvinar mattis faucibus quisque donec convallis commodo. ", + links: [{ key: "Facebook", href: "https://www.google.com", icon: FiFacebook }], + }, + { + value: "MESA", + name: "MESA", + logo: esnLogo, + content: + "Lorem ipsum dolor sit amet consectetur. Velit integer diam in id proin blandit fames id. Volutpat at vel risus non amet tortor. Potenti sit gravida donec lacinia et posuere faucibus. Elementum libero diam nullam ultricies mauris mauris erat porttitor. At morbi commodo nunc vulputate id odio pellentesque ipsum. Adipiscing at dictumst pulvinar mattis faucibus quisque donec convallis commodo. ", + links: [{ key: "Facebook", href: "https://www.google.com", icon: FiFacebook }], + }, + { + value: "Polifonia", + name: "Polifonia", + logo: esnLogo, + content: + "Lorem ipsum dolor sit amet consectetur. Velit integer diam in id proin blandit fames id. Volutpat at vel risus non amet tortor. Potenti sit gravida donec lacinia et posuere faucibus. Elementum libero diam nullam ultricies mauris mauris erat porttitor. At morbi commodo nunc vulputate id odio pellentesque ipsum. Adipiscing at dictumst pulvinar mattis faucibus quisque donec convallis commodo. ", + links: [{ key: "Facebook", href: "https://www.google.com", icon: FiFacebook }], + }, + { + value: "POLI.RADIO", + name: "POLI.RADIO", + logo: esnLogo, + content: + "Lorem ipsum dolor sit amet consectetur. Velit integer diam in id proin blandit fames id. Volutpat at vel risus non amet tortor. Potenti sit gravida donec lacinia et posuere faucibus. Elementum libero diam nullam ultricies mauris mauris erat porttitor. At morbi commodo nunc vulputate id odio pellentesque ipsum. Adipiscing at dictumst pulvinar mattis faucibus quisque donec convallis commodo. ", + links: [{ key: "Facebook", href: "https://www.google.com", icon: FiFacebook }], + }, + { + value: "Studenti Indipendenti", + name: "Studenti Indipendenti", + logo: esnLogo, + content: + "Lorem ipsum dolor sit amet consectetur. Velit integer diam in id proin blandit fames id. Volutpat at vel risus non amet tortor. Potenti sit gravida donec lacinia et posuere faucibus. Elementum libero diam nullam ultricies mauris mauris erat porttitor. At morbi commodo nunc vulputate id odio pellentesque ipsum. Adipiscing at dictumst pulvinar mattis faucibus quisque donec convallis commodo. ", + links: [{ key: "Facebook", href: "https://www.google.com", icon: FiFacebook }], + }, +] + +export function AssociationsList() { + return ( +
+ +
+ ) +} diff --git a/src/components/callout-item/types.ts b/src/components/callout-item/types.ts index e4d35f8..f78554d 100644 --- a/src/components/callout-item/types.ts +++ b/src/components/callout-item/types.ts @@ -1,6 +1,8 @@ +import type React from "react" + export type CalloutItemProps = { title: string href: string - buttonText: string + buttonText: React.ReactNode className?: string } diff --git a/src/components/card-caption.tsx b/src/components/card-caption.tsx index 60be7a1..0f04b10 100644 --- a/src/components/card-caption.tsx +++ b/src/components/card-caption.tsx @@ -1,3 +1,4 @@ +import Link from "next/link" import type { ReactNode } from "react" import type { IconType } from "react-icons" import { Card, CardAction, CardContent, CardHeader, CardTitle } from "./ui/card" @@ -7,16 +8,19 @@ export function CardCaption({ caption, icon, iconPosition = "right", + href, className, }: { title: string caption: ReactNode icon?: IconType iconPosition?: "top" | "right" + href?: string className?: string }) { return ( + {href && } diff --git a/src/components/card-group/header.tsx b/src/components/card-group/header.tsx new file mode 100644 index 0000000..a8fd10d --- /dev/null +++ b/src/components/card-group/header.tsx @@ -0,0 +1,26 @@ +import { cn } from "@/lib/utils" +import { GradientIcon } from "../gradient-icon" +import type { CardGroupProps } from "./types" + +export function CardGroupHeader({ + icon, + title, + description, + horizontal, +}: Pick) { + return ( +
+
+ {icon && } +

+ {title} +

+
+ {description && ( +

+ {description} +

+ )} +
+ ) +} diff --git a/src/components/card-group/index.tsx b/src/components/card-group/index.tsx new file mode 100644 index 0000000..84c994a --- /dev/null +++ b/src/components/card-group/index.tsx @@ -0,0 +1,22 @@ +import { Glass } from "@/components/glass" +import { cn } from "@/lib/utils" +import { CardGroupHeader } from "./header" +import type { CardGroupProps } from "./types" + +export function CardGroup({ icon, title, description, children, horizontal = false, className }: CardGroupProps) { + return ( + + {horizontal ? ( +
+ +
{children}
+
+ ) : ( + <> + +
{children}
+ + )} +
+ ) +} diff --git a/src/components/card-group/types.ts b/src/components/card-group/types.ts new file mode 100644 index 0000000..2ca0cf6 --- /dev/null +++ b/src/components/card-group/types.ts @@ -0,0 +1,11 @@ +import type { ReactNode } from "react" +import type { GradientIconType } from "../gradient-icon" + +export type CardGroupProps = { + icon?: GradientIconType + title: string + description?: string + children: ReactNode + horizontal?: boolean + className?: string +} diff --git a/src/components/card-icon/classes.ts b/src/components/card-icon/classes.ts index fb4256f..801eb48 100644 --- a/src/components/card-icon/classes.ts +++ b/src/components/card-icon/classes.ts @@ -6,6 +6,7 @@ export const ICON_SIZE_CLASSES: SizeClassMap = { sm: "h-14 w-14", md: "h-32 w-32", lg: "h-44 w-44", + inline: "h-6 w-6", } export const CARD_PADDING_WITHOUT_DESCRIPTION: SizeClassMap = { @@ -14,6 +15,7 @@ export const CARD_PADDING_WITHOUT_DESCRIPTION: SizeClassMap = { sm: "px-8 py-4", md: "p-8", lg: "p-8", + inline: "p-6", } export const CARD_PADDING_WITH_DESCRIPTION: SizeClassMap = { @@ -22,6 +24,7 @@ export const CARD_PADDING_WITH_DESCRIPTION: SizeClassMap = { sm: "p-8", md: "p-8", lg: "p-8", + inline: "p-6", } export const CONTENT_GAP_CLASSES: SizeClassMap = { @@ -30,6 +33,7 @@ export const CONTENT_GAP_CLASSES: SizeClassMap = { sm: "gap-2", md: "gap-6", lg: "gap-6", + inline: "gap-4", } export const TITLE_SIZE_CLASSES: SizeClassMap = { @@ -38,4 +42,14 @@ export const TITLE_SIZE_CLASSES: SizeClassMap = { sm: "typo-headline-medium", md: "typo-headline-medium", lg: "typo-headline-medium", + inline: "typo-title-large", +} + +export const INLINE_CONTAINER_SIZE_CLASSES: SizeClassMap = { + compact: "h-12 w-12", + xs: "h-10 w-10", + sm: "h-12 w-12", + md: "h-14 w-14", + lg: "h-16 w-16", + inline: "h-10 w-10", } diff --git a/src/components/card-icon/index.tsx b/src/components/card-icon/index.tsx index 1ad086c..a38598f 100644 --- a/src/components/card-icon/index.tsx +++ b/src/components/card-icon/index.tsx @@ -3,30 +3,18 @@ import { cn } from "@/lib/utils" import { BasicCardMedia } from "./basic-card-media" import { DescriptionCardMedia } from "./description-card-media" import { CardHoverBackground } from "./hover-background" +import { InlineCardMedia } from "./inline-card-media" import type { CardIconProps } from "./types" -import { getCardPaddingClasses, getContentGapClasses, getTitleSizeClasses } from "./utils" +import { getAlignmentClasses, getCardPaddingClasses, getContentGapClasses, getTitleSizeClasses } from "./utils" export function CardIcon(props: CardIconProps) { const { title, icon, size = "md", href, hoverEffect = false, align = "center", className } = props const description = "description" in props ? props.description : undefined const Root = href ? "a" : "div" const isDescriptionCard = Boolean(description) - const isStartAligned = align === "start" + const isInlineAligned = align === "inline" const isCompactDescriptionCard = isDescriptionCard && size === "compact" - const contentAlignmentClass = isDescriptionCard - ? isStartAligned - ? "items-start justify-start text-left" - : "justify-between" - : isStartAligned - ? "items-start justify-center text-left" - : "items-center justify-center text-center" - const textAlignmentClass = isDescriptionCard - ? isStartAligned - ? "items-start gap-5 text-left" - : "gap-2 text-left" - : isStartAligned - ? "items-start text-left" - : "items-center text-center" + const { contentClass, textClass, iconWrapClass } = getAlignmentClasses(align, isDescriptionCard) return ( {hoverEffect && } -
-
- {isDescriptionCard ? ( - - ) : ( - - )} + {isInlineAligned ? ( +
+
+ +

+ {title} +

+
+ {description &&

{description}

}
- -
-

+
+ {isDescriptionCard ? ( + + ) : ( + )} - > - {title} -

- {description && ( -

+ +

+

- {description} -

- )} + {title} +

+ {description && ( +

+ {description} +

+ )} +
-
+ )} ) diff --git a/src/components/card-icon/inline-card-media.tsx b/src/components/card-icon/inline-card-media.tsx new file mode 100644 index 0000000..be18ca5 --- /dev/null +++ b/src/components/card-icon/inline-card-media.tsx @@ -0,0 +1,17 @@ +import { cn } from "@/lib/utils" +import type { GradientIconType } from "../gradient-icon" +import type { ResponsiveCardSize } from "./types" +import { getIconSizeClasses, getInlineContainerClasses } from "./utils" + +export function InlineCardMedia({ icon: Icon, size }: { icon: GradientIconType; size: ResponsiveCardSize }) { + return ( +
+ +
+ ) +} diff --git a/src/components/card-icon/types.ts b/src/components/card-icon/types.ts index 54a333f..1ac84a5 100644 --- a/src/components/card-icon/types.ts +++ b/src/components/card-icon/types.ts @@ -1,8 +1,8 @@ import type { GradientIconType } from "@/components/gradient-icon" -export type CardSize = "compact" | "xs" | "sm" | "md" | "lg" +export type CardSize = "compact" | "xs" | "sm" | "md" | "lg" | "inline" export type CardBreakpoint = "base" | "sm" | "md" | "lg" -export type CardAlign = "center" | "start" +export type CardAlign = "center" | "start" | "inline" export type SizeClassMap = Record diff --git a/src/components/card-icon/utils.ts b/src/components/card-icon/utils.ts index 156adac..25b75ba 100644 --- a/src/components/card-icon/utils.ts +++ b/src/components/card-icon/utils.ts @@ -3,10 +3,11 @@ import { CARD_PADDING_WITHOUT_DESCRIPTION, CONTENT_GAP_CLASSES, ICON_SIZE_CLASSES, + INLINE_CONTAINER_SIZE_CLASSES, TITLE_SIZE_CLASSES, } from "./classes" -import type { CardBreakpoint, ResponsiveCardSize, SizeClassMap } from "./types" +import type { CardAlign, CardBreakpoint, ResponsiveCardSize, SizeClassMap } from "./types" const BREAKPOINTS: CardBreakpoint[] = ["base", "sm", "md", "lg"] @@ -61,3 +62,27 @@ export function getContentGapClasses(size: ResponsiveCardSize) { export function getTitleSizeClasses(size: ResponsiveCardSize) { return resolveResponsiveClasses(size, TITLE_SIZE_CLASSES) } + +export function getInlineContainerClasses(size: ResponsiveCardSize) { + return resolveResponsiveClasses(size, INLINE_CONTAINER_SIZE_CLASSES) +} + +export function getAlignmentClasses(align: CardAlign, hasDescription: boolean) { + return { + center: { + contentClass: hasDescription ? "justify-between" : "items-center justify-center text-center", + textClass: hasDescription ? "gap-2 text-left" : "items-center text-center", + iconWrapClass: "justify-center", + }, + start: { + contentClass: hasDescription ? "items-start justify-start text-left" : "items-start justify-center text-left", + textClass: hasDescription ? "items-start gap-5 text-left" : "items-start text-left", + iconWrapClass: "justify-start", + }, + inline: { + contentClass: "", + textClass: "", + iconWrapClass: "", + }, + }[align] +} diff --git a/src/components/faqs/faqs-tabs.tsx b/src/components/faqs/faqs-tabs.tsx new file mode 100644 index 0000000..5d1af7c --- /dev/null +++ b/src/components/faqs/faqs-tabs.tsx @@ -0,0 +1,137 @@ +"use client" + +import type { IconType } from "react-icons" +import { FiBookOpen, FiDollarSign, FiEdit, FiFileText, FiSend } from "react-icons/fi" +import AccordionList from "@/components/accordion-list" +import type { AccordionListItem } from "@/components/accordion-list/types" +import CalloutItem from "@/components/callout-item" +import TabsNavigation from "@/components/tabs" +import { Tabs } from "@/components/tabs/tabs" +import { TabsContent } from "@/components/tabs/tabs-content" + +const faqItems: { + value: string + label: string + icon: IconType + accordionItems: AccordionListItem[] +}[] = [ + { + value: "tab1", + label: "Lezioni", + icon: FiBookOpen, + accordionItems: [ + { + value: "item-1", + trigger: "Per le lauree le lezioni sono sospese?", + 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: "tab2", + label: "Tasse", + icon: FiDollarSign, + accordionItems: [ + { + value: "item-1", + trigger: "Per le lauree le lezioni sono sospese?", + 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: "tab3", + label: "Esami", + icon: FiEdit, + accordionItems: [ + { + value: "item-1", + trigger: "Per le lauree le lezioni sono sospese?", + 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: "tab4", + label: "Piano di Studi", + icon: FiFileText, + accordionItems: [ + { + value: "item-1", + trigger: "Per le lauree le lezioni sono sospese?", + 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: "tab5", + label: "Mobilità Internazionale", + icon: FiSend, + accordionItems: [ + { + value: "item-1", + trigger: "Per le lauree le lezioni sono sospese? ", + 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: "Per le lauree le lezioni sono sospese? ", + 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-3", + trigger: "Per le lauree le lezioni sono sospese? ", + 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-4", + trigger: "Per le lauree le lezioni sono sospese? ", + 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-5", + trigger: "Per le lauree le lezioni sono sospese? ", + 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.", + }, + ], + }, +] + +export function FaqsTabs() { + return ( +
+ + + {faqItems.map((item) => ( + + + + ))} + + + + Guide + Esplora le Guide + + } + className="w-full" + /> +
+ ) +} diff --git a/src/components/header/constants.ts b/src/components/header/constants.ts index 38a1e4f..a785ca5 100644 --- a/src/components/header/constants.ts +++ b/src/components/header/constants.ts @@ -5,29 +5,29 @@ export const headerMenuItems: HeaderMenuItem[] = [ { title: "Resources", menu: [ - { title: "Materials", href: "#", icon: FiChevronRight }, - { title: "Guides", href: "#", icon: FiChevronRight }, - { title: "Computer Recs", href: "#", icon: FiChevronRight }, - { title: "FAQs", href: "#", icon: FiChevronRight }, - { title: "Rankings", href: "#", icon: FiArrowUpRight }, - { title: "Tol Project", href: "#", icon: FiArrowUpRight }, + { title: "Materials", href: "/materials", icon: FiChevronRight }, + { title: "Guides", href: "/guides", icon: FiChevronRight }, + { title: "Computer Recs", href: "/computer-recs", icon: FiChevronRight }, + { title: "FAQs", href: "/faqs", icon: FiChevronRight }, + { title: "Rankings", href: "https://rankings.polinetwork.org/", icon: FiArrowUpRight }, + { title: "Tol Project", href: "https://tol.polinetwork.org/", icon: FiArrowUpRight }, ], }, { title: "Community", menu: [ - { title: "Groups", href: "#", icon: FiChevronRight }, - { title: "Projects", href: "#", icon: FiChevronRight }, + { title: "Groups", href: "/groups", icon: FiChevronRight }, + { title: "Projects", href: "/projects", icon: FiChevronRight }, { title: "Freshman", href: "/matricole", icon: FiChevronRight }, - { title: "Associations", href: "#", icon: FiChevronRight }, + { title: "Associations", href: "/associations", icon: FiChevronRight }, ], }, { title: "About", menu: [ - { title: "About us", href: "#", icon: FiChevronRight }, - { title: "Join us", href: "#", icon: FiChevronRight }, - { title: "Contact us", href: "#", icon: FiChevronRight }, + { title: "About us", href: "/about", icon: FiChevronRight }, + { title: "Join us", href: "/join", icon: FiChevronRight }, + { title: "Contact us", href: "/contact", icon: FiChevronRight }, ], }, ] diff --git a/src/components/header/desktop-layout.tsx b/src/components/header/desktop-layout.tsx index d9719f2..ecceec2 100644 --- a/src/components/header/desktop-layout.tsx +++ b/src/components/header/desktop-layout.tsx @@ -18,7 +18,7 @@ import { Logo } from "./logo" export const DesktopLayout = () => { const removeDefaultHoverEffectClass = "hover:bg-transparent focus:bg-transparent bg-transparent!" - const customHoverEffectClass = "hover:underline focus:underline decoration-1 decoration-blue-secondary " + const customHoverEffectClass = "hover:underline focus-visible:underline decoration-1 decoration-blue-secondary " return (
@@ -44,6 +44,9 @@ export const DesktopLayout = () => { customHoverEffectClass )} hideChevron + onPointerEnter={(e) => e.preventDefault()} + onPointerLeave={(e) => e.preventDefault()} + onPointerMove={(e) => e.preventDefault()} >
{item.title} diff --git a/src/components/home/about-us.tsx b/src/components/home/about-us.tsx index 8d9ab54..f879f91 100644 --- a/src/components/home/about-us.tsx +++ b/src/components/home/about-us.tsx @@ -1,3 +1,5 @@ +import Link from "next/link" +import { FiArrowUpRight } from "react-icons/fi" import { Button } from "@/components/ui/button" export function AboutUs() { @@ -7,8 +9,11 @@ export function AboutUs() { PoliNetwork unisce gli studenti del Poli: gruppi, risorse e supporto, tutti in un'unica community. - ) diff --git a/src/components/home/carousel-mock.tsx b/src/components/home/carousel-mock.tsx deleted file mode 100644 index c4f9758..0000000 --- a/src/components/home/carousel-mock.tsx +++ /dev/null @@ -1,49 +0,0 @@ -"use client" - -import { CardCaption } from "@/components/card-caption" -import { Carousel, CarouselContent, CarouselDots, CarouselItem } from "@/components/ui/carousel" - -const mockCards = [ - { - title: "WeBeepSync", - caption: - "WeBeep Sync è una semplice app, user-friendly e senza compromessi che serve per tenere sincronizzati tutti i tuoi file di WeBeep.", - }, - { - title: "PolimiSchedule", - caption: - "Genera un file iCalendar (.ics) a partire dal formato testuale dell’Orario delle lezioni. Possibilità di importare su Google Calendar.", - }, - { - title: "WiFiLinux", - caption: "Scarica ed esegui lo script Python per attivare la connessione permanente al WiFi Polimi.", - }, - { - title: "The TOL Project", - caption: "Un simulatore gratuito del test di ammissione per le aspiranti matricole di Ingegneria del PoliMi.", - }, -] as const - -// TODO: delete this when merging -export function CarouselMock() { - return ( -
-

- PoliNetwork -

- - - - {mockCards.map((card) => ( - -
- -
-
- ))} -
- -
-
- ) -} diff --git a/src/components/home/group-search.tsx b/src/components/home/group-search.tsx new file mode 100644 index 0000000..53d3f41 --- /dev/null +++ b/src/components/home/group-search.tsx @@ -0,0 +1,78 @@ +"use client" +import { useAsyncDebouncer } from "@tanstack/react-pacer" +import Image from "next/image" +import Link from "next/link" +import { useEffect, useState } from "react" +import { FiSearch } from "react-icons/fi" +import telegram from "@/assets/icons/telegram-fill.svg" +import { Input } from "@/components/ui/input" +import { cn } from "@/lib/utils" +import { searchGroups } from "@/queries/groups" +import type { ApiOutput } from "@/types" +import { Glass } from "../glass" +import { Spinner } from "../spinner" + +export function GroupSearch() { + const [query, setQuery] = useState("") + const [results, setResults] = useState(null) + + const debouncedSearch = useAsyncDebouncer( + async (searchTerm: string) => { + const res = await searchGroups(searchTerm, 20) + setResults(res) + }, + { + wait: 500, + }, + (state) => ({ isLoading: state.isPending || state.isExecuting }) + ) + + useEffect(() => { + setResults(null) + if (query) debouncedSearch.maybeExecute(query) + }, [query, debouncedSearch.maybeExecute]) + + return ( +
+ } + type="text" + placeholder="Find your group" + aria-label="Find your group" + containerClassName="w-full" + className="typo-body-medium" + value={query} + onChange={(e) => { + setQuery(e.target.value) + }} + /> + + {query && ( + +
+ {results && results.count > 0 ? ( + results?.groups + .filter((g): g is typeof g & { link: string } => !!g.link) + .map((g) => ( + +
+ Telegram + {g.title} +
+ + )) + ) : ( +
+ {debouncedSearch.state.isLoading ? ( + + ) : ( + "No group found" + )} +
+ )} +
+
+ )} +
+ ) +} diff --git a/src/components/home/hero.tsx b/src/components/home/hero.tsx index 02d7444..ff1152f 100644 --- a/src/components/home/hero.tsx +++ b/src/components/home/hero.tsx @@ -1,7 +1,7 @@ import Link from "next/link" -import { FiNavigation, FiSearch, FiUserPlus } from "react-icons/fi" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" +import { FiNavigation, FiUserPlus } from "react-icons/fi" +import { Button } from "../ui/button" +import { GroupSearch } from "./group-search" export function Hero() { return ( @@ -11,18 +11,13 @@ export function Hero() { Trova gruppi, risorse e supporto tra gli studenti del Polimi - } - type="text" - placeholder="Find your group" - aria-label="Find your group" - containerClassName="max-w-lg" - className="typo-body-medium" - /> + -
diff --git a/src/components/home/materials.tsx b/src/components/home/materials.tsx index 45a9f5e..1dec069 100644 --- a/src/components/home/materials.tsx +++ b/src/components/home/materials.tsx @@ -1,6 +1,8 @@ +import Link from "next/link" import { FiArrowUpRight, FiBook, FiBookOpen, FiClipboard, FiFileText, FiUploadCloud } from "react-icons/fi" import { CardIcon } from "@/components/card-icon" import { Button } from "@/components/ui/button" +import { CardCaption } from "../card-caption" const featuredCards = [ { @@ -30,13 +32,19 @@ const quickLinks = [ export function Materials() { return (
-
-
- {/* TODO sotto sm usare le altre card fatte da Diubi */} +
+ {/* Desktop Cards */} +
{featuredCards.map((card) => ( ))}
+ {/* Mobile Cards */} +
+ {featuredCards.map((card) => ( + + ))} +
{quickLinks.map((card) => ( @@ -58,9 +66,11 @@ export function Materials() { Il più grande archivio didattico degli studenti del Politecnico. Trova appunti, dispense ed esami, oppure carica i tuoi file per far crescere la community.

-
diff --git a/src/components/home/projects.tsx b/src/components/home/projects.tsx index 210d37b..b66fafc 100644 --- a/src/components/home/projects.tsx +++ b/src/components/home/projects.tsx @@ -1,3 +1,4 @@ +import Link from "next/link" import { FiArrowUpRight } from "react-icons/fi" import { CardCaption } from "@/components/card-caption" import { Button } from "@/components/ui/button" @@ -8,19 +9,23 @@ const featuredCards = [ title: "WeBeepSync", caption: "WeBeep Sync è una semplice app, user-friendly e senza compromessi che serve per tenere sincronizzati tutti i tuoi file di WeBeep.", + href: "https://github.com/toto04/webeep-sync", }, { title: "PolimiSchedule", caption: "Genera un file iCalendar (.ics) a partire dal formato testuale dell’Orario delle lezioni. Possibilità di importare su Google Calendar.", + href: "https://github.com/jacopo-j/polimi-schedule", }, { title: "WiFiLinux", caption: "Scarica ed esegui lo script Python per attivare la connessione permanente al WiFi Polimi", + href: "https://github.com/PoliNetworkOrg/WiFiLinux", }, { title: "The TOL Project", caption: "Un simulatore gratuito del test di ammissione per le aspiranti matricole di Ingegneria del PoliMi.", + href: "https://tol.polinetwork.org/", }, ] as const @@ -40,9 +45,11 @@ export function Projects() { Progetti e strumenti nati dalla community del Politecnico per semplificare la vita universitaria e promuovere l’innovazione. Hai un’idea? Proponila, trova collaboratori e realizza la tua soluzione.

-
diff --git a/src/components/matricole/constants.ts b/src/components/matricole/constants.ts new file mode 100644 index 0000000..c26dfa4 --- /dev/null +++ b/src/components/matricole/constants.ts @@ -0,0 +1,169 @@ +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: "/guides", + }, + { + 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 + +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: "/materials", +} + +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 56d5569..9643cab 100644 --- a/src/components/matricole/faqs.tsx +++ b/src/components/matricole/faqs.tsx @@ -1,43 +1,10 @@ "use client" import AccordionList from "@/components/accordion-list" -import type { AccordionListItem } from "@/components/accordion-list/types" - -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.", - }, -] +import { accordionItems } from "./constants" export function FAQsPage() { return ( -
+

Domande Frequenti tra le Matricole

diff --git a/src/components/matricole/guides.tsx b/src/components/matricole/guides.tsx new file mode 100644 index 0000000..d952b32 --- /dev/null +++ b/src/components/matricole/guides.tsx @@ -0,0 +1,61 @@ +import { CardIcon } from "@/components/card-icon" +import { CardGroup } from "../card-group" +import { guides, hub, materiali, techTools } from "./constants" + +export function MatricoleGuides() { + return ( +
+
+

Guide e Utility

+ +
+ {/* Hidden in mobile */} + +
+ {guides.slice(1).map((guide) => ( + + ))} +
+
+ + +
+ {hub.cards.map((card) => ( + + ))} +
+
+ +
+ {/* hidden in mobile */} + + +
+ {techTools.cards.map((card) => ( + + ))} +
+
+
+
+
+ ) +} diff --git a/src/components/matricole/intro.tsx b/src/components/matricole/intro.tsx index e8fa720..a0c7363 100644 --- a/src/components/matricole/intro.tsx +++ b/src/components/matricole/intro.tsx @@ -1,22 +1,9 @@ -import { FiBarChart2, FiMonitor } from "react-icons/fi" import { CardIcon } from "@/components/card-icon" - -const resources = [ - { - title: "Graduatorie", - description: "Risultati storici e soglie di accesso per i vari corsi di laurea.", - icon: FiBarChart2, - }, - { - title: "Progetto TOL", - description: "Informazioni sul test di ingresso (TOL) e su come prepararsi al meglio.", - icon: FiMonitor, - }, -] as const +import { resources } from "./constants" export function MatricoleIntro() { return ( -
+

@@ -27,7 +14,7 @@ export function MatricoleIntro() {

-
+
{resources.map((resource) => ( ))} diff --git a/src/components/projects/collection.tsx b/src/components/projects/collection.tsx index 81221e0..81c651d 100644 --- a/src/components/projects/collection.tsx +++ b/src/components/projects/collection.tsx @@ -83,7 +83,7 @@ export function Collection() {
-
+
{collectionCards.map((card) => ( ))} diff --git a/src/components/projects/community-news.tsx b/src/components/projects/community-news.tsx index f5881ff..1aa9e9b 100644 --- a/src/components/projects/community-news.tsx +++ b/src/components/projects/community-news.tsx @@ -1,6 +1,7 @@ import { FiCrop } from "react-icons/fi" import { CardCaption } from "../card-caption" import { Carousel, CarouselContent, CarouselDots, CarouselItem } from "../ui/carousel" +import { Hero } from "../ui/hero" const communityCards = [ { @@ -36,14 +37,7 @@ const communityCards = [ export function CommunityNews() { return (
-
-

- Projects -

-

- Esplora e contribuisci ai progetti degli studenti -

-
+
diff --git a/src/components/projects/deprecated.tsx b/src/components/projects/deprecated.tsx index ebff0a9..67a3c07 100644 --- a/src/components/projects/deprecated.tsx +++ b/src/components/projects/deprecated.tsx @@ -47,7 +47,7 @@ export function Deprecated() {
-
+
{communityCards.map((card) => ( ))} diff --git a/src/components/spinner.tsx b/src/components/spinner.tsx new file mode 100644 index 0000000..abdf860 --- /dev/null +++ b/src/components/spinner.tsx @@ -0,0 +1,26 @@ +import { cn } from "@/lib/utils" + +export function Spinner({ className }: React.HtmlHTMLAttributes) { + return ( + // biome-ignore lint/a11y/useSemanticElements: incorrect change suggestion +
+ + Loading... +
+ ) +} diff --git a/src/components/tabs/tabs-list.tsx b/src/components/tabs/tabs-list.tsx index 804ef07..5a8a09c 100644 --- a/src/components/tabs/tabs-list.tsx +++ b/src/components/tabs/tabs-list.tsx @@ -6,15 +6,23 @@ import { Glass } from "../glass" export function TabsList({ className, children, ...props }: React.ComponentProps) { return ( - +
- {children} +
+ + {children} + +
- +
) } diff --git a/src/components/ui/hero.tsx b/src/components/ui/hero.tsx new file mode 100644 index 0000000..d4ebc4b --- /dev/null +++ b/src/components/ui/hero.tsx @@ -0,0 +1,10 @@ +export function Hero({ title, description }: { title: string; description: string }) { + return ( +
+

+ {title} +

+

{description}

+
+ ) +} diff --git a/src/components/ui/item.tsx b/src/components/ui/item.tsx index 9ee61ab..6e9fe15 100644 --- a/src/components/ui/item.tsx +++ b/src/components/ui/item.tsx @@ -27,7 +27,13 @@ function ItemContent({ className, ...props }: React.ComponentProps<"div">) { } function ItemTitle({ className, ...props }: React.ComponentProps<"p">) { - return

+ return ( +

+ ) } function ItemActions({ className, ...props }: React.ComponentProps<"div">) { diff --git a/src/env.js b/src/env.js index fd2e79c..10022e7 100644 --- a/src/env.js +++ b/src/env.js @@ -8,6 +8,7 @@ export const env = createEnv({ */ server: { NODE_ENV: z.enum(["development", "test", "production"]).default("development"), + BACKEND_URL: z.url().default("http://localhost:3000"), }, /** @@ -25,6 +26,7 @@ export const env = createEnv({ */ runtimeEnv: { NODE_ENV: process.env.NODE_ENV, + BACKEND_URL: process.env.BACKEND_URL, }, /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially diff --git a/src/lib/backend.ts b/src/lib/backend.ts new file mode 100644 index 0000000..d21bdfe --- /dev/null +++ b/src/lib/backend.ts @@ -0,0 +1,16 @@ +import "server-only" + +import { type AppRouter, TRPC_PATH } from "@polinetwork/backend" +import { createTRPCClient, httpBatchLink } from "@trpc/client" +import SuperJSON from "superjson" +import { env } from "@/env" + +const url = env.BACKEND_URL + TRPC_PATH +export const trpc = createTRPCClient({ + links: [ + httpBatchLink({ + url, + transformer: SuperJSON, + }), + ], +}) diff --git a/src/queries/groups.ts b/src/queries/groups.ts new file mode 100644 index 0000000..a2c5f64 --- /dev/null +++ b/src/queries/groups.ts @@ -0,0 +1,8 @@ +"use server" + +import { trpc } from "@/lib/backend" + +export async function searchGroups(query: string, limit: number = 6) { + const res = await trpc.tg.groups.search.query({ query, limit }) + return res +} diff --git a/src/styles/globals.css b/src/styles/globals.css index 2086df8..2560003 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -31,3 +31,11 @@ margin-right: 0px !important; } } + +@media (max-width: 767px) { + /* max-md */ + .scroll-fade-x { + mask-image: linear-gradient(to right, transparent, black 28px, black calc(100% - 36px), transparent); + -webkit-mask-image: linear-gradient(to right, transparent, black 28px, black calc(100% - 28px), transparent); + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..ca397f6 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,6 @@ +import type { AppRouter } from "@polinetwork/backend" +import type { inferRouterError, inferRouterInputs, inferRouterOutputs } from "@trpc/server" + +export type ApiOutput = inferRouterOutputs +export type ApiInput = inferRouterInputs +export type ApiError = inferRouterError