mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
Implemented create protfolio api
This commit is contained in:
parent
1e305912f6
commit
0ce99e0f94
6 changed files with 155 additions and 34 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex justify-center rounded-md border border-transparent bg-brandtan px-4 py-2 text-sm font-medium text-grey-900 hover:bg-hovertan focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2 disabled:bg-gray-300 disabled:opacity-50"
|
||||
onClick={createPortfolio}
|
||||
disabled={buttonDisabled}
|
||||
onClick={handleSubmit}
|
||||
disabled={buttonDisabled || isLoading}
|
||||
>
|
||||
Create
|
||||
{isLoading ? "Creating..." : "Create"}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<IconProps> = ({ name, selected, onSelect }: IconProps) => {
|
|||
// add more mappings here if needed
|
||||
"Increasing EPC": <EnvironmentIcon fill="white" />,
|
||||
"Reducing CO2 emissions": <CarbonIcon fill="white" />,
|
||||
"Nothing Specific": <QuestionMarkIcon fill="white" />,
|
||||
None: <QuestionMarkIcon fill="white" />,
|
||||
};
|
||||
|
||||
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 | number>(undefined);
|
||||
const [selectedOutcome, setSelectedOutcome] =
|
||||
useState<string>("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:
|
||||
</span>
|
||||
<div className="flex space-x-2">
|
||||
{outcomes.map((outcome) => (
|
||||
{PortfolioGoal.map((outcome) => (
|
||||
<Icon
|
||||
key={outcome}
|
||||
name={outcome}
|
||||
|
|
@ -192,7 +185,7 @@ export default function NewPortfolioModal({
|
|||
</button>
|
||||
<ModalSubmit
|
||||
buttonDisabled={buttonDisabled}
|
||||
porfolioName={portfolioName}
|
||||
portfolioName={portfolioName}
|
||||
budget={budget}
|
||||
objective={selectedOutcome}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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<Portfolio[]> {
|
||||
export async function getPortfolios(userId: number): Promise<Portfolio[]> {
|
||||
const userPortfolios = await db
|
||||
.select()
|
||||
.from(portfolio)
|
||||
|
|
|
|||
14
src/types/next-auth.d.ts
vendored
Normal file
14
src/types/next-auth.d.ts
vendored
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue