Skip to content

Commit

Permalink
create project form - WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
agnlez committed Nov 18, 2024
1 parent 1a9462d commit 28bde6f
Show file tree
Hide file tree
Showing 21 changed files with 826 additions and 19 deletions.
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@radix-ui/react-icons": "1.3.0",
"@radix-ui/react-label": "2.1.0",
"@radix-ui/react-popover": "1.1.2",
"@radix-ui/react-radio-group": "1.2.1",
"@radix-ui/react-scroll-area": "1.2.0",
"@radix-ui/react-select": "2.1.2",
"@radix-ui/react-separator": "1.1.0",
Expand Down
4 changes: 1 addition & 3 deletions client/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ export default async function RootLayout({
<body className={inter.className}>
<SidebarProvider>
<MainNav />
<main className="flex h-dvh flex-1 overflow-hidden">
{children}
</main>
<main className="mx-3 flex h-dvh flex-1">{children}</main>
</SidebarProvider>
<Toaster />
</body>
Expand Down
25 changes: 25 additions & 0 deletions client/src/app/projects/new/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from "@tanstack/react-query";

import { client } from "@/lib/query-client";
import { queryKeys } from "@/lib/query-keys";

import CreateCustomProject from "@/containers/projects/new";

export default async function CreateCustomProjectPage() {
const queryClient = new QueryClient();

await queryClient.prefetchQuery({
queryKey: queryKeys.projects.countries.queryKey,
queryFn: () => client.projects.getProjectCountries.query(),
});

return (
<HydrationBoundary state={dehydrate(queryClient)}>
<CreateCustomProject />
</HydrationBoundary>
);
}
17 changes: 17 additions & 0 deletions client/src/app/projects/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { useForm } from "react-hook-form";

import { atom } from "jotai/index";

import { CreateCustomProjectForm } from "@/containers/projects/form/setup";

export const projectFormState = atom<{
setup: ReturnType<typeof useForm<CreateCustomProjectForm>> | null;
// todo: define schema
assumptions: ReturnType<typeof useForm> | null;
// todo: define schema
costInputsOverrides: ReturnType<typeof useForm> | null;
}>({
setup: null,
assumptions: null,
costInputsOverrides: null,
});
24 changes: 22 additions & 2 deletions client/src/components/ui/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Slot } from "@radix-ui/react-slot";

import { cn } from "@/lib/utils";

import InfoButton from "@/components/ui/info-button";
import { Label } from "@/components/ui/label";

const Form = FormProvider;
Expand Down Expand Up @@ -91,10 +92,29 @@ FormItem.displayName = "FormItem";

const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & {
tooltip?: {
title: string;
content: React.ReactNode;
};
}
>(({ className, tooltip, ...props }, ref) => {
const { error, formItemId } = useFormField();

if (tooltip) {
return (
<div className="flex items-center gap-2">
<Label
ref={ref}
className={cn(error && "text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
<InfoButton title={tooltip.title}>{tooltip.content}</InfoButton>
</div>
);
}

return (
<Label
ref={ref}
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/ui/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const inputVariants = cva(
"flex h-9 w-full rounded-full border border-input bg-transparent px-3 py-2 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
"flex h-9 w-full rounded-full border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-big-stone-950 text-foreground shadow border-border focus-visible:border-primary focus-visible:ring-sky-blue-300/40",
"bg-big-stone-950 text-big-stone-50 shadow border-border focus-visible:border-primary focus-visible:ring-sky-blue-300/40",
ghost: "text-foreground border-0 focus-visible:ring-sky-blue-300/40",
},
},
Expand Down
76 changes: 76 additions & 0 deletions client/src/components/ui/radio-group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"use client";

import * as React from "react";

import { DotFilledIcon } from "@radix-ui/react-icons";
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";

import { cn } from "@/lib/utils";

import { Label } from "@/components/ui/label";

const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Root
className={cn("grid gap-2", className)}
{...props}
ref={ref}
/>
);
});
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName;

const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<DotFilledIcon className="h-3.5 w-3.5 fill-primary" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
);
});
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName;

