Create resource working

This commit is contained in:
Owen Schwartz 2024-10-19 16:19:47 -04:00
parent 0ff183796c
commit 091d649997
No known key found for this signature in database
GPG key ID: 8271FDFFD9E0CCBD
17 changed files with 721 additions and 130 deletions

View file

@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import { z } from 'zod'; import { z } from 'zod';
import { db } from '@server/db'; import { db } from '@server/db';
import { resources, roleResources, roles, userResources } from '@server/db/schema'; import { orgs, resources, roleResources, roles, userResources } from '@server/db/schema';
import response from "@server/utils/response"; import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode'; import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors'; import createHttpError from 'http-errors';
@ -10,7 +10,7 @@ import logger from '@server/logger';
import { eq, and } from 'drizzle-orm'; import { eq, and } from 'drizzle-orm';
const createResourceParamsSchema = z.object({ const createResourceParamsSchema = z.object({
siteId: z.number().int().positive(), siteId: z.string().transform(Number).pipe(z.number().int().positive()),
orgId: z.string() orgId: z.string()
}); });
@ -58,8 +58,23 @@ export async function createResource(req: Request, res: Response, next: NextFunc
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have a role')); return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have a role'));
} }
// get the org
const org = await db.select()
.from(orgs)
.where(eq(orgs.orgId, orgId))
.limit(1);
if (org.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Organization with ID ${orgId} not found`
)
);
}
// Generate a unique resourceId // Generate a unique resourceId
const resourceId = "subdomain" // TODO: create the subdomain here const resourceId = `${subdomain}.${org[0].domain}`;
// Create new resource in the database // Create new resource in the database
const newResource = await db.insert(resources).values({ const newResource = await db.insert(resources).values({
@ -70,8 +85,6 @@ export async function createResource(req: Request, res: Response, next: NextFunc
subdomain, subdomain,
}).returning(); }).returning();
// find the superuser roleId and also add the resource to the superuser role // find the superuser roleId and also add the resource to the superuser role
const superuserRole = await db.select() const superuserRole = await db.select()
.from(roles) .from(roles)
@ -108,7 +121,7 @@ export async function createResource(req: Request, res: Response, next: NextFunc
status: HttpCode.CREATED, status: HttpCode.CREATED,
}); });
} catch (error) { } catch (error) {
logger.error(error); throw error;
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred...")); return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
} }
} }

View file

@ -14,6 +14,13 @@ const getResourceSchema = z.object({
resourceId: z.string().uuid() resourceId: z.string().uuid()
}); });
export type GetResourceResponse = {
resourceId: string;
siteId: number;
orgId: string;
name: string;
}
export async function getResource(req: Request, res: Response, next: NextFunction): Promise<any> { export async function getResource(req: Request, res: Response, next: NextFunction): Promise<any> {
try { try {
// Validate request parameters // Validate request parameters
@ -51,7 +58,12 @@ export async function getResource(req: Request, res: Response, next: NextFunctio
} }
return response(res, { return response(res, {
data: resource[0], data: {
resourceId: resource[0].resourceId,
siteId: resource[0].siteId,
orgId: resource[0].orgId,
name: resource[0].name
},
success: true, success: true,
error: false, error: false,
message: "Resource retrieved successfully", message: "Resource retrieved successfully",

View file

@ -16,7 +16,7 @@ import logger from "@server/logger";
const listResourcesParamsSchema = z const listResourcesParamsSchema = z
.object({ .object({
siteId: z.number().int().positive().optional(), siteId: z.string().optional().transform(Number).pipe(z.number().int().positive()),
orgId: z.string().optional(), orgId: z.string().optional(),
}) })
.refine((data) => !!data.siteId !== !!data.orgId, { .refine((data) => !!data.siteId !== !!data.orgId, {

View file

@ -0,0 +1,51 @@
"use client";
import { SidebarNav } from "@app/components/sidebar-nav";
import { useResourceContext } from "@app/hooks/useResourceContext";
const sidebarNavItems = [
{
title: "General",
href: "/{orgId}/resources/{resourceId}",
},
// {
// title: "Appearance",
// href: "/{orgId}/resources/{resourceId}/appearance",
// },
// {
// title: "Notifications",
// href: "/{orgId}/resources/{resourceId}/notifications",
// },
]
export function ClientLayout({ isCreate, children }: { isCreate: boolean; children: React.ReactNode }) {
const { resource } = useResourceContext();
return (<div className="hidden space-y-6 0 pb-16 md:block">
<div className="space-y-0.5">
<h2 className="text-2xl font-bold tracking-tight">
{isCreate
? "New Resource"
: resource?.name + " Settings"}
</h2>
<p className="text-muted-foreground">
{isCreate
? "Create a new resource"
: "Configure the settings on your resource: " +
resource?.name || ""}
.
</p>
</div>
<div className="flex flex-col space-y-6 lg:flex-row lg:space-x-12 lg:space-y-0">
<aside className="-mx-4 lg:w-1/5">
<SidebarNav
items={sidebarNavItems}
disabled={isCreate}
/>
</aside>
<div className="flex-1 lg:max-w-2xl">
{children}
</div>
</div>
</div>);
}

View file

@ -0,0 +1,241 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { CalendarIcon, CaretSortIcon, CheckIcon, ChevronDownIcon } from "@radix-ui/react-icons"
import { useForm } from "react-hook-form"
import { z } from "zod"
import { cn } from "@/lib/utils"
import { toast } from "@/hooks/use-toast"
import { Button, buttonVariants } from "@/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import React, { useState, useEffect } from "react";
import { api } from "@/api";
import { useParams } from "next/navigation";
import { useRouter } from "next/navigation";
import { Checkbox } from "@app/components/ui/checkbox"
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import { ListSitesResponse } from "@server/routers/site"
import { AxiosResponse } from "axios"
import CustomDomainInput from "./CustomDomainInput"
const method = [
{ label: "Wireguard", value: "wg" },
{ label: "Newt", value: "newt" },
] as const;
const accountFormSchema = z.object({
subdomain: z
.string()
.min(2, {
message: "Name must be at least 2 characters.",
})
.max(30, {
message: "Name must not be longer than 30 characters.",
}),
name: z.string(),
siteId: z.number()
});
type AccountFormValues = z.infer<typeof accountFormSchema>;
const defaultValues: Partial<AccountFormValues> = {
subdomain: "someanimalherefromapi",
name: "My Resource"
};
export function CreateResourceForm() {
const params = useParams();
const orgId = params.orgId;
const router = useRouter();
const [sites, setSites] = useState<ListSitesResponse["sites"]>([]);
const [domainSuffix, setDomainSuffix] = useState<string>(".example.com");
const form = useForm<AccountFormValues>({
resolver: zodResolver(accountFormSchema),
defaultValues,
});
useEffect(() => {
if (typeof window !== "undefined") {
const fetchSites = async () => {
const res = await api.get<AxiosResponse<ListSitesResponse>>(`/org/${orgId}/sites/`);
setSites(res.data.data.sites);
};
fetchSites();
}
}, []);
async function onSubmit(data: AccountFormValues) {
console.log(data);
const res = await api
.put(`/org/${orgId}/site/${data.siteId}/resource/`, {
name: data.name,
subdomain: data.subdomain,
// subdomain: data.subdomain,
})
.catch((e) => {
toast({
title: "Error creating resource..."
});
});
if (res && res.status === 201) {
const niceId = res.data.data.niceId;
// navigate to the resource page
router.push(`/${orgId}/resources/${niceId}`);
}
}
return (
<>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="Your name" {...field} />
</FormControl>
<FormDescription>
This is the name that will be displayed for this resource.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="subdomain"
render={({ field }) => (
<FormItem>
<FormLabel>Subdomain</FormLabel>
<FormControl>
{/* <Input placeholder="Your name" {...field} /> */}
<CustomDomainInput {...field}
domainSuffix={domainSuffix}
placeholder="Enter subdomain"
/>
</FormControl>
<FormDescription>
This is the fully qualified domain name that will be used to access the resource.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
{/* <FormField
control={form.control}
name="subdomain"
render={({ field }) => (
<FormItem>
<FormLabel>Subdomain</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
The subdomain of the resource. This will be used to access resources on the resource.
</FormDescription>
<FormMessage />
</FormItem>
)}
/> */}
<FormField
control={form.control}
name="siteId"
render={({ field }) => (
<FormItem className="flex flex-col">
<FormLabel>Site</FormLabel>
<Popover>
<PopoverTrigger asChild>
<FormControl>
<Button
variant="outline"
role="combobox"
className={cn(
"w-[200px] justify-between",
!field.value && "text-muted-foreground"
)}
>
{field.value
? sites.find(
(site) => site.siteId === field.value
)?.name
: "Select site"}
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="w-[200px] p-0">
<Command>
<CommandInput placeholder="Search site..." />
<CommandList>
<CommandEmpty>No site found.</CommandEmpty>
<CommandGroup>
{sites.map((site) => (
<CommandItem
value={site.name}
key={site.siteId}
onSelect={() => {
form.setValue("siteId", site.siteId)
}}
>
<CheckIcon
className={cn(
"mr-2 h-4 w-4",
site.siteId === field.value
? "opacity-100"
: "opacity-0"
)}
/>
{site.name}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
<FormDescription>
This is the site that will be used in the dashboard.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Create Resource</Button>
</form>
</Form>
</>
);
}

View file

@ -0,0 +1,45 @@
"use client"
import * as React from "react"
import { Input } from "@/components/ui/input"
interface CustomDomainInputProps {
domainSuffix: string
placeholder?: string
onChange?: (value: string) => void
}
export default function CustomDomainInput({
domainSuffix,
placeholder = "Enter subdomain",
onChange
}: CustomDomainInputProps = {
domainSuffix: ".example.com"
}) {
const [value, setValue] = React.useState("")
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value
setValue(newValue)
if (onChange) {
onChange(newValue)
}
}
return (
<div className="relative w-full max-w-sm">
<div className="flex">
<Input
type="text"
placeholder={placeholder}
value={value}
onChange={handleChange}
className="rounded-r-none flex-grow"
/>
<div className="inline-flex items-center px-3 rounded-r-md border border-l-0 border-input bg-muted text-muted-foreground">
<span className="text-sm">{domainSuffix}</span>
</div>
</div>
</div>
)
}

View file

@ -0,0 +1,174 @@
"use client"
import Link from "next/link"
import { zodResolver } from "@hookform/resolvers/zod"
import { useFieldArray, useForm } from "react-hook-form"
import { z } from "zod"
import { cn } from "@/lib/utils"
import { toast } from "@/hooks/use-toast"
import { Button } from "@/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
import { useSiteContext } from "@app/hooks/useSiteContext"
import api from "@app/api"
const GeneralFormSchema = z.object({
name: z.string()
// email: z
// .string({
// required_error: "Please select an email to display.",
// })
// .email(),
// bio: z.string().max(160).min(4),
// urls: z
// .array(
// z.object({
// value: z.string().url({ message: "Please enter a valid URL." }),
// })
// )
// .optional(),
})
type GeneralFormValues = z.infer<typeof GeneralFormSchema>
export function GeneralForm() {
const { site, updateSite } = useSiteContext();
const form = useForm<GeneralFormValues>({
resolver: zodResolver(GeneralFormSchema),
defaultValues: {
name: site?.name
},
mode: "onChange",
})
// const { fields, append } = useFieldArray({
// name: "urls",
// control: form.control,
// })
async function onSubmit(data: GeneralFormValues) {
await updateSite({ name: data.name });
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
This is the display name of the site.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
{/* <FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a verified email to display" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="m@example.com">m@example.com</SelectItem>
<SelectItem value="m@google.com">m@google.com</SelectItem>
<SelectItem value="m@support.com">m@support.com</SelectItem>
</SelectContent>
</Select>
<FormDescription>
You can manage verified email addresses in your{" "}
<Link href="/examples/forms">email settings</Link>.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="bio"
render={({ field }) => (
<FormItem>
<FormLabel>Bio</FormLabel>
<FormControl>
<Textarea
placeholder="Tell us a little bit about yourself"
className="resize-none"
{...field}
/>
</FormControl>
<FormDescription>
You can <span>@mention</span> other users and organizations to
link to them.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div>
{fields.map((field, index) => (
<FormField
control={form.control}
key={field.id}
name={`urls.${index}.value`}
render={({ field }) => (
<FormItem>
<FormLabel className={cn(index !== 0 && "sr-only")}>
URLs
</FormLabel>
<FormDescription className={cn(index !== 0 && "sr-only")}>
Add links to your website, blog, or social media profiles.
</FormDescription>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
))}
<Button
type="button"
variant="outline"
size="sm"
className="mt-2"
onClick={() => append({ value: "" })}
>
Add URL
</Button>
</div> */}
<Button type="submit">Update Site</Button>
</form>
</Form>
)
}

View file

@ -1,52 +1,81 @@
import { Metadata } from "next" import { Metadata } from "next";
import Image from "next/image" import Image from "next/image";
import { Separator } from "@/components/ui/separator" import { Separator } from "@/components/ui/separator";
import { SidebarNav } from "@/components/sidebar-nav" import { SidebarNav } from "@/components/sidebar-nav";
import ResourceProvider from "@app/providers/ResourceProvider";
import { internal } from "@app/api";
import { GetResourceResponse } from "@server/routers/resource";
import { AxiosResponse } from "axios";
import { redirect } from "next/navigation";
import { authCookieHeader } from "@app/api/cookies";
import Link from "next/link";
import { ArrowLeft, ChevronLeft } from "lucide-react";
import { useEffect, useState } from "react";
import { toast } from "@app/hooks/use-toast";
import { ClientLayout } from "./components/ClientLayout";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Forms", title: "Forms",
description: "Advanced form example using react-hook-form and Zod.", description: "Advanced form example using react-hook-form and Zod.",
} };
const sidebarNavItems = [
{
title: "Profile",
href: "/{orgId}/resources/{resourceId}",
},
{
title: "Appearance",
href: "/{orgId}/resources/{resourceId}/appearance",
},
{
title: "Notifications",
href: "/{orgId}/resources/{resourceId}/notifications",
},
]
interface SettingsLayoutProps { interface SettingsLayoutProps {
children: React.ReactNode, children: React.ReactNode;
params: { resourceId: string, orgId: string } params: { resourceId: string; orgId: string };
}
export default async function SettingsLayout({
children,
params,
}: SettingsLayoutProps) {
let resource = null;
if (params.resourceId !== "create") {
try {
const res = await internal.get<AxiosResponse<GetResourceResponse>>(
`/org/${params.orgId}/resource/${params.resourceId}`,
authCookieHeader(),
);
resource = res.data.data;
} catch {
redirect(`/${params.orgId}/resources`);
}
} }
export default function SettingsLayout({ children, params }: SettingsLayoutProps) {
return ( return (
<> <>
<div> <div className="md:hidden">
<div className="space-y-0.5"> <Image
<h2 className="text-2xl font-bold tracking-tight">Settings</h2> src="/configuration/forms-light.png"
<p className="text-muted-foreground"> width={1280}
Manage your account settings and set e-mail preferences. height={791}
</p> alt="Forms"
</div> className="block dark:hidden"
<Separator className="my-6" /> />
<div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0"> <Image
<aside className="-mx-4 lg:w-1/5"> src="/configuration/forms-dark.png"
<SidebarNav items={sidebarNavItems.map(i => { i.href = i.href.replace("{resourceId}", params.resourceId).replace("{orgId}", params.orgId); return i})} /> width={1280}
</aside> height={791}
<div className="flex-1 lg:max-w-2xl">{children}</div> alt="Forms"
className="hidden dark:block"
/>
</div> </div>
<div className="mb-4">
<Link
href={`/${params.orgId}/resources`}
className="text-primary font-medium"
>
</Link>
</div> </div>
<ResourceProvider resource={resource}>
<ClientLayout
isCreate={params.resourceId === "create"}
>
{children}
</ClientLayout></ResourceProvider>
</> </>
) );
} }

View file

@ -1,17 +1,30 @@
import { Separator } from "@/components/ui/separator" import React from "react";
import { ProfileForm } from "@app/components/profile-form" import { Separator } from "@/components/ui/separator";
import { CreateResourceForm } from "./components/CreateResource";
import { GeneralForm } from "./components/GeneralForm";
export default function SettingsPage({
params,
}: {
params: { resourceId: string };
}) {
const isCreate = params.resourceId === "create";
export default function SettingsProfilePage() {
return ( return (
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<h3 className="text-lg font-medium">Profile</h3> <h3 className="text-lg font-medium">
{isCreate ? "Create Resource" : "General"}
</h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
This is how others will see you on the site. {isCreate
? "Create a new resource"
: "Edit basic resource settings"}
</p> </p>
</div> </div>
<Separator /> <Separator />
<ProfileForm />
{isCreate ? <CreateResourceForm /> : <GeneralForm />}
</div> </div>
) );
} }

View file

@ -1,18 +0,0 @@
import { Separator } from "@/components/ui/separator"
import { AppearanceForm } from "@/components/appearance-form"
export default function SettingsAppearancePage() {
return (
<div className="space-y-6">
<div>
<h3 className="text-lg font-medium">Appearance</h3>
<p className="text-sm text-muted-foreground">
Customize the appearance of the app. Automatically switch between day
and night themes.
</p>
</div>
<Separator />
<AppearanceForm />
</div>
)
}

View file

@ -5,21 +5,21 @@ import { useSiteContext } from "@app/hooks/useSiteContext";
const sidebarNavItems = [ const sidebarNavItems = [
{ {
title: "Profile", title: "General",
href: "/{orgId}/sites/{niceId}", href: "/{orgId}/sites/{niceId}",
}, },
{ // {
title: "Appearance", // title: "Appearance",
href: "/{orgId}/sites/{niceId}/appearance", // href: "/{orgId}/sites/{niceId}/appearance",
}, // },
{ // {
title: "Notifications", // title: "Notifications",
href: "/{orgId}/sites/{niceId}/notifications", // href: "/{orgId}/sites/{niceId}/notifications",
}, // },
{ // {
title: "Display", // title: "Display",
href: "/{orgId}/sites/{niceId}/display", // href: "/{orgId}/sites/{niceId}/display",
}, // },
]; ];
export function ClientLayout({ isCreate, children }: { isCreate: boolean; children: React.ReactNode }) { export function ClientLayout({ isCreate, children }: { isCreate: boolean; children: React.ReactNode }) {

View file

@ -1,17 +0,0 @@
import { Separator } from "@/components/ui/separator"
import { DisplayForm } from "@/components/display-form"
export default function SettingsDisplayPage() {
return (
<div className="space-y-6">
<div>
<h3 className="text-lg font-medium">Display</h3>
<p className="text-sm text-muted-foreground">
Turn items on or off to control what&apos;s displayed in the app.
</p>
</div>
<Separator />
<DisplayForm />
</div>
)
}

View file

@ -1,17 +0,0 @@
import { Separator } from "@/components/ui/separator"
import { NotificationsForm } from "@/components/notifications-form"
export default function SettingsNotificationsPage() {
return (
<div className="space-y-6">
<div>
<h3 className="text-lg font-medium">Notifications</h3>
<p className="text-sm text-muted-foreground">
Configure how you receive notifications.
</p>
</div>
<Separator />
<NotificationsForm />
</div>
)
}

View file

@ -3,7 +3,7 @@ import { Separator } from "@/components/ui/separator";
import { CreateSiteForm } from "./components/CreateSite"; import { CreateSiteForm } from "./components/CreateSite";
import { GeneralForm } from "./components/GeneralForm"; import { GeneralForm } from "./components/GeneralForm";
export default function SettingsProfilePage({ export default function SettingsPage({
params, params,
}: { }: {
params: { niceId: string }; params: { niceId: string };
@ -14,12 +14,12 @@ export default function SettingsProfilePage({
<div className="space-y-6"> <div className="space-y-6">
<div> <div>
<h3 className="text-lg font-medium"> <h3 className="text-lg font-medium">
{isCreate ? "Create Site" : "Profile"} {isCreate ? "Create Site" : "General"}
</h3> </h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
{isCreate {isCreate
? "Create a new site for your profile." ? "Create a new site"
: "This is how others will see you on the site."} : "Edit basic site settings"}
</p> </p>
</div> </div>
<Separator /> <Separator />

View file

@ -0,0 +1,11 @@
import { GetResourceResponse } from "@server/routers/resource/getResource";
import { createContext } from "react";
interface ResourceContextType {
resource: GetResourceResponse | null;
updateResource: (updatedResource: Partial<GetResourceResponse>) => Promise<void>;
}
const ResourceContext = createContext<ResourceContextType | undefined>(undefined);
export default ResourceContext;

View file

@ -0,0 +1,10 @@
import ResourceContext from "@app/contexts/resourceContext";
import { useContext } from "react";
export function useResourceContext() {
const context = useContext(ResourceContext);
if (context === undefined) {
throw new Error('useResourceContext must be used within a ResourceProvider');
}
return context;
}

View file

@ -0,0 +1,44 @@
"use client";
import api from "@app/api";
import ResourceContext from "@app/contexts/resourceContext";
import { toast } from "@app/hooks/use-toast";
import { GetResourceResponse } from "@server/routers/resource/getResource";
import { AxiosResponse } from "axios";
import { useState } from "react";
interface ResourceProviderProps {
children: React.ReactNode;
resource: GetResourceResponse | null;
}
export function ResourceProvider({ children, resource: serverResource }: ResourceProviderProps) {
const [resource, setResource] = useState<GetResourceResponse | null>(serverResource);
const updateResource = async (updatedResource: Partial<GetResourceResponse>) => {
try {
if (!resource) {
throw new Error("No resource to update");
}
const res = await api.post<AxiosResponse<GetResourceResponse>>(
`resource/${resource.resourceId}`,
updatedResource,
);
setResource(res.data.data);
toast({
title: "Resource updated!",
});
} catch (error) {
console.error(error);
toast({
title: "Error updating resource...",
})
}
};
return <ResourceContext.Provider value={{ resource, updateResource }}>{children}</ResourceContext.Provider>;
}
export default ResourceProvider;