Skip to content
This repository has been archived by the owner on Jul 23, 2024. It is now read-only.

Commit

Permalink
edit metrics logic
Browse files Browse the repository at this point in the history
  • Loading branch information
ahmedivy committed Aug 24, 2023
1 parent 953cc31 commit d56ee2e
Show file tree
Hide file tree
Showing 12 changed files with 373 additions and 45 deletions.
27 changes: 17 additions & 10 deletions app/(portal)/layout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import ColorsClassesTw from "@/components/dynamic-cn";
import PortalHeader from "@/components/portal-header";
import AuthProvider from "@/components/providers/auth-provider";
import ThemeProvider from "@/components/providers/theme-provider";
import UserProvider from "@/components/providers/user-provider";

const font = Nunito_Sans({ subsets: ["latin"] });

Expand Down Expand Up @@ -40,16 +41,22 @@ export default async function RootLayout({ children }) {
<body className={cn(`min-h-screen`, font.className)}>
<main className="flex min-h-screen flex-col items-center justify-between">
<AuthProvider>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<PortalHeader />
<Separator />
<div className="w-full h-full flex flex-row flex-grow container mt-3">
<Sidebar />
{children}
</div>
<Footer />
<ColorsClassesTw />
</ThemeProvider>
<UserProvider session={session}>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
>
<PortalHeader />
<Separator />
<div className="w-full h-full flex flex-row flex-grow container mt-3">
<Sidebar />
{children}
</div>
<Footer />
<ColorsClassesTw />
</ThemeProvider>
</UserProvider>
</AuthProvider>
</main>
<Toaster />
Expand Down
Empty file.
19 changes: 19 additions & 0 deletions app/api/users/[id]/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NextResponse } from "next/server";

import prisma from "@/lib/db";

export async function GET(req, {params}) {
const { id } = params;

const user = await prisma.user.findUnique({
where: {
id,
},
});

if (!user) {
return new NextResponse("User not found", { status: 404 });
}

return NextResponse.json(user);
}
120 changes: 93 additions & 27 deletions components/edit-metrics-model.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,60 @@
"use client";

import * as z from "zod";
import { Drawer } from "vaul";
import { useForm } from "react-hook-form";
import { LuSettings2 } from "react-icons/lu";
import { zodResolver } from "@hookform/resolvers/zod";

import {
Dialog,
DialogContent,
DialogTrigger,
} from "@/components/ui/dialog";
import { useUser } from "@/lib/hooks/useUser";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { editMetrics } from "@/lib/actions/editMetrics";
import { useToast } from "./ui/use-toast";

const formSchema = z.object({
dailyDrawdownLimit: z.number().min(0, {
message: "Daily drawdown limit must be greater than 0",
}),
maxLossLimit: z.number().min(0, {
message: "Max loss must be greater than 0",
}),
maxExposureLimit: z.number().min(0, {
message: "Max exposure must be greater than 0",
}),
});

function ModelContent() {
const { user, revalidateUser } = useUser();
const { toast } = useToast();

const form = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
dailyDrawdownLimit: user?.dailyDrawdownLimit,
maxLossLimit: user?.maxLossLimit,
maxExposureLimit: user?.maxExposureLimit,
},
});

function onSumit(values) {
editMetrics(values, user.id);
revalidateUser();

toast({
description: "Risk metrics updated successfully",
});
}