export interface RadioGroupItemBoxProps
extends React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> {
label: string;
}

const RadioGroupItemBox = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
RadioGroupItemBoxProps
>(({ className, checked, label, value, ...radioProps }, ref) => {
return (
<div
className={cn(
"cursor-pointer rounded-xl border border-border p-4",
className,
{
"border-primary": checked,
},
)}
>
<div className="flex items-center gap-2">
<RadioGroupItem ref={ref} value={value} id={value} {...radioProps} />
<Label htmlFor={value}>{label}</Label>
</div>
</div>
);
});

RadioGroupItemBox.displayName = "RadioGroupItemBox";

export { RadioGroup, RadioGroupItem, RadioGroupItemBox };
4 changes: 2 additions & 2 deletions client/src/components/ui/scroll-area.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";

import { cn } from "@/lib/utils";

const ScrollArea = React.forwardRef<
export const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
Expand All @@ -24,7 +24,7 @@ const ScrollArea = React.forwardRef<
));
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;

const ScrollBar = React.forwardRef<
export const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
Expand Down
2 changes: 1 addition & 1 deletion client/src/containers/overview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export default function Overview() {
>
<ProjectsFilters />
</motion.aside>
<div className="mx-3 flex flex-1 flex-col">
<div className="flex flex-1 flex-col">
<ProjectsHeader />
<ResizablePanelGroup
direction="vertical"
Expand Down
3 changes: 3 additions & 0 deletions client/src/containers/projects/form/assumptions/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function AssumptionsProjectForm() {
return <div>AssumptionsProjectForm</div>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function CostInputsOverridesProjectForm() {
return <div>CostInputsOverridesProjectForm</div>;
}
21 changes: 21 additions & 0 deletions client/src/containers/projects/form/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import AssumptionsProjectForm from "@/containers/projects/form/assumptions";
import CostInputsOverridesProjectForm from "@/containers/projects/form/cost-inputs-overrides";
import SetupProjectForm from "@/containers/projects/form/setup";

import { Card } from "@/components/ui/card";

export default function ProjectForm() {
return (
<div className="flex flex-col gap-3">
<Card className="flex flex-1 flex-col bg-transparent">
<SetupProjectForm />
</Card>
<Card className="flex flex-1 flex-col bg-transparent">
<AssumptionsProjectForm />
</Card>
<Card className="flex flex-1 flex-col bg-transparent">
<CostInputsOverridesProjectForm />
</Card>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import * as React from "react";

import {
PROJECT_SPECIFIC_EMISSION,
CARBON_REVENUES_TO_COVER,
} from "@shared/entities/custom-project.entity";
import { useAtom } from "jotai/index";

import { projectFormState } from "@/app/projects/store";

import { Card } from "@/components/ui/card";
import {
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";

export default function ConservationProjectDetails() {
const [{ setup: form }] = useAtom(projectFormState);

return (
<Card className="bg-transparent">
<h2 className="font-semibold">Conservation project details</h2>
<p className="text-sm text-muted-foreground">
This information only applies for conservation projects
</p>

<FormField
control={form?.control}
name="initialCarbonPriceAssumption"
render={({ field }) => (
<FormItem className="flex justify-between gap-4">
<FormLabel
tooltip={{
title: "Initial carbon price assumption",
content: "TBD",
}}
>
Initial carbon price assumption in $
</FormLabel>
<FormControl className="relative after:absolute after:right-9 after:inline-block after:text-sm after:text-muted-foreground after:content-['$']">
<div className="relative flex items-center">
<Input
type="number"
placeholder="Insert project name"
className="min-w-[225px]"
min={0}
{...field}
onChange={async (v) => {
form?.setValue(
"initialCarbonPriceAssumption",
Number(v.target.value),
);
await form?.trigger("initialCarbonPriceAssumption");
}}
/>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</Card>
);
}
Loading

0 comments on commit 28bde6f

Please sign in to comment.