diff --git a/src/app/api/profile/route.ts b/src/app/api/profile/route.ts index e45c823..46238aa 100644 --- a/src/app/api/profile/route.ts +++ b/src/app/api/profile/route.ts @@ -19,7 +19,14 @@ export async function POST(req: NextRequest) { { status: StatusCodes.BAD_REQUEST } ); - if (!username || !display_name || !avatar_url) + if ( + !username || + !display_name || + avatar_url === undefined || + avatar_url === null || + node_address === undefined || + node_address === null + ) return NextResponse.json( { message: diff --git a/src/app/api/user/route.ts b/src/app/api/user/route.ts index 40f25fe..11e57fd 100644 --- a/src/app/api/user/route.ts +++ b/src/app/api/user/route.ts @@ -5,10 +5,10 @@ import { supabase } from "../supa"; /** * POST /api/user * Register new user - * Body: { name, username, email, password } + * Body: { username, email, password } */ export async function POST(req: NextRequest) { - const { name, username, email, password } = await req.json(); + const { username, email, password } = await req.json(); if (!email || !password || !username) return NextResponse.json( @@ -28,29 +28,35 @@ export async function POST(req: NextRequest) { { status: StatusCodes.CONFLICT } ); - const { data: authData, error: authError } = - await supabase.auth.admin.createUser({ - email, - password, - email_confirm: true, - user_metadata: { name, username }, - }); + const { data: authData, error: authError } = await supabase.auth.signUp({ + email, + password, + }); if (authError) return NextResponse.json( { message: authError.message }, - { status: StatusCodes.BAD_REQUEST } + { status: StatusCodes.INTERNAL_SERVER_ERROR } + ); + + if (!authData.user) + return NextResponse.json( + { message: "User is null" }, + { status: StatusCodes.INTERNAL_SERVER_ERROR } ); await supabase.from("profiles").insert({ id: authData.user.id, - name, username, + display_name: username, + avatar_url: "", + node_address: "", }); return NextResponse.json({ - message: "ok", - user_id: authData.user.id, + message: "User registered. Please check your email to verify your account.", + user_id: authData.user?.id, + email_sent: true, }); } @@ -61,30 +67,14 @@ export async function POST(req: NextRequest) { */ export async function GET(req: NextRequest) { const params = new URL(req.url).searchParams; - const [username, password] = [params.get("username"), params.get("password")]; + const [email, password] = [params.get("email"), params.get("password")]; - if (!username || !password) + if (!email || !password) return NextResponse.json( - { message: "Params username and password are required" }, + { message: "Params email and password are required" }, { status: StatusCodes.BAD_REQUEST } ); - let email = username; - if (!username.includes("@")) { - const { data } = await supabase - .from("profiles") - .select("email") - .eq("email", username) - .maybeSingle(); - email = data?.email; - } - - if (!email) - return NextResponse.json( - { message: "Invalid username" }, - { status: StatusCodes.UNAUTHORIZED } - ); - const { data, error } = await supabase.auth.signInWithPassword({ email, password, @@ -104,7 +94,6 @@ export async function GET(req: NextRequest) { user_id: session?.user?.id, }); - // Optionally set cookie res.cookies.set("token", session?.access_token ?? "", { httpOnly: true }); return res; diff --git a/src/app/api/verify/route.ts b/src/app/api/verify/route.ts new file mode 100644 index 0000000..223f423 --- /dev/null +++ b/src/app/api/verify/route.ts @@ -0,0 +1,40 @@ +import { NextRequest, NextResponse } from "next/server"; +import { supabase } from "../supa"; +import { StatusCodes } from "http-status-codes"; + +export async function GET(req: NextRequest) { + const email = req.nextUrl.searchParams.get("email"); + const password = req.nextUrl.searchParams.get("password"); + + if (!email || !password) + return NextResponse.json( + { message: "Missing email or password" }, + { status: StatusCodes.BAD_REQUEST } + ); + + const { data, error } = await supabase.auth.signInWithPassword({ + email, + password, + }); + + if (error) + return NextResponse.json( + { message: error.message }, + { status: StatusCodes.INTERNAL_SERVER_ERROR } + ); + + if (!data) + return NextResponse.json( + { verified: false, message: "User not found" }, + { status: StatusCodes.NOT_FOUND } + ); + + const res = NextResponse.json({ + verified: !!data.user.email_confirmed_at, + user_id: data.user.id, + access_token: data.session.access_token, + refresh_token: data.session.refresh_token, + }); + + return res; +} diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx index 546180b..dd9e76f 100644 --- a/src/app/chat/page.tsx +++ b/src/app/chat/page.tsx @@ -2,16 +2,20 @@ import AppSidebar from "@/components/Sidebar"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { useIsMobile } from "@/hooks/is-mobile"; import useApp from "@/hooks/use-app"; export default function Chat() { const app = useApp(); + const isMobile = useIsMobile(); return ( - Direct Messages + isMobile && app.setSidebarOpen(true)}> + Direct Messages + diff --git a/src/app/verify/page.tsx b/src/app/verify/page.tsx new file mode 100644 index 0000000..4d63ff3 --- /dev/null +++ b/src/app/verify/page.tsx @@ -0,0 +1,74 @@ +"use client"; + +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; +import { toast } from "sonner"; +import Cookies from "js-cookie"; +import axios from "axios"; + +export default function Verify() { + const [email, setEmail] = useState(); + const router = useRouter(); + const [verified, setVerified] = useState(false); + + useEffect(() => { + const email = Cookies.get("verify_temp_email"); + setEmail(email); + const password = Cookies.get("verify_temp_password"); + + const i = setInterval(async () => { + const { data } = await axios.get("/api/verify", { + params: { email, password }, + }); + const response = data as { + verified: boolean; + user_id: string; + access_token?: string; + refresh_token?: string; + }; + if (response.verified) { + clearInterval(i); + if (response.access_token) { + setVerified(true); + toast.info("Email verified, redirecting..."); + Cookies.set("token", response.access_token); + Cookies.remove("verify_temp_email"); + Cookies.remove("verify_temp_password"); + setTimeout(() => router.push("/chat"), 500); + } else { + console.log(response); + } + } + }, 8000); + }, []); + + return ( +
+ + + Verify Email + + {verified ? ( + "Successfully verified" + ) : ( + <> + We sent you a verification email at {email}. Please verify to + continue. + + )} + + + + Current status: {verified ? "Verified" : "Unverified"} + + +
+ ); +} diff --git a/src/components/MessageBox.tsx b/src/components/MessageBox.tsx index d1cc086..b846f7f 100644 --- a/src/components/MessageBox.tsx +++ b/src/components/MessageBox.tsx @@ -10,8 +10,8 @@ import { DropdownMenuContent, DropdownMenuTrigger, } from "./ui/dropdown-menu"; -import { ChevronLeftIcon, SmilePlusIcon } from "lucide-react"; -import { useEffect, useState } from "react"; +import { ChevronLeftIcon, SendIcon, SmilePlusIcon } from "lucide-react"; +import { useEffect, useRef, useState } from "react"; import { format, isToday, isYesterday } from "date-fns"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; @@ -19,6 +19,8 @@ import { UserProfile } from "@/hooks/get-user"; import axios from "axios"; import ProfilePicture from "./ProfilePicture"; import App from "@/types/app"; +import { useIsMobile } from "@/hooks/use-mobile"; +import { Textarea } from "./ui/textarea"; function MessageContainer({ message, app }: { message: Message; app: App }) { useEffect(() => { @@ -41,14 +43,14 @@ function MessageContainer({ message, app }: { message: Message; app: App }) { url={app.profiles[message.from]?.avatar_url} name={String( app.profiles[message.from] - ? app.profiles[message.from].display_name + ? app.profiles[message.from]?.display_name : message.from )} />
{app.profiles[message.from] - ? app.profiles[message.from].display_name + ? app.profiles[message.from]?.display_name : message.from}

@@ -142,13 +144,15 @@ export default function MessageBox({ messages: Message[]; }) { const [text, setText] = useState(""); + const isMobile = useIsMobile(); + const inputRef = useRef(null); return (

{channelName && (
app.setSidebarOpen(true)} + onClick={() => isMobile && app.setSidebarOpen(true)} className="cursor-pointer flex" > @@ -156,27 +160,70 @@ export default function MessageBox({
)} -
+
{messages.toReversed().map((msg) => ( ))}
-