diff --git a/thoughts-backend/models/src/schemas/user.rs b/thoughts-backend/models/src/schemas/user.rs index fc862a5..44013a0 100644 --- a/thoughts-backend/models/src/schemas/user.rs +++ b/thoughts-backend/models/src/schemas/user.rs @@ -9,12 +9,18 @@ use crate::domains::user; pub struct UserSchema { pub id: Uuid, pub username: String, + #[serde(rename = "displayName")] pub display_name: Option, pub bio: Option, + #[serde(rename = "avatarUrl")] pub avatar_url: Option, + #[serde(rename = "headerUrl")] pub header_url: Option, + #[serde(rename = "customCss")] pub custom_css: Option, + #[serde(rename = "topFriends")] pub top_friends: Vec, + #[serde(rename = "joinedAt")] pub joined_at: DateTimeWithTimeZoneWrapper, } diff --git a/thoughts-frontend/app/globals.css b/thoughts-frontend/app/globals.css index a2dc41e..dc98be7 100644 --- a/thoughts-frontend/app/globals.css +++ b/thoughts-frontend/app/globals.css @@ -1,26 +1,122 @@ @import "tailwindcss"; +@import "tw-animate-css"; -:root { - --background: #ffffff; - --foreground: #171717; -} +@custom-variant dark (&:is(.dark *)); @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); --font-sans: var(--font-geist-sans); --font-mono: var(--font-geist-mono); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); } -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; } } - -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; -} diff --git a/thoughts-frontend/app/page.tsx b/thoughts-frontend/app/page.tsx index d6c90fa..3d499cc 100644 --- a/thoughts-frontend/app/page.tsx +++ b/thoughts-frontend/app/page.tsx @@ -1,6 +1,10 @@ "use client"; import { useState, useEffect, FormEvent } from "react"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import { Card, CardHeader, CardContent } from "@/components/ui/card"; +import { Alert } from "@/components/ui/alert"; interface Thought { id: number; @@ -10,16 +14,11 @@ interface Thought { } export default function Home() { - // State to store the list of thoughts for the feed const [thoughts, setThoughts] = useState([]); - // State for the content of the new thought being typed const [newThoughtContent, setNewThoughtContent] = useState(""); - // State to manage loading status const [isLoading, setIsLoading] = useState(true); - // State to hold any potential errors during API calls const [error, setError] = useState(null); - // Function to fetch the feed from the backend API const fetchFeed = async () => { try { setError(null); @@ -28,7 +27,6 @@ export default function Home() { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); - // The API returns { thoughts: [...] }, so we access the nested array setThoughts(data.thoughts || []); } catch (e: unknown) { console.error("Failed to fetch feed:", e); @@ -40,15 +38,13 @@ export default function Home() { } }; - // useEffect hook to fetch the feed when the component first loads useEffect(() => { fetchFeed(); }, []); - // Handler for submitting the new thought form const handleSubmitThought = async (e: FormEvent) => { e.preventDefault(); - if (!newThoughtContent.trim()) return; // Prevent empty posts + if (!newThoughtContent.trim()) return; try { const response = await fetch("http://localhost:8000/thoughts", { @@ -56,7 +52,6 @@ export default function Home() { headers: { "Content-Type": "application/json", }, - // We are hardcoding author_id: 1 as we don't have auth yet body: JSON.stringify({ content: newThoughtContent, author_id: 1 }), }); @@ -64,9 +59,7 @@ export default function Home() { throw new Error(`HTTP error! status: ${response.status}`); } - // Clear the input box setNewThoughtContent(""); - // Refresh the feed to show the new post fetchFeed(); } catch (e: unknown) { console.error("Failed to post thought:", e); @@ -91,12 +84,14 @@ export default function Home() { {/* New Thought Form */} -
+
-