return (
<>
Expand All @@ -25,27 +65,53 @@ function ModelContent() {
</div>
</div>
<div className="grid gap-4 py-4">
<div className="grid items-center gap-2">
<Label htmlFor="name" className="font-semibold">
Daily Drawdown Limit
</Label>
<Input id="name" className="" />
</div>
<div className="grid items-center gap-2">
<Label htmlFor="name" className="font-semibold">
Max Loss (per trade)
</Label>
<Input id="name" className="" />
</div>
<div className="grid items-center gap-2">
<Label htmlFor="name" className="font-semibold">
Max Exposure (per trade)
</Label>
<Input id="name" className="" />
</div>
</div>
<div className="flex ml-auto w-full justify-end">
<Button type="submit">Save changes</Button>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSumit)} className="space-y-4">
<FormField
control={form.control}
name="dailyDrawdownLimit"
render={({ field }) => (
<FormItem>
<FormLabel>Daily Drawdown Limit</FormLabel>
<FormControl>
<Input {...field} type="number" onChange={event => field.onChange(+event.target.value)}/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="maxLossLimit"
render={({ field }) => (
<FormItem>
<FormLabel>Max Loss</FormLabel>
<FormControl>
<Input {...field} type="number" onChange={event => field.onChange(+event.target.value)}/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="maxExposureLimit"
render={({ field }) => (
<FormItem>
<FormLabel>Max Exposure</FormLabel>
<FormControl>
<Input {...field} type="number" onChange={event => field.onChange(+event.target.value)}/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<Button type="submit" className="">
Save Changes
</Button>
</form>
</Form>
</div>
</>
);
Expand Down
11 changes: 11 additions & 0 deletions components/providers/user-provider.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use client";

import { UserContextProvider } from "@/lib/hooks/useUser";

function UserProvider({ session, children }) {
return (
<UserContextProvider session={session}>{children}</UserContextProvider>
);
}

export default UserProvider;
133 changes: 133 additions & 0 deletions components/ui/form.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { Controller, FormProvider, useFormContext } from "react-hook-form";

import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"

const Form = FormProvider

const FormFieldContext = React.createContext({})

const FormField = (
{
...props
}
) => {
return (
(<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>)
);
}

const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()

const fieldState = getFieldState(fieldContext.name, formState)

if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}

const { id } = itemContext

return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}

const FormItemContext = React.createContext({})

const FormItem = React.forwardRef(({ className, ...props }, ref) => {
const id = React.useId()

return (
(<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>)
);
})
FormItem.displayName = "FormItem"

const FormLabel = React.forwardRef(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField()

return (
(<Label
ref={ref}
className={cn(error && "text-destructive", className)}
htmlFor={formItemId}
{...props} />)
);
})
FormLabel.displayName = "FormLabel"

const FormControl = React.forwardRef(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()

return (
(<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props} />)
);
})
FormControl.displayName = "FormControl"

const FormDescription = React.forwardRef(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField()

return (
(<p
ref={ref}
id={formDescriptionId}
className={cn("text-[0.8rem] text-muted-foreground", className)}
{...props} />)
);
})
FormDescription.displayName = "FormDescription"

const FormMessage = React.forwardRef(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message) : children

if (!body) {
return null
}

return (
(<p
ref={ref}
id={formMessageId}
className={cn("text-[0.8rem] font-medium text-destructive", className)}
{...props}>
{body}
</p>)
);
})
FormMessage.displayName = "FormMessage"

export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
}
14 changes: 14 additions & 0 deletions lib/actions/editMetrics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"use server";

export async function editMetrics(values, id) {
const user = await prisma.user.update({
where: {
id,
},
data: {
...values,
},
});

return user;
}
34 changes: 34 additions & 0 deletions lib/hooks/useUser.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useState, useContext, createContext, useEffect, useCallback } from "react";

export const UserContext = createContext(null);

export const UserContextProvider = ({ session, children }) => {
const [user, setUser] = useState(null);
const id = session?.user?.id;

const revalidateUser = useCallback(() => {
fetch(`api/users/${id}`)
.then((res) => res.json())
.then((user) => setUser(user));
}, [id]);

useEffect(() => {
if (session) {
revalidateUser();
}
}, [session, revalidateUser])

return (
<UserContext.Provider value={{ user, revalidateUser }}>
{children}
</UserContext.Provider>
);
};

export const useUser = () => {
const context = useContext(UserContext);
if (context === undefined) {
throw new Error("useUser must be used within a UserContextProvider");
}
return context;
};
Loading

0 comments on commit d56ee2e

Please sign in to comment.