Dine Løpepartner
+ +{match.username}
+TODO : Messaging
+Sorry, something went wrong
; +} diff --git a/src/app/favicon.ico b/src/app/favicon.ico new file mode 100644 index 0000000..718d6fe Binary files /dev/null and b/src/app/favicon.ico differ diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..d162f84 --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,26 @@ +@import "tailwindcss"; + +:root { + --background: #ffffff; + --foreground: #171717; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #ededed; + --foreground: #0a0a0a; + } +} + +body { + background: var(--background); + color: var(--foreground); + font-family: Arial, Helvetica, sans-serif; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..8a4e581 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,36 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; +import Nav from "@/component/nav"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "Løpetid", + description: "Finn din løpepartner", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + + ); +} diff --git a/src/app/login/actions.ts b/src/app/login/actions.ts new file mode 100644 index 0000000..f883ef2 --- /dev/null +++ b/src/app/login/actions.ts @@ -0,0 +1,39 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { redirect } from "next/navigation"; + +import { createClient } from "@/utils/supabase/server"; + +export async function login(formData: FormData) { + const supabase = await createClient(); + + // type-casting here for convenience + // in practice, you should validate your inputs + const data = { + email: formData.get("email") as string, + password: formData.get("password") as string, + }; + + const { error } = await supabase.auth.signInWithPassword(data); + + if (error) { + redirect("/error"); + } + + revalidatePath("/", "layout"); + redirect("/"); +} + +export async function logout() { + const supabase = await createClient(); + + const { error } = await supabase.auth.signOut(); + + if (error) { + redirect("/error"); + } + + revalidatePath("/", "layout"); + redirect("/"); +} diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx new file mode 100644 index 0000000..09662ad --- /dev/null +++ b/src/app/login/page.tsx @@ -0,0 +1,48 @@ +import { login } from "@/app/login/actions"; + +export default function LoginPage() { + return ( ++ Swipe, match, and run with like-minded runners in your area. + RunnerMatch connects you with running partners who match your + pace, distance, and schedule. +
+Hello {data.user.id}
; +} diff --git a/src/app/profile/[username]/actions.ts b/src/app/profile/[username]/actions.ts new file mode 100644 index 0000000..716c60d --- /dev/null +++ b/src/app/profile/[username]/actions.ts @@ -0,0 +1,78 @@ +"use server"; + +import currentLoggedInUser from "@/lib/currentLoggedInUser"; +import { createClient } from "@/utils/supabase/server"; +import { updateUserInfo } from "@/lib/updateUserInfo"; +import { User } from "@/types/user"; +import { randomUUID } from "crypto"; +import { redirect } from "next/navigation"; + +export default async function updateUser(formData: FormData) { + const supabase = await createClient(); + const user = await currentLoggedInUser(); + + if (!user) { + throw new Error("Not allowed to update user"); + } + let uptUsr: User; + const name = (formData.get("name") as string) ?? user?.attributes.name; + const sko = (formData.get("sko") as string) ?? user?.attributes.sko; + const fart = (formData.get("fart") as string) ?? user?.attributes.fart; + const bio = (formData.get("bio") as string) ?? user?.attributes.bio; + const avatarFile = formData.get("avatar") as File | null; + console.log("avatarFile", avatarFile); + let avatarUrl: string | null = null; + + if (avatarFile?.size && avatarFile.size > 0) { + const fileExt = avatarFile.name.split(".").pop(); + const filePath = `avatars/${user.id}-${randomUUID()}.${fileExt}`; + + const { error: uploadError } = await supabase.storage + .from("avatars") + .upload(filePath, avatarFile, { + cacheControl: "3600", + upsert: true, + contentType: avatarFile.type, + }); + + if (uploadError) { + console.error("Error uploading avatar:", uploadError); + throw new Error("Failed to upload avatar"); + } + + const { data } = await supabase.storage + .from("avatars") + .getPublicUrl(filePath); + + avatarUrl = data.publicUrl; + + uptUsr = { + id: user.id, + email: user.email, + attributes: { + name, + sko, + fart, + bio, + }, + username: user.username, + avatar_url: avatarUrl, + }; + } else { + uptUsr = { + id: user.id, + email: user.email, + attributes: { + name, + sko, + fart, + bio, + }, + username: user.username, + avatar_url: user.avatar_url, + }; + } + + await updateUserInfo(uptUsr as User); + redirect("/profile/" + uptUsr.username); +} diff --git a/src/app/profile/[username]/page.tsx b/src/app/profile/[username]/page.tsx new file mode 100644 index 0000000..d77a1a9 --- /dev/null +++ b/src/app/profile/[username]/page.tsx @@ -0,0 +1,122 @@ +import UserProifle from "@/component/profile"; +import currentLoggedInUser from "@/lib/currentLoggedInUser"; +import { getUserByUsername } from "@/lib/getUserInfo"; +import updateUser from "./actions"; +import { redirect } from "next/navigation"; + +export default async function Profile({ + params, +}: { + params: Promise<{ username: string }>; +}) { + const user = await currentLoggedInUser(); + if (!user) { + return+ {user.attributes.bio} +
+No matches yet!
++ Keep swiping to find running partners +
++ Matched {new Date(match.created_at).toLocaleDateString()} +
+ { + // Prevent the parent onClick from also firing + e.stopPropagation(); + console.log("Link clicked"); + }}>View their profile ++ Connect with your running partners +
+No messages yet
++ Start the conversation with your running partner! +
+{message.content}
++ {new Date(message.created_at).toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + })} +
+please wait...
; + + console.log("Matches:", matches); + + return ( +TODO : Messaging
++ {user.attributes.bio} +
++ {cards[index].attributes.name} +
++ {cards[index].attributes.bio} +
++ Shoe: {cards[index].attributes.sko} +
++ Pace: {cards[index].attributes.fart} min/km +
+