From e2f9757a36985391b3ccadb6c0dea660f3741dc6 Mon Sep 17 00:00:00 2001 From: Sebastian Robak Date: Fri, 14 Nov 2025 09:56:35 +0100 Subject: [PATCH 1/2] Sidebar added --- package-lock.json | 10 +++ package.json | 1 + src/app/layout.tsx | 5 +- src/components/layout/AppShell.tsx | 51 +++++++++++++ src/components/layout/Navbar.tsx | 60 +++++++++++++++ src/components/layout/Sidebar.tsx | 118 +++++++++++++++++++++++++++++ 6 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 src/components/layout/AppShell.tsx create mode 100644 src/components/layout/Navbar.tsx create mode 100644 src/components/layout/Sidebar.tsx diff --git a/package-lock.json b/package-lock.json index d93b375..486d841 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@auth/prisma-adapter": "^2.11.0", + "@heroicons/react": "^2.2.0", "@hookform/resolvers": "^5.2.2", "@prisma/client": "^6.17.1", "bcryptjs": "^3.0.2", @@ -255,6 +256,15 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@heroicons/react": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz", + "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16 || ^19.0.0-rc" + } + }, "node_modules/@hookform/resolvers": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.2.2.tgz", diff --git a/package.json b/package.json index 43d6405..15e0e12 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ }, "dependencies": { "@auth/prisma-adapter": "^2.11.0", + "@heroicons/react": "^2.2.0", "@hookform/resolvers": "^5.2.2", "@prisma/client": "^6.17.1", "bcryptjs": "^3.0.2", diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 427203b..bd49f0f 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import SessionProvider from "@/components/providers/SessionProvider"; +import AppShell from "@/components/layout/AppShell"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -28,7 +29,9 @@ export default function RootLayout({ - {children} + + {children} + ); diff --git a/src/components/layout/AppShell.tsx b/src/components/layout/AppShell.tsx new file mode 100644 index 0000000..c6957eb --- /dev/null +++ b/src/components/layout/AppShell.tsx @@ -0,0 +1,51 @@ +"use client"; + +import React, { useEffect, useState } from "react"; +import Navbar from "@/components/layout/Navbar"; +import Sidebar from "@/components/layout/Sidebar"; + +export default function AppShell({ children }: { children: React.ReactNode }) { + const [collapsed, setCollapsed] = useState(false); + + // Wczytanie stanu sidebaru z localStorage + useEffect(() => { + try { + const stored = window.localStorage.getItem("localaid-sidebar-collapsed"); + if (stored === "true") { + setCollapsed(true); + } + } catch { + // ignorujemy + } + }, []); + + const handleToggle = () => { + setCollapsed((prev) => { + const next = !prev; + try { + window.localStorage.setItem( + "localaid-sidebar-collapsed", + next ? "true" : "false" + ); + } catch { + // ignorujemy + } + return next; + }); + }; + + return ( +
+ {/* Sidebar – pełna wysokość */} + + + {/* Prawa część */} +
+ +
+
{children}
+
+
+
+ ); +} diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx new file mode 100644 index 0000000..49cb510 --- /dev/null +++ b/src/components/layout/Navbar.tsx @@ -0,0 +1,60 @@ +"use client"; + +import React from "react"; +import { BellIcon } from "@heroicons/react/24/outline"; + +export default function Navbar() { + return ( +
+
+ {/* Logo / nazwa appki */} +
+ + 🏠 + +
+
LocalAid
+
+ Pomoc sąsiedzka w Twojej okolicy +
+
+
+ + {/* Search */} +
+
+ +
+
+ + {/* Prawa strona – powiadomienia + user */} +
+ + +
+
+
+ Jan Kowalski +
+
+ jan.kowalski@example.com +
+
+
+ JK +
+
+
+
+
+ ); +} diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx new file mode 100644 index 0000000..249bf36 --- /dev/null +++ b/src/components/layout/Sidebar.tsx @@ -0,0 +1,118 @@ +"use client"; + +import React from "react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { + HomeIcon, + MapIcon, + ClipboardDocumentListIcon, + UserCircleIcon, + Cog6ToothIcon, + PlusIcon, + ChevronDoubleLeftIcon, + ChevronDoubleRightIcon, +} from "@heroicons/react/24/outline"; + +type NavItem = { + label: string; + href: string; + icon: React.ComponentType>; +}; + +const mainNav: NavItem[] = [ + { label: "Przegląd", href: "/", icon: HomeIcon }, + { label: "Ogłoszenia", href: "/posts", icon: ClipboardDocumentListIcon }, + { label: "Mapa", href: "/map", icon: MapIcon }, +]; + +const accountNav: NavItem[] = [ + { label: "Profil", href: "/profile", icon: UserCircleIcon }, + { label: "Ustawienia", href: "/settings", icon: Cog6ToothIcon }, +]; + +export default function Sidebar({ + collapsed, + onToggle, +}: { + collapsed: boolean; + onToggle: () => void; +}) { + const pathname = usePathname(); + + const renderItem = (item: NavItem) => { + const Icon = item.icon; + const isActive = + item.href === "/" ? pathname === "/" : pathname.startsWith(item.href); + + return ( + + + {!collapsed && {item.label}} + + ); + }; + + return ( + + ); +} From 77f4d896155e50a27d9d63ed2ffb14bd2b31b72e Mon Sep 17 00:00:00 2001 From: Sebastian Robak Date: Fri, 14 Nov 2025 12:34:35 +0100 Subject: [PATCH 2/2] aktualizacja --- package-lock.json | 1 + src/app/layout.tsx | 4 +- src/app/loading.tsx | 34 ++++ src/app/page.tsx | 254 +++++++++++++++------------ src/components/layout/Navbar.tsx | 18 +- src/components/layout/Sidebar.tsx | 24 +-- src/components/ui/Button.tsx | 57 ++++++ src/components/ui/LoadingSpinner.tsx | 9 + src/components/ui/Skeleton.tsx | 18 ++ src/components/ui/index.ts | 3 + 10 files changed, 290 insertions(+), 132 deletions(-) create mode 100644 src/app/loading.tsx create mode 100644 src/components/ui/Button.tsx create mode 100644 src/components/ui/LoadingSpinner.tsx create mode 100644 src/components/ui/Skeleton.tsx create mode 100644 src/components/ui/index.ts diff --git a/package-lock.json b/package-lock.json index 486d841..2a961c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2431,6 +2431,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index bd49f0f..7fd3a88 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -26,9 +26,7 @@ export default function RootLayout({ }>) { return ( - + {children} diff --git a/src/app/loading.tsx b/src/app/loading.tsx new file mode 100644 index 0000000..aa98824 --- /dev/null +++ b/src/app/loading.tsx @@ -0,0 +1,34 @@ +import { LoadingSpinner, Skeleton } from "@/components/ui"; + +export default function Loading() { + return ( +
+ {/* mały spinner na górze */} +
+ +
+ + {/* skeleton hero */} +
+ + + +
+ + {/* skeleton statystyki */} +
+ + + +
+ + {/* skeleton lista ogłoszeń */} +
+ + + + +
+
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index be62a83..72f965f 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,144 +1,178 @@ -import { auth } from "@/lib/auth" -import { prisma } from "@/lib/prisma" -import Link from "next/link" -import { signOut } from "@/lib/auth" +import { auth, signOut } from "@/lib/auth"; +import { prisma } from "@/lib/prisma"; +import Link from "next/link"; export default async function Home() { - const session = await auth() - - // Fetch some posts from database + const session = await auth(); + const posts = await prisma.post.findMany({ take: 3, - orderBy: { createdAt: 'desc' }, + orderBy: { createdAt: "desc" }, include: { author: { select: { name: true, email: true, - } + }, }, _count: { - select: { comments: true } - } - } - }) + select: { comments: true }, + }, + }, + }); - return ( -
- {/* Header */} -
-
-
-

🏠 LocalAid

-

Pomoc sąsiedzka w Twojej okolicy

-
-
- {session ? ( - <> - - Cześć, {session.user?.name || session.user?.email} - -
{ - "use server" - await signOut() - }} - > - -
- - ) : ( - <> - - Zaloguj się - - - Zarejestruj się - - - )} -
-
-
+ const totalComments = posts.reduce( + (sum, post) => sum + post._count.comments, + 0 + ); - {/* Main Content */} -
- {/* Welcome Section */} -
-

- {session ? `Witaj ponownie! 👋` : 'Witamy w LocalAid! 🎉'} -

-

- LocalAid to platforma łącząca sąsiadów, którzy potrzebują pomocy z tymi, którzy mogą jej udzielić. - Pożycz narzędzie, pomóż w zakupach, lub znajdź kogoś kto pomoże w transporcie. + return ( +

+ {/* Welcome / hero + akcje */} +
+
+

+ {session ? "Witaj ponownie! 👋" : "Witamy w LocalAid! 🎉"} +

+

+ LocalAid to platforma łącząca sąsiadów, którzy potrzebują pomocy z + tymi, którzy mogą jej udzielić. Pożycz narzędzie, pomóż w zakupach, + albo znajdź kogoś, kto pomoże w transporcie.

+ {!session && ( -
-

🧪 Demo - konta testowe:

-

Email: jan.kowalski@example.com

-

Hasło: password123

+
+

🧪 Konto demo:

+

+ Email:{" "} + + jan.kowalski@example.com + +

+

+ Hasło:{" "} + + password123 + +

)}
- {/* Stats */} -
-
-
{posts.length}
-
Aktywnych ogłoszeń
-
-
-
- {posts.reduce((sum, post) => sum + post._count.comments, 0)} -
-
Komentarzy
+
+ {session ? ( + <> +
{ + "use server"; + await signOut(); + }} + > + +
+ + ) : ( + <> + + Zaloguj się + + + Zarejestruj się + + + )} +
+
+ + {/* Statystyki */} +
+
+
+ {posts.length}
-
-
3
-
Użytkowników
+
Aktywnych ogłoszeń
+
+
+
+ {totalComments}
+
Komentarzy
+
+
+
3
+
Użytkowników (demo)
+
+
+ + {/* Ostatnie ogłoszenia */} +
+
+

+ 📋 Najnowsze ogłoszenia +

+ + Zobacz wszystkie +
- {/* Recent Posts */} -
-

📋 Najnowsze ogłoszenia

+ {posts.length === 0 ? ( +

+ Brak ogłoszeń. Dodaj pierwsze ogłoszenie, aby zacząć. +

+ ) : (
{posts.map((post) => ( -
-

{post.title}

-

{post.description}

-
- 👤 {post.author.name} +
+

+ {post.title} +

+

+ {post.description} +

+
+ 👤 {post.author.name ?? post.author.email} 📁 {post.category} 💬 {post._count.comments} komentarzy - 🕒 {new Date(post.createdAt).toLocaleDateString('pl-PL')} + + 🕒{" "} + {new Date(post.createdAt).toLocaleDateString("pl-PL", { + day: "2-digit", + month: "2-digit", + year: "numeric", + })} +
))}
-
+ )} +
- {/* Database Connection Test */} -
-

- ✅ Baza danych działa poprawnie! -

-

- Połączenie z SQLite zostało nawiązane. Załadowano {posts.length} ogłoszenia. -

-
-
+ {/* Info o bazie – jako mały banner */} +
+

✅ Baza danych działa poprawnie

+

+ Połączenie z SQLite zostało nawiązane. Załadowano {posts.length}{" "} + ogłoszenia. +

+
- ) + ); } diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx index 49cb510..f08c022 100644 --- a/src/components/layout/Navbar.tsx +++ b/src/components/layout/Navbar.tsx @@ -2,6 +2,7 @@ import React from "react"; import { BellIcon } from "@heroicons/react/24/outline"; +import Button from "@/components/ui/Button"; export default function Navbar() { return ( @@ -30,16 +31,16 @@ export default function Navbar() { - {/* Prawa strona – powiadomienia + user */} + {/* Prawa strona */}
- +
); diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx new file mode 100644 index 0000000..e2448ea --- /dev/null +++ b/src/components/ui/Button.tsx @@ -0,0 +1,57 @@ +"use client"; + +import React from "react"; +import clsx from "clsx"; + +type ButtonVariant = "primary" | "secondary" | "danger" | "icon"; + +interface ButtonProps extends React.ButtonHTMLAttributes { + variant?: ButtonVariant; + children?: React.ReactNode; // może być pusty przy przycisku-ikonce + iconLeft?: React.ReactNode; + iconRight?: React.ReactNode; + fullWidth?: boolean; +} + +const baseClasses = + "inline-flex items-center justify-center gap-2 rounded-lg font-medium text-sm transition active:scale-[0.98] disabled:opacity-60 disabled:cursor-not-allowed focus:outline-none"; + +const variantClasses: Record = { + primary: "bg-indigo-600 text-white hover:bg-indigo-500 shadow-sm", + secondary: + "bg-slate-100 text-slate-700 hover:bg-slate-200 border border-slate-200", + danger: "bg-rose-600 text-white hover:bg-rose-500 shadow-sm", + icon: ` + bg-transparent + text-slate-600 + hover:bg-slate-100 + rounded-full + `, +}; + +export default function Button({ + variant = "primary", + children, + iconLeft, + iconRight, + fullWidth = false, + className, + ...props +}: ButtonProps) { + return ( + + ); +} diff --git a/src/components/ui/LoadingSpinner.tsx b/src/components/ui/LoadingSpinner.tsx new file mode 100644 index 0000000..f907132 --- /dev/null +++ b/src/components/ui/LoadingSpinner.tsx @@ -0,0 +1,9 @@ +"use client"; + +export default function LoadingSpinner() { + return ( +
+
+
+ ); +} diff --git a/src/components/ui/Skeleton.tsx b/src/components/ui/Skeleton.tsx new file mode 100644 index 0000000..dafb1db --- /dev/null +++ b/src/components/ui/Skeleton.tsx @@ -0,0 +1,18 @@ +"use client"; + +import clsx from "clsx"; + +export default function Skeleton({ + className, +}: { + className?: string; +}) { + return ( +
+ ); +} diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts new file mode 100644 index 0000000..e89aa0e --- /dev/null +++ b/src/components/ui/index.ts @@ -0,0 +1,3 @@ +export { default as Button } from "./Button"; +export { default as Skeleton } from "./Skeleton"; +export { default as LoadingSpinner } from "./LoadingSpinner";