From 002552d93accf5a085cbf867be7e701fc5a39438 Mon Sep 17 00:00:00 2001 From: aakashg00 Date: Mon, 9 Mar 2026 01:10:15 -0400 Subject: [PATCH 1/3] added sdk methods, implemented request new account --- src/app/(auth)/layout.tsx | 33 +++-- src/app/(auth)/request-account/page.tsx | 80 +++++------ src/lib/accountRequests.ts | 174 ++++++++++++------------ 3 files changed, 149 insertions(+), 138 deletions(-) diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx index 61769bb..d516d7f 100644 --- a/src/app/(auth)/layout.tsx +++ b/src/app/(auth)/layout.tsx @@ -1,3 +1,4 @@ +import { ReactQueryProvider } from "@/components/providers/ReactQueryProvider"; import InstructionPanel from "@/components/auth/InstructionPanel"; import Image from "next/image"; @@ -7,23 +8,25 @@ export default function AuthLayout({ children: React.ReactNode; }) { return ( -
-
-
- Juno + +
+
+
+ Juno +
+
{children}
-
{children}
-
-
- +
+ +
-
+ ); } diff --git a/src/app/(auth)/request-account/page.tsx b/src/app/(auth)/request-account/page.tsx index 3ca635b..5e419db 100644 --- a/src/app/(auth)/request-account/page.tsx +++ b/src/app/(auth)/request-account/page.tsx @@ -9,7 +9,6 @@ import { SelectTrigger, SelectValue, } from "@/components/auth/ui"; -import { Alert } from "@/components/ui/alert"; import { Form, FormControl, @@ -19,11 +18,14 @@ import { FormLabel, FormMessage, } from "@/components/ui/form"; +import { requestNewAccount } from "@/lib/accountRequests"; import { zodResolver } from "@hookform/resolvers/zod"; -import { CheckCircle2, CircleX, Loader2 } from "lucide-react"; +import { useMutation } from "@tanstack/react-query"; +import { CheckCircle2, Loader2 } from "lucide-react"; import Link from "next/link"; -import React, { useState } from "react"; +import { useState } from "react"; import { useForm } from "react-hook-form"; +import { toast } from "sonner"; import { z } from "zod"; const requestAccountSchema = z @@ -52,9 +54,7 @@ const requestAccountSchema = z type RequestAccountValues = z.infer; const RequestAccountPage = () => { - const [error, setError] = useState(""); const [success, setSuccess] = useState(false); - const [loading, setLoading] = useState(false); const form = useForm({ resolver: zodResolver(requestAccountSchema), @@ -69,29 +69,30 @@ const RequestAccountPage = () => { const userType = form.watch("userType"); - async function handleSubmit(e: React.FormEvent) { - e.preventDefault(); + const requestAccountMutation = useMutation({ + mutationFn: async (values: RequestAccountValues) => { + const result = await requestNewAccount({ + name: values.name, + email: values.email, + password: values.password, + userType: values.userType === "Admin" ? "ADMIN" : "USER", + projectName: + values.userType === "Admin" ? values.projectName : undefined, + }); - const valid = await form.trigger(); - if (!valid) return; - - setLoading(true); - setError(""); - - /* TODO: Send request to infra team for approval - const values = form.getValues(); - const payload = { - name: values.name, - email: values.email, - password: values.password, - userType: values.userType, - projectName: values.userType === "Admin" ? values.projectName : undefined, - }; - */ + if (!result.success) { + throw new Error(result.error || "Failed to request account."); + } - setSuccess(true); - setLoading(false); - } + return result; + }, + onSuccess: () => setSuccess(true), + onError: (error) => { + toast.error("Failed to request account", { + description: error.message, + }); + }, + }); if (success) { return ( @@ -120,17 +121,14 @@ const RequestAccountPage = () => { Request new account - {error.length > 0 && ( - -
- - {error} -
-
- )} -
- + + requestAccountMutation.mutate(values), + )} + noValidate + className="space-y-4" + > { /> )} - diff --git a/src/lib/accountRequests.ts b/src/lib/accountRequests.ts index 947f885..f857485 100644 --- a/src/lib/accountRequests.ts +++ b/src/lib/accountRequests.ts @@ -1,25 +1,52 @@ "use server"; -import { - createUserAction, - createProjectAction, - linkUserToProject, -} from "./actions"; +import { UserType } from "juno-sdk/build/main/lib/auth"; +import { getJunoInstance } from "./juno"; import { getSession } from "./session"; import { requireAdmin } from "./auth"; export type AccountRequestRole = "ADMIN" | "USER" | "SUPERADMIN"; +type RequestNewAccountInput = { + email: string; + name: string; + password: string; + userType: UserType; + projectName?: string; +}; + export type AccountRequest = { id: string; email: string; name: string; - password: string; userType: AccountRequestRole; projectName?: string; + createdAt: string; }; -// PLACEHOLDER: replace with real SDK call +export async function requestNewAccount(data: RequestNewAccountInput): Promise<{ + success: boolean; + error?: string; +}> { + try { + const juno = getJunoInstance(); + await juno.auth.requestNewAccount(data); + + return { + success: true, + }; + } catch (error) { + console.error("Error requesting new account:", error.body); + return { + success: false, + error: + error?.body?.message || + error?.message || + "Failed to request new account.", + }; + } +} + export async function getAccountRequests(): Promise<{ success: boolean; requests: AccountRequest[]; @@ -36,37 +63,36 @@ export async function getAccountRequests(): Promise<{ }; } - return { - success: true, - requests: [ - { - id: "1", - name: "Alice Johnson", - email: "alice.johnson@example.com", - password: "placeholder", - userType: "ADMIN", - projectName: "Apollo", - }, - { - id: "2", - name: "Marcus Lee", - email: "marcus.lee@example.com", - password: "placeholder", - userType: "USER", - }, - { - id: "3", - name: "Priya Nair", - email: "priya.nair@example.com", - password: "placeholder", - userType: "ADMIN", - projectName: "Hermes", - }, - ], - }; + try { + const juno = getJunoInstance(); + const result = await juno.auth.getAllAccountRequests({ + credentials: session.jwt, + }); + + return { + success: true, + requests: (result.requests || []).map((request) => ({ + id: String(request.id), + email: request.email, + name: request.name, + userType: String(request.userType) as unknown as AccountRequestRole, + projectName: request.projectName, + createdAt: request.createdAt, + })), + }; + } catch (error) { + console.error("Error fetching account requests:", error); + return { + success: false, + requests: [], + error: + error?.body?.message || + error?.message || + "Failed to fetch account requests.", + }; + } } -// PLACEHOLDER: replace with real SDK call export async function deleteAccountRequest(id: string): Promise<{ success: boolean; error?: string; @@ -78,61 +104,39 @@ export async function deleteAccountRequest(id: string): Promise<{ return { success: false, error: "Only admins can manage account requests" }; } - const success = id === id; - return { success }; + if (!id.trim()) { + return { success: false, error: "Account request ID is required" }; + } + + try { + const juno = getJunoInstance(); + await juno.auth.deleteAccountRequest({ + id, + credentials: session.jwt, + }); + return { success: true }; + } catch (error) { + console.error("Error deleting account request:", error); + return { + success: false, + error: + error?.body?.message || + error?.message || + "Failed to delete account request.", + }; + } } export async function acceptAccountRequest(request: AccountRequest): Promise<{ success: boolean; error?: string; }> { - const userResult = await createUserAction({ - name: request.name, - email: request.email, - password: request.password, - }); - - if (!userResult.success) { - const raw = String(userResult.error ?? ""); - const errorMessage = raw.includes("ALREADY_EXISTS") - ? `An account with the email ${request.email} already exists.` - : "An unexpected error occurred while creating the account. Please try again."; - return { success: false, error: errorMessage }; - } - - if (request.userType === "ADMIN" && request.projectName) { - const projectResult = await createProjectAction({ - projectName: request.projectName, - }); - - if (!projectResult.success) { - return { - success: false, - error: `User created but failed to create project: ${projectResult.error}`, - }; - } - - const linkResult = await linkUserToProject({ - projectName: request.projectName, - userId: String(userResult.user.id), - }); - - if (!linkResult.success) { - return { - success: false, - error: `User and project created but failed to link them: ${linkResult.error}`, - }; - } - } - - const deleteResult = await deleteAccountRequest(request.id); - if (!deleteResult.success) { - console.error( - `Failed to delete account request ${request.id}: ${deleteResult.error}`, - ); - } - - return { success: true }; + console.log("acceptAccountRequest", request); + return { + success: false, + error: + "Accepting account requests is not supported yet because the API does not expose an approval route or the stored request password.", + }; } export async function declineAccountRequest(id: string): Promise<{ From 53897cefe79d6d5c2dd4c15ce85bd21f36175355 Mon Sep 17 00:00:00 2001 From: aakashg00 Date: Mon, 9 Mar 2026 02:48:57 -0400 Subject: [PATCH 2/3] implemented accept/decline account requests --- .../admin/PendingAccountRequests.tsx | 46 ++++++++--- src/lib/accountRequests.ts | 78 +++++++++++++++---- 2 files changed, 97 insertions(+), 27 deletions(-) diff --git a/src/components/admin/PendingAccountRequests.tsx b/src/components/admin/PendingAccountRequests.tsx index 71c8255..9c3fb8a 100644 --- a/src/components/admin/PendingAccountRequests.tsx +++ b/src/components/admin/PendingAccountRequests.tsx @@ -3,7 +3,7 @@ import { CheckCircle, XCircle } from "lucide-react"; import { toast } from "sonner"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; @@ -12,7 +12,7 @@ import { Skeleton } from "@/components/ui/skeleton"; import { acceptAccountRequest, AccountRequest, - declineAccountRequest, + deleteAccountRequest, getAccountRequests, } from "@/lib/accountRequests"; @@ -38,13 +38,31 @@ export function PendingAccountRequests() { queryFn: getAccountRequests, }); - const requests = data?.success ? data.requests : []; + const requests = useMemo( + () => + data?.success + ? [...data.requests].sort( + (a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + ) + : [], + [data], + ); - if (isError) { - toast.error("Error", { - description: `Failed to fetch account requests: ${JSON.stringify(error)}`, - }); - } + useEffect(() => { + if (isError) { + toast.error("Error", { + description: `Failed to fetch account requests: ${JSON.stringify(error)}`, + }); + return; + } + + if (data && !data.success && data.error) { + toast.error("Failed to Fetch Account Requests", { + description: data.error, + }); + } + }, [data, error, isError]); const setProcessing = (id: string, processing: boolean) => { setProcessingIds((prev) => { @@ -70,6 +88,7 @@ export function PendingAccountRequests() { }); queryClient.invalidateQueries({ queryKey: ["accountRequests"] }); queryClient.invalidateQueries({ queryKey: ["users"] }); + queryClient.invalidateQueries({ queryKey: ["projects"] }); } else { toast.error("Failed to Accept Request", { description: result.error ?? "An unexpected error occurred.", @@ -82,9 +101,9 @@ export function PendingAccountRequests() { }, }); - const declineRequestHandler = useMutation({ + const deleteRequestHandler = useMutation({ mutationFn: async (request: AccountRequest) => - declineAccountRequest(request.id), + deleteAccountRequest(request.id), onMutate: (request) => setProcessing(request.id, true), onSuccess: (result, request) => { setProcessing(request.id, false); @@ -136,6 +155,11 @@ export function PendingAccountRequests() {
))}
+ ) : data && !data.success ? ( +
+ + {data.error ?? "Failed to load account requests."} +
) : requests.length === 0 ? (
@@ -185,7 +209,7 @@ export function PendingAccountRequests() { size="sm" variant="outline" disabled={isProcessing} - onClick={() => declineRequestHandler.mutate(request)} + onClick={() => deleteRequestHandler.mutate(request)} className="gap-1.5 border-destructive text-destructive hover:bg-destructive hover:text-destructive-foreground" > diff --git a/src/lib/accountRequests.ts b/src/lib/accountRequests.ts index f857485..6f56dbc 100644 --- a/src/lib/accountRequests.ts +++ b/src/lib/accountRequests.ts @@ -24,6 +24,22 @@ export type AccountRequest = { createdAt: string; }; +type AcceptAccountRequestResult = { + success: boolean; + error?: string; + user?: { + id: number; + email: string; + name: string; + type: AccountRequestRole; + projectIds?: number[]; + }; + project?: { + id: number; + name: string; + }; +}; + export async function requestNewAccount(data: RequestNewAccountInput): Promise<{ success: boolean; error?: string; @@ -127,21 +143,51 @@ export async function deleteAccountRequest(id: string): Promise<{ } } -export async function acceptAccountRequest(request: AccountRequest): Promise<{ - success: boolean; - error?: string; -}> { - console.log("acceptAccountRequest", request); - return { - success: false, - error: - "Accepting account requests is not supported yet because the API does not expose an approval route or the stored request password.", - }; -} +export async function acceptAccountRequest( + request: AccountRequest, +): Promise { + const session = await getSession(); + if (!session) return { success: false, error: "Unauthorized" }; -export async function declineAccountRequest(id: string): Promise<{ - success: boolean; - error?: string; -}> { - return deleteAccountRequest(id); + if (!requireAdmin(session.user)) { + return { success: false, error: "Only admins can manage account requests" }; + } + + if (!request.id.trim()) { + return { success: false, error: "Account request ID is required" }; + } + + try { + const juno = getJunoInstance(); + const result = await juno.auth.acceptAccountRequest({ + id: request.id, + credentials: session.jwt, + }); + + return { + success: true, + user: { + id: result.user.id, + email: result.user.email, + name: result.user.name, + type: String(result.user.type) as unknown as AccountRequestRole, + projectIds: result.user.projectIds, + }, + project: result.project + ? { + id: result.project.id, + name: result.project.name, + } + : undefined, + }; + } catch (error) { + console.error("Error accepting account request:", error); + return { + success: false, + error: + error?.body?.message || + error?.message || + "Failed to accept account request.", + }; + } } From 3718448a02696d7dcb168fe5362d011df5288e86 Mon Sep 17 00:00:00 2001 From: aakashg00 Date: Mon, 9 Mar 2026 03:55:39 -0400 Subject: [PATCH 3/3] remove console errors, remove toast on get requests error --- .../admin/PendingAccountRequests.tsx | 19 ++----------------- src/lib/accountRequests.ts | 4 ---- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/src/components/admin/PendingAccountRequests.tsx b/src/components/admin/PendingAccountRequests.tsx index 9c3fb8a..5ac65f1 100644 --- a/src/components/admin/PendingAccountRequests.tsx +++ b/src/components/admin/PendingAccountRequests.tsx @@ -3,7 +3,7 @@ import { CheckCircle, XCircle } from "lucide-react"; import { toast } from "sonner"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { useEffect, useMemo, useState } from "react"; +import { useMemo, useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; @@ -33,7 +33,7 @@ export function PendingAccountRequests() { const [processingIds, setProcessingIds] = useState>(new Set()); const queryClient = useQueryClient(); - const { data, isLoading, isError, error } = useQuery({ + const { data, isLoading } = useQuery({ queryKey: ["accountRequests"], queryFn: getAccountRequests, }); @@ -49,21 +49,6 @@ export function PendingAccountRequests() { [data], ); - useEffect(() => { - if (isError) { - toast.error("Error", { - description: `Failed to fetch account requests: ${JSON.stringify(error)}`, - }); - return; - } - - if (data && !data.success && data.error) { - toast.error("Failed to Fetch Account Requests", { - description: data.error, - }); - } - }, [data, error, isError]); - const setProcessing = (id: string, processing: boolean) => { setProcessingIds((prev) => { const next = new Set(prev); diff --git a/src/lib/accountRequests.ts b/src/lib/accountRequests.ts index 6f56dbc..76982d4 100644 --- a/src/lib/accountRequests.ts +++ b/src/lib/accountRequests.ts @@ -52,7 +52,6 @@ export async function requestNewAccount(data: RequestNewAccountInput): Promise<{ success: true, }; } catch (error) { - console.error("Error requesting new account:", error.body); return { success: false, error: @@ -97,7 +96,6 @@ export async function getAccountRequests(): Promise<{ })), }; } catch (error) { - console.error("Error fetching account requests:", error); return { success: false, requests: [], @@ -132,7 +130,6 @@ export async function deleteAccountRequest(id: string): Promise<{ }); return { success: true }; } catch (error) { - console.error("Error deleting account request:", error); return { success: false, error: @@ -181,7 +178,6 @@ export async function acceptAccountRequest( : undefined, }; } catch (error) { - console.error("Error accepting account request:", error); return { success: false, error: