diff --git a/src/app/api/portfolio/route.ts b/src/app/api/portfolio/route.ts index a13a9f4..5a2874c 100644 --- a/src/app/api/portfolio/route.ts +++ b/src/app/api/portfolio/route.ts @@ -1,6 +1,55 @@ -import { eq } from "drizzle-orm"; -import { portfolio, portfolioUsers } from "@/app/db/schema/portfolio"; +import { + portfolio, + portfolioUsers, + PortfolioGoal, + PortfolioStatus, + PortfolioRole, +} from "@/app/db/schema/portfolio"; +import type { NewPortfolio } from "@/app/db/schema/portfolio"; import { NextRequest, NextResponse } from "next/server"; import { db } from "@/app/db/db"; +import { z } from "zod"; -export async function POST(request: NextRequest) {} +const createPortfolioSchema = z.object({ + userId: z.number(), + portfolioName: z.string(), + budget: z.number().optional(), + goal: z.enum(PortfolioGoal), + status: z.enum(PortfolioStatus), + role: z.enum(PortfolioRole), +}); + +export async function POST(request: NextRequest) { + const body = await request.json(); + + try { + const validatedBody = createPortfolioSchema.parse(body); + const { userId, portfolioName, budget, goal, status, role } = validatedBody; + + const creationDate = new Date(); + + const newPortfolio = await db + .insert(portfolio) + .values({ + name: portfolioName, + budget: budget, + goal: goal, + status: status, + createdAt: creationDate, + updatedAt: creationDate, + }) + .returning({ portfolioId: portfolio.id }); + + const newPortfolioId = newPortfolio[0].portfolioId; + + const response = await db + .insert(portfolioUsers) + .values({ userId: userId, portfolioId: newPortfolioId, role: role }) + .returning(); + + return new NextResponse(JSON.stringify(response), { status: 201 }); + } catch (error) { + console.error(error); + throw error; + } +} diff --git a/src/app/components/home/ModalSubmit.tsx b/src/app/components/home/ModalSubmit.tsx index b5c063d..65d4325 100644 --- a/src/app/components/home/ModalSubmit.tsx +++ b/src/app/components/home/ModalSubmit.tsx @@ -1,35 +1,102 @@ +import { useMutation } from "@tanstack/react-query"; import { useRouter } from "next/navigation"; +import { + PortfolioStatus, + PortfolioGoal, + PortfolioRole, +} from "@/app/db/schema/portfolio"; + +type CreatePortfolioArgs = { + userId: number; + portfolioName: string; + budget: number | undefined; + goal: (typeof PortfolioGoal)[number]; + status: (typeof PortfolioStatus)[number]; + role: (typeof PortfolioRole)[number]; +}; + +const createPortfolio = async ({ + userId, + portfolioName, + budget, + goal, + status, + role, +}: CreatePortfolioArgs) => { + const requestBody = JSON.stringify({ + userId: userId, + portfolioName: portfolioName, + budget: budget, + goal: goal, + status: status, + role: role, + }); + + const response = await fetch("/api/portfolio/", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: requestBody, + }); + + if (!response.ok) { + throw new Error("Network response was not ok"); + } + + return response.json(); +}; const ModalSubmit = ({ buttonDisabled, - porfolioName, + portfolioName, budget, objective, }: { buttonDisabled: boolean; - porfolioName: string; - budget: string; + portfolioName: string; + budget: number | undefined; objective: string; }) => { const router = useRouter(); - function createPortfolio() { - // TODO: This will be a server action that will create a new portfolio in the database - // That endpoint will return a uuid which will be the new portfolio's id - const id = "f290f1ee-6c54-4b01-90e6-d701748f0851"; - router.push( - `/portfolio/${id}?name=${porfolioName}&budget=${budget}&objective=${objective}` - ); - } + const { mutate, isLoading } = useMutation(createPortfolio, { + onSuccess: (data) => { + // handle success, e.g. navigate to new page + console.log("SUCCESS", data); + router.push( + `/portfolio/${data.id}?name=${portfolioName}&budget=${budget}&objective=${objective}` + ); + }, + onError: (error) => { + // handle error + console.log(error); + }, + }); + + const handleSubmit = () => { + const userId = 2; + const status = "scoping"; + const role = "creator"; + + mutate({ + userId, + portfolioName: portfolioName, + budget, + goal: objective, + status, + role, + }); + }; return ( ); }; diff --git a/src/app/components/home/NewPortfolioModal.tsx b/src/app/components/home/NewPortfolioModal.tsx index cc22041..613adbf 100644 --- a/src/app/components/home/NewPortfolioModal.tsx +++ b/src/app/components/home/NewPortfolioModal.tsx @@ -6,14 +6,7 @@ import CarbonIcon from "./CarbonIcon"; import QuestionMarkIcon from "./QuestionMarkIcon"; import EnvironmentIcon from "./EnvironmentIconSvg"; import ModalSubmit from "./ModalSubmit"; - -const outcomes = [ - "Valuation Improvement", - "Increasing EPC", - "Reducing CO2 emissions", - "Energy Savings", - "Nothing Specific", -]; +import { PortfolioGoal } from "@/app/db/schema/portfolio"; // Mock Icon component interface IconProps { @@ -39,7 +32,7 @@ const Icon: React.FC = ({ name, selected, onSelect }: IconProps) => { // add more mappings here if needed "Increasing EPC": , "Reducing CO2 emissions": , - "Nothing Specific": , + None: , }; return ( @@ -66,7 +59,7 @@ export default function NewPortfolioModal({ setIsOpen: (isOpen: boolean) => void; }) { const [portfolioName, setPortfolioName] = useState(""); - const [budget, setBudget] = useState(""); + const [budget, setBudget] = useState(undefined); const [selectedOutcome, setSelectedOutcome] = useState("Nothing Specific"); const [buttonDisabled, setButtonDisabled] = useState(true); @@ -153,7 +146,7 @@ export default function NewPortfolioModal({ type="number" placeholder="1000" value={budget} - onChange={(e) => setBudget(e.target.value)} + onChange={(e) => setBudget(Number(e.target.value))} onKeyDown={(e) => (e.key === "e" || e.key === "E") && e.preventDefault() @@ -170,7 +163,7 @@ export default function NewPortfolioModal({ Select Outcome:
- {outcomes.map((outcome) => ( + {PortfolioGoal.map((outcome) => ( diff --git a/src/app/home/page.tsx b/src/app/home/page.tsx index faab169..f536cbc 100644 --- a/src/app/home/page.tsx +++ b/src/app/home/page.tsx @@ -1,5 +1,5 @@ import CardTiles from "../components/home/CardTiles"; -import getPortfolios from "./utils"; +import { getPortfolios } from "./utils"; import { AuthOptions } from "@/app/api/auth/[...nextauth]/route"; import { getServerSession } from "next-auth"; import { redirect } from "next/navigation"; diff --git a/src/app/home/utils.ts b/src/app/home/utils.ts index 9c78b22..61e3c2c 100644 --- a/src/app/home/utils.ts +++ b/src/app/home/utils.ts @@ -6,9 +6,7 @@ import { NextRequest, NextResponse } from "next/server"; import { portfolio } from "@/app/db/schema/portfolio"; import type { Portfolio } from "@/app/db/schema/portfolio"; -export default async function getPortfolios( - userId: number -): Promise { +export async function getPortfolios(userId: number): Promise { const userPortfolios = await db .select() .from(portfolio) diff --git a/src/types/next-auth.d.ts b/src/types/next-auth.d.ts new file mode 100644 index 0000000..f95c692 --- /dev/null +++ b/src/types/next-auth.d.ts @@ -0,0 +1,14 @@ +import NextAuth from "next-auth"; + +// This extends the session object to allow us to insert user data into it + +declare module "next-auth" { + interface Session { + user: { + id: string; + } & DefaultSession["user"]; + } + interface User { + dbId: number; + } +}