From 01377b91efb5f3e1765b940066e98785602ee43a Mon Sep 17 00:00:00 2001 From: Lorenzo Corallo Date: Fri, 29 May 2026 23:38:48 +0200 Subject: [PATCH 1/3] feat: trpc backend, group search in homepage hero --- .env.example | 1 + package.json | 6 ++ pnpm-lock.yaml | 139 +++++++++++++++++++++++++++ src/assets/icons/telegram-fill.svg | 1 + src/components/home/group-search.tsx | 78 +++++++++++++++ src/components/home/hero.tsx | 15 +-- src/components/spinner.tsx | 26 +++++ src/env.js | 2 + src/lib/backend.ts | 16 +++ src/queries/groups.ts | 8 ++ src/types.ts | 6 ++ 11 files changed, 288 insertions(+), 10 deletions(-) create mode 100644 src/assets/icons/telegram-fill.svg create mode 100644 src/components/home/group-search.tsx create mode 100644 src/components/spinner.tsx create mode 100644 src/lib/backend.ts create mode 100644 src/queries/groups.ts create mode 100644 src/types.ts 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/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/home/group-search.tsx b/src/components/home/group-search.tsx new file mode 100644 index 0000000..022197f --- /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]) + + 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.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..8449c4a 100644 --- a/src/components/home/hero.tsx +++ b/src/components/home/hero.tsx @@ -1,7 +1,9 @@ import Link from "next/link" -import { FiNavigation, FiSearch, FiUserPlus } from "react-icons/fi" -import { Button } from "@/components/ui/button" +import { FiNavigation, FiUserPlus } from "react-icons/fi" import { Input } from "@/components/ui/input" +import { Glass } from "../glass" +import { Button } from "../ui/button" +import { GroupSearch } from "./group-search" export function Hero() { return ( @@ -11,14 +13,7 @@ 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" - /> +