From a4aff33531e7a5cb803a7086cecb29d4df294862 Mon Sep 17 00:00:00 2001 From: Ben Date: Sun, 21 Jan 2024 11:02:30 +0100 Subject: [PATCH 1/3] IPK-77 Changed all requests to support backend --- src/lib/actions.ts | 141 +++++++++++++++++++++++++-------------------- 1 file changed, 80 insertions(+), 61 deletions(-) diff --git a/src/lib/actions.ts b/src/lib/actions.ts index fed8fde..320f35c 100644 --- a/src/lib/actions.ts +++ b/src/lib/actions.ts @@ -37,18 +37,21 @@ export async function deleteToken() { export async function refreshAccessToken() { try { if (!cookies().get("accessToken")) { - return { status: 401, message: "No access token" }; + return; } await fetch("http://localhost:8080/api/test/refresh", { method: "POST", headers: { - "Content-Type": "application/json" - /* Authorization: cookies().get("accessToken")?.value */ - } - /* body: JSON.stringify({ refreshToken: cookies().get("refreshToken") }) */ + "Content-Type": "application/json", + Authorization: "Bearer " + cookies().get("accessToken")?.value + }, + body: JSON.stringify({ refreshToken: cookies().get("refreshToken") }) }) .then((response) => { - if (!response.ok) { + if (response.status === 401) { + deleteToken(); + throw new Error("Unauthorized"); + } else if (!response.ok) { throw new Error("Network error"); } return response.json(); @@ -67,23 +70,23 @@ export async function refreshAccessToken() { export async function getUser() { try { if (!cookies().get("accessToken")) { - return { status: 401, message: "No access token" }; + throw new Error("Unauthorized"); } - await fetch("http://localhost:8080/api/test/user", { + await fetch("http://localhost:8080/api/user/get", { method: "GET", headers: { - "Content-Type": "application/json" + "Content-Type": "application/json", + Authorization: "Bearer " + cookies().get("accessToken")?.value } - }) - .then((response) => { - if (!response.ok) { - throw new Error("Network error"); - } - return response.json(); - }) - .then((data) => { - return data; - }); + }).then((response) => { + if (response.status === 401) { + deleteToken(); + throw new Error("Unauthorized"); + } else if (!response.ok) { + throw new Error("Network error"); + } + return response.json(); + }); } catch (error) { console.error("There was a problem with the Fetch operation: ", error); } @@ -122,42 +125,36 @@ export async function loginUser(prevState: LoginFormState, formData: FormData): }; } const data = parse.data; + const encodedData = Object.keys(data) + .map((key) => encodeURIComponent(key) + "=" + encodeURIComponent(data[key as keyof typeof data])) + .join("&"); - await new Promise((resolve) => setTimeout(resolve, 1000)); try { - return { - message: "success", - errors: undefined, - fieldValues: { - email: "", - password: "" - } - }; - } catch (error) { - const zodError = error as ZodError; - const errorMap = zodError.flatten().fieldErrors; - return { - message: "error", - errors: { email: errorMap["email"]?.[0] ?? "", password: errorMap["password"]?.[0] ?? "" }, - fieldValues: { email, password } - }; - } - /* try { - await fetch("http://localhost:8080/api/auth/signin", { + const response = await fetch("http://localhost:8080/api/user/login", { method: "POST", headers: { - "Content-Type": "application/json" + "Content-Type": "application/x-www-form-urlencoded" }, - body: JSON.stringify({ - email: formData.get("email"), - password: formData.get("password") - }) - }).then((response) => { - if (!response.ok) { - return { status: response.status, message: response.statusText }; - } - return response.json(); + body: encodedData }); + if (response.ok) { + const data = await response.json(); + storeToken({ token: data.accessToken, refresh_token: data.refreshToken }); + return { + message: "success", + errors: undefined, + fieldValues: { + email: "", + password: "" + } + }; + } else { + return { + message: "error", + errors: { email: "Invalid email or password", password: "Invalid email or password" }, + fieldValues: { email, password } + }; + } } catch (error) { const zodError = error as ZodError; const errorMap = zodError.flatten().fieldErrors; @@ -166,7 +163,7 @@ export async function loginUser(prevState: LoginFormState, formData: FormData): errors: { email: errorMap["email"]?.[0] ?? "", password: errorMap["password"]?.[0] ?? "" }, fieldValues: { email, password } }; - } */ + } } export type SignupFormState = { @@ -211,19 +208,41 @@ export async function registerUser(prevState: SignupFormState, formData: FormDat }; } const data = parse.data; - console.log(data); + const encodedData = Object.keys(data) + .map((key) => encodeURIComponent(key) + "=" + encodeURIComponent(data[key as keyof typeof data])) + .join("&"); try { - return { - message: "Success", - errors: undefined, - fieldValues: { - name: "", - email: "", - password: "", - confirmPassword: "" - } - }; + const response = await fetch("http://localhost:8080/api/user/signup", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded" + }, + body: encodedData + }); + if (response.ok) { + return { + message: "success", + errors: undefined, + fieldValues: { + name: "", + email: "", + password: "", + confirmPassword: "" + } + }; + } else { + return { + message: "error", + errors: { + name: "Something went wrong", + email: "Something went wrong", + password: "Something went wrong", + confirmPassword: "Something went wrong" + }, + fieldValues: { name, email, password, confirmPassword } + }; + } } catch (error) { const zodError = error as ZodError; const errorMap = zodError.flatten().fieldErrors; From a383e5bdef11bed64967e97ec26ecb7f4fd10450 Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Sun, 21 Jan 2024 10:03:20 +0000 Subject: [PATCH 2/3] style: format code with Prettier This commit fixes the style issues introduced in a4aff33 according to the output from Prettier. Details: https://github.com/Informatik-Projekt-Kurs/frontend/pull/30 --- src/components/ui/toast.tsx | 81 +++++++---------- src/components/ui/toaster.tsx | 23 ++--- src/components/ui/use-toast.ts | 153 ++++++++++++++++----------------- 3 files changed, 113 insertions(+), 144 deletions(-) diff --git a/src/components/ui/toast.tsx b/src/components/ui/toast.tsx index a822477..28a233b 100644 --- a/src/components/ui/toast.tsx +++ b/src/components/ui/toast.tsx @@ -1,11 +1,11 @@ -import * as React from "react" -import * as ToastPrimitives from "@radix-ui/react-toast" -import { cva, type VariantProps } from "class-variance-authority" -import { X } from "lucide-react" +import * as React from "react"; +import * as ToastPrimitives from "@radix-ui/react-toast"; +import { cva, type VariantProps } from "class-variance-authority"; +import { X } from "lucide-react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; -const ToastProvider = ToastPrimitives.Provider +const ToastProvider = ToastPrimitives.Provider; const ToastViewport = React.forwardRef< React.ElementRef, @@ -19,8 +19,8 @@ const ToastViewport = React.forwardRef< )} {...props} /> -)) -ToastViewport.displayName = ToastPrimitives.Viewport.displayName +)); +ToastViewport.displayName = ToastPrimitives.Viewport.displayName; const toastVariants = cva( "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", @@ -28,30 +28,22 @@ const toastVariants = cva( variants: { variant: { default: "border bg-background text-foreground", - destructive: - "destructive group border-destructive bg-destructive text-destructive-foreground", - }, + destructive: "destructive group border-destructive bg-destructive text-destructive-foreground" + } }, defaultVariants: { - variant: "default", - }, + variant: "default" + } } -) +); const Toast = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef & - VariantProps + React.ComponentPropsWithoutRef & VariantProps >(({ className, variant, ...props }, ref) => { - return ( - - ) -}) -Toast.displayName = ToastPrimitives.Root.displayName + return ; +}); +Toast.displayName = ToastPrimitives.Root.displayName; const ToastAction = React.forwardRef< React.ElementRef, @@ -65,8 +57,8 @@ const ToastAction = React.forwardRef< )} {...props} /> -)) -ToastAction.displayName = ToastPrimitives.Action.displayName +)); +ToastAction.displayName = ToastPrimitives.Action.displayName; const ToastClose = React.forwardRef< React.ElementRef, @@ -79,40 +71,31 @@ const ToastClose = React.forwardRef< className )} toast-close="" - {...props} - > + {...props}> -)) -ToastClose.displayName = ToastPrimitives.Close.displayName +)); +ToastClose.displayName = ToastPrimitives.Close.displayName; const ToastTitle = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -ToastTitle.displayName = ToastPrimitives.Title.displayName + +)); +ToastTitle.displayName = ToastPrimitives.Title.displayName; const ToastDescription = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -ToastDescription.displayName = ToastPrimitives.Description.displayName + +)); +ToastDescription.displayName = ToastPrimitives.Description.displayName; -type ToastProps = React.ComponentPropsWithoutRef +type ToastProps = React.ComponentPropsWithoutRef; -type ToastActionElement = React.ReactElement +type ToastActionElement = React.ReactElement; export { type ToastProps, @@ -123,5 +106,5 @@ export { ToastTitle, ToastDescription, ToastClose, - ToastAction, -} + ToastAction +}; diff --git a/src/components/ui/toaster.tsx b/src/components/ui/toaster.tsx index e223385..9f42138 100644 --- a/src/components/ui/toaster.tsx +++ b/src/components/ui/toaster.tsx @@ -1,17 +1,10 @@ -"use client" +"use client"; -import { - Toast, - ToastClose, - ToastDescription, - ToastProvider, - ToastTitle, - ToastViewport, -} from "@/components/ui/toast" -import { useToast } from "@/components/ui/use-toast" +import { Toast, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport } from "@/components/ui/toast"; +import { useToast } from "@/components/ui/use-toast"; export function Toaster() { - const { toasts } = useToast() + const { toasts } = useToast(); return ( @@ -20,16 +13,14 @@ export function Toaster() {
{title && {title}} - {description && ( - {description} - )} + {description && {description}}
{action}
- ) + ); })}
- ) + ); } diff --git a/src/components/ui/use-toast.ts b/src/components/ui/use-toast.ts index 1671307..c62a8eb 100644 --- a/src/components/ui/use-toast.ts +++ b/src/components/ui/use-toast.ts @@ -1,104 +1,99 @@ // Inspired by react-hot-toast library -import * as React from "react" +import * as React from "react"; -import type { - ToastActionElement, - ToastProps, -} from "@/components/ui/toast" +import type { ToastActionElement, ToastProps } from "@/components/ui/toast"; -const TOAST_LIMIT = 1 -const TOAST_REMOVE_DELAY = 1000000 +const TOAST_LIMIT = 1; +const TOAST_REMOVE_DELAY = 1000000; type ToasterToast = ToastProps & { - id: string - title?: React.ReactNode - description?: React.ReactNode - action?: ToastActionElement -} + id: string; + title?: React.ReactNode; + description?: React.ReactNode; + action?: ToastActionElement; +}; const actionTypes = { ADD_TOAST: "ADD_TOAST", UPDATE_TOAST: "UPDATE_TOAST", DISMISS_TOAST: "DISMISS_TOAST", - REMOVE_TOAST: "REMOVE_TOAST", -} as const + REMOVE_TOAST: "REMOVE_TOAST" +} as const; -let count = 0 +let count = 0; function genId() { - count = (count + 1) % Number.MAX_SAFE_INTEGER - return count.toString() + count = (count + 1) % Number.MAX_SAFE_INTEGER; + return count.toString(); } -type ActionType = typeof actionTypes +type ActionType = typeof actionTypes; type Action = | { - type: ActionType["ADD_TOAST"] - toast: ToasterToast + type: ActionType["ADD_TOAST"]; + toast: ToasterToast; } | { - type: ActionType["UPDATE_TOAST"] - toast: Partial + type: ActionType["UPDATE_TOAST"]; + toast: Partial; } | { - type: ActionType["DISMISS_TOAST"] - toastId?: ToasterToast["id"] + type: ActionType["DISMISS_TOAST"]; + toastId?: ToasterToast["id"]; } | { - type: ActionType["REMOVE_TOAST"] - toastId?: ToasterToast["id"] - } + type: ActionType["REMOVE_TOAST"]; + toastId?: ToasterToast["id"]; + }; interface State { - toasts: ToasterToast[] + toasts: ToasterToast[]; } -const toastTimeouts = new Map>() +const toastTimeouts = new Map>(); const addToRemoveQueue = (toastId: string) => { if (toastTimeouts.has(toastId)) { - return + return; } const timeout = setTimeout(() => { - toastTimeouts.delete(toastId) + toastTimeouts.delete(toastId); dispatch({ type: "REMOVE_TOAST", - toastId: toastId, - }) - }, TOAST_REMOVE_DELAY) + toastId: toastId + }); + }, TOAST_REMOVE_DELAY); - toastTimeouts.set(toastId, timeout) -} + toastTimeouts.set(toastId, timeout); +}; export const reducer = (state: State, action: Action): State => { switch (action.type) { case "ADD_TOAST": return { ...state, - toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), - } + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT) + }; case "UPDATE_TOAST": return { ...state, - toasts: state.toasts.map((t) => - t.id === action.toast.id ? { ...t, ...action.toast } : t - ), - } + toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t)) + }; case "DISMISS_TOAST": { - const { toastId } = action + const { toastId } = action; // ! Side effects ! - This could be extracted into a dismissToast() action, // but I'll keep it here for simplicity if (toastId) { - addToRemoveQueue(toastId) + addToRemoveQueue(toastId); } else { state.toasts.forEach((toast) => { - addToRemoveQueue(toast.id) - }) + addToRemoveQueue(toast.id); + }); } return { @@ -107,48 +102,48 @@ export const reducer = (state: State, action: Action): State => { t.id === toastId || toastId === undefined ? { ...t, - open: false, + open: false } : t - ), - } + ) + }; } case "REMOVE_TOAST": if (action.toastId === undefined) { return { ...state, - toasts: [], - } + toasts: [] + }; } return { ...state, - toasts: state.toasts.filter((t) => t.id !== action.toastId), - } + toasts: state.toasts.filter((t) => t.id !== action.toastId) + }; } -} +}; -const listeners: Array<(state: State) => void> = [] +const listeners: Array<(state: State) => void> = []; -let memoryState: State = { toasts: [] } +let memoryState: State = { toasts: [] }; function dispatch(action: Action) { - memoryState = reducer(memoryState, action) + memoryState = reducer(memoryState, action); listeners.forEach((listener) => { - listener(memoryState) - }) + listener(memoryState); + }); } -type Toast = Omit +type Toast = Omit; function toast({ ...props }: Toast) { - const id = genId() + const id = genId(); const update = (props: ToasterToast) => dispatch({ type: "UPDATE_TOAST", - toast: { ...props, id }, - }) - const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) + toast: { ...props, id } + }); + const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); dispatch({ type: "ADD_TOAST", @@ -157,36 +152,36 @@ function toast({ ...props }: Toast) { id, open: true, onOpenChange: (open) => { - if (!open) dismiss() - }, - }, - }) + if (!open) dismiss(); + } + } + }); return { id: id, dismiss, - update, - } + update + }; } function useToast() { - const [state, setState] = React.useState(memoryState) + const [state, setState] = React.useState(memoryState); React.useEffect(() => { - listeners.push(setState) + listeners.push(setState); return () => { - const index = listeners.indexOf(setState) + const index = listeners.indexOf(setState); if (index > -1) { - listeners.splice(index, 1) + listeners.splice(index, 1); } - } - }, [state]) + }; + }, [state]); return { ...state, toast, - dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), - } + dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }) + }; } -export { useToast, toast } +export { useToast, toast }; From 8f6e9bc33501cf73c1e4b32fc2c8f416aef5ce33 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 22 Jan 2024 19:00:32 +0100 Subject: [PATCH 3/3] IPK-77 Fixed middleware bug --- src/lib/actions.ts | 7 +++++-- src/middleware.ts | 9 ++++----- src/types/index.d.ts | 7 +++++++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/lib/actions.ts b/src/lib/actions.ts index 320f35c..93996a4 100644 --- a/src/lib/actions.ts +++ b/src/lib/actions.ts @@ -1,5 +1,6 @@ "use server"; +import { User } from "@/types"; import { cookies } from "next/headers"; import { ZodError, z } from "zod"; @@ -67,7 +68,7 @@ export async function refreshAccessToken() { } } -export async function getUser() { +export async function getUser(): Promise { try { if (!cookies().get("accessToken")) { throw new Error("Unauthorized"); @@ -85,10 +86,12 @@ export async function getUser() { } else if (!response.ok) { throw new Error("Network error"); } - return response.json(); + return response.json() as Promise; }); + return null; } catch (error) { console.error("There was a problem with the Fetch operation: ", error); + return null; } } diff --git a/src/middleware.ts b/src/middleware.ts index ec6a21c..ad029e4 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,13 +1,12 @@ import { NextRequest, NextResponse } from "next/server"; import { getUser } from "./lib/actions"; +import { User } from "./types"; export async function middleware(req: NextRequest) { - const user = await getUser(); - if (user?.status !== 200 && user?.status != undefined) { - return NextResponse.redirect(new URL("/login", req.url)); - } + const user = (await getUser()) as User; + if (user) return NextResponse.redirect(new URL("/login", req.url)); try { - if (req.nextUrl.pathname.startsWith("/admin") && user && "role" in user && user?.role !== "ADMIN") { + if (req.nextUrl.pathname.startsWith("/admin") && (user as User).role !== "ADMIN") { return NextResponse.redirect(new URL("/unauthorized", req.url)); } diff --git a/src/types/index.d.ts b/src/types/index.d.ts index d59a7ad..8e686e3 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -28,3 +28,10 @@ type RegisterInputs = { password: string; password_confirmation: string; }; + +type User = { + id: number; + name: string; + email: string; + role: string; +};