Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 18 additions & 15 deletions src/app/(auth)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ReactQueryProvider } from "@/components/providers/ReactQueryProvider";
import InstructionPanel from "@/components/auth/InstructionPanel";
import Image from "next/image";

Expand All @@ -7,23 +8,25 @@ export default function AuthLayout({
children: React.ReactNode;
}) {
return (
<div className="flex h-screen">
<div className="flex w-[60%] flex-col">
<div className="w-full p-5">
<Image
src="/InfraJuno.png"
alt="Juno"
width={180}
height={30}
className="mb-10"
/>
<ReactQueryProvider>
<div className="flex h-screen">
<div className="flex w-[60%] flex-col">
<div className="w-full p-5">
<Image
src="/InfraJuno.png"
alt="Juno"
width={180}
height={30}
className="mb-10"
/>
</div>
<div className="flex flex-col items-center">{children}</div>
</div>
<div className="flex flex-col items-center">{children}</div>
</div>

<div className="relative w-[40%] overflow-visible bg-neutral-950">
<InstructionPanel />
<div className="relative w-[40%] overflow-visible bg-neutral-950">
<InstructionPanel />
</div>
</div>
</div>
</ReactQueryProvider>
);
}
80 changes: 42 additions & 38 deletions src/app/(auth)/request-account/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/auth/ui";
import { Alert } from "@/components/ui/alert";
import {
Form,
FormControl,
Expand All @@ -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
Expand Down Expand Up @@ -52,9 +54,7 @@ const requestAccountSchema = z
type RequestAccountValues = z.infer<typeof requestAccountSchema>;

const RequestAccountPage = () => {
const [error, setError] = useState("");
const [success, setSuccess] = useState(false);
const [loading, setLoading] = useState(false);

const form = useForm<RequestAccountValues>({
resolver: zodResolver(requestAccountSchema),
Expand All @@ -69,29 +69,30 @@ const RequestAccountPage = () => {

const userType = form.watch("userType");

async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
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 (
Expand Down Expand Up @@ -120,17 +121,14 @@ const RequestAccountPage = () => {
Request new account
</h2>

{error.length > 0 && (
<Alert className="mb-4 border-red-500/20 bg-red-500/10">
<div className="flex items-center gap-2 text-red-400">
<CircleX className="h-4 w-4" />
<span className="text-sm">{error}</span>
</div>
</Alert>
)}

<Form {...form}>
<form onSubmit={handleSubmit} noValidate className="space-y-4">
<form
onSubmit={form.handleSubmit((values) =>
requestAccountMutation.mutate(values),
)}
noValidate
className="space-y-4"
>
<FormField
control={form.control}
name="name"
Expand Down Expand Up @@ -233,8 +231,14 @@ const RequestAccountPage = () => {
/>
)}

<Button type="submit" className="mt-2 w-full" disabled={loading}>
{loading && <Loader2 className="animate-spin" />}
<Button
type="submit"
className="mt-2 w-full"
disabled={requestAccountMutation.isPending}
>
{requestAccountMutation.isPending && (
<Loader2 className="animate-spin" />
)}
Request Account
</Button>
</form>
Expand Down
35 changes: 22 additions & 13 deletions src/components/admin/PendingAccountRequests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 { useMemo, useState } from "react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
Expand All @@ -12,7 +12,7 @@ import { Skeleton } from "@/components/ui/skeleton";
import {
acceptAccountRequest,
AccountRequest,
declineAccountRequest,
deleteAccountRequest,
getAccountRequests,
} from "@/lib/accountRequests";

Expand All @@ -33,18 +33,21 @@ export function PendingAccountRequests() {
const [processingIds, setProcessingIds] = useState<Set<string>>(new Set());
const queryClient = useQueryClient();

const { data, isLoading, isError, error } = useQuery({
const { data, isLoading } = useQuery({
queryKey: ["accountRequests"],
queryFn: getAccountRequests,
});

const requests = data?.success ? data.requests : [];

if (isError) {
toast.error("Error", {
description: `Failed to fetch account requests: ${JSON.stringify(error)}`,
});
}
const requests = useMemo(
() =>
data?.success
? [...data.requests].sort(
(a, b) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(),
)
: [],
[data],
);

const setProcessing = (id: string, processing: boolean) => {
setProcessingIds((prev) => {
Expand All @@ -70,6 +73,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.",
Expand All @@ -82,9 +86,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);
Expand Down Expand Up @@ -136,6 +140,11 @@ export function PendingAccountRequests() {
</div>
))}
</div>
) : data && !data.success ? (
<div className="flex flex-col items-center justify-center py-12 text-center text-sm text-muted-foreground gap-2">
<XCircle className="h-8 w-8 text-muted-foreground/40" />
<span>{data.error ?? "Failed to load account requests."}</span>
</div>
) : requests.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12 text-center text-sm text-muted-foreground gap-2">
<CheckCircle className="h-8 w-8 text-muted-foreground/40" />
Expand Down Expand Up @@ -185,7 +194,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"
>
<XCircle className="h-3.5 w-3.5" />
Expand Down
Loading