mirror of
https://github.com/Sosokker/B2D-Ventures.git
synced 2025-12-19 14:04:06 +01:00
feat: add arrange meeting page
This commit is contained in:
parent
0b76ed64da
commit
c79bc4229e
128
src/app/calendar/MeetEventDialog.tsx
Normal file
128
src/app/calendar/MeetEventDialog.tsx
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Clock } from "lucide-react";
|
||||||
|
import { DateTimePicker, TimePicker } from "@/components/ui/datetime-picker";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { createCalendarEvent } from "./actions";
|
||||||
|
import { Session } from "@supabase/supabase-js";
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
open?: boolean;
|
||||||
|
defaultOpen?: boolean;
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
onOpenChange?(open: boolean): void;
|
||||||
|
modal?: boolean;
|
||||||
|
session: Session;
|
||||||
|
projectName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MeetEventDialog(props: DialogProps) {
|
||||||
|
const [eventDate, setEventDate] = useState<Date | undefined>(undefined);
|
||||||
|
const [startTime, setStartTime] = useState<Date | undefined>(undefined);
|
||||||
|
const [endTime, setEndTime] = useState<Date | undefined>(undefined);
|
||||||
|
const [eventName, setEventName] = useState(`Meet with ${props.projectName}`);
|
||||||
|
const [eventDescription, setEventDescription] = useState(
|
||||||
|
"Meet and gather more information on business in B2DVentures"
|
||||||
|
);
|
||||||
|
const [noteToBusiness, setNoteToBusiness] = useState<string>("");
|
||||||
|
const session = props.session;
|
||||||
|
|
||||||
|
const handleCreateEvent = async () => {
|
||||||
|
if (!session || !eventDate || !startTime || !endTime || !eventName) {
|
||||||
|
alert("Please fill in all event details.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startDate = new Date(eventDate);
|
||||||
|
startDate.setHours(startTime.getHours(), startTime.getMinutes());
|
||||||
|
|
||||||
|
const endDate = new Date(eventDate);
|
||||||
|
endDate.setHours(endTime.getHours(), endTime.getMinutes());
|
||||||
|
await createCalendarEvent(session, startDate, endDate, eventName, eventDescription);
|
||||||
|
props.onOpenChange?.(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog {...props}>
|
||||||
|
<DialogContent className="sm:max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex gap-2 items-center">
|
||||||
|
<Clock />
|
||||||
|
Arrange Meeting
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Arrange a meeting with the business you're interested in for more information.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-4 w-[90%]">
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="eventName">Event Name</Label>
|
||||||
|
<Input
|
||||||
|
id="eventName"
|
||||||
|
placeholder="Enter event name"
|
||||||
|
value={eventName}
|
||||||
|
onChange={(e) => setEventName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="eventDescription">Event Description</Label>
|
||||||
|
<Input
|
||||||
|
id="eventDescription"
|
||||||
|
placeholder="Enter event description"
|
||||||
|
value={eventDescription}
|
||||||
|
onChange={(e) => setEventDescription(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="eventDescription">Event Description</Label>
|
||||||
|
<Input
|
||||||
|
id="note"
|
||||||
|
placeholder="Your note to business"
|
||||||
|
value={noteToBusiness}
|
||||||
|
onChange={(e) => setNoteToBusiness(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Date</Label>
|
||||||
|
<DateTimePicker granularity="day" hourCycle={24} value={eventDate} onChange={setEventDate} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<Label>Start Time</Label>
|
||||||
|
<TimePicker date={startTime} onChange={setStartTime} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>End Time</Label>
|
||||||
|
<TimePicker date={endTime} onChange={setEndTime} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter className="sm:justify-start mt-4">
|
||||||
|
<Button type="button" onClick={handleCreateEvent} className="mr-2">
|
||||||
|
Create Event
|
||||||
|
</Button>
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button type="button" variant="secondary">
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</DialogClose>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
41
src/app/calendar/actions.ts
Normal file
41
src/app/calendar/actions.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { Session } from "@supabase/supabase-js";
|
||||||
|
|
||||||
|
export async function createCalendarEvent(
|
||||||
|
session: Session,
|
||||||
|
start: Date,
|
||||||
|
end: Date,
|
||||||
|
eventName: string,
|
||||||
|
eventDescription: string
|
||||||
|
) {
|
||||||
|
console.log("Creating calendar event");
|
||||||
|
|
||||||
|
const event = {
|
||||||
|
summary: eventName,
|
||||||
|
description: eventDescription,
|
||||||
|
start: {
|
||||||
|
dateTime: start.toISOString(),
|
||||||
|
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
dateTime: end.toISOString(),
|
||||||
|
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("https://www.googleapis.com/calendar/v3/calendars/primary/events", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer " + session.provider_token,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(event),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(data);
|
||||||
|
alert("Event created, check your Google Calendar!");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error creating calendar event:", error);
|
||||||
|
alert("Failed to create the event.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,21 +1,145 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useState } from "react";
|
|
||||||
import { DateTimePicker } from "@/components/ui/datetime-picker";
|
import React, { useMemo, useState } from "react";
|
||||||
import { Label } from "@/components/ui/label";
|
import useSession from "@/lib/supabase/useSession";
|
||||||
|
import { MeetEventDialog } from "./MeetEventDialog";
|
||||||
|
import { Clock } from "lucide-react";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCaption,
|
||||||
|
TableCell,
|
||||||
|
TableHeader,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
TableFooter,
|
||||||
|
} from "@/components/ui/table";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { useQuery } from "@supabase-cache-helpers/postgrest-react-query";
|
||||||
|
import { getInvestmentByUserId } from "@/lib/data/investmentQuery";
|
||||||
|
import { LegacyLoader } from "@/components/loading/LegacyLoader";
|
||||||
|
import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
|
||||||
|
|
||||||
|
interface Investment {
|
||||||
|
id: any;
|
||||||
|
project_id: any;
|
||||||
|
project_name: any;
|
||||||
|
project_short_description: any;
|
||||||
|
dataroom_id: any;
|
||||||
|
deal_amount: any;
|
||||||
|
investor_id: any;
|
||||||
|
created_time: any;
|
||||||
|
}
|
||||||
|
|
||||||
const DatetimePickerHourCycle = () => {
|
const DatetimePickerHourCycle = () => {
|
||||||
const [date12, setDate12] = useState<Date | undefined>(undefined);
|
const supabase = createSupabaseClient();
|
||||||
const [date24, setDate24] = useState<Date | undefined>(undefined);
|
const [showModal, setShowModal] = useState<boolean>(false);
|
||||||
|
const [currentProjectName, setCurrentProjectName] = useState<string>("");
|
||||||
|
const { session, loading } = useSession();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: investments = [],
|
||||||
|
error: investmentsError,
|
||||||
|
isLoading: isLoadingInvestments,
|
||||||
|
} = useQuery(getInvestmentByUserId(supabase, session?.user.id ?? ""), {
|
||||||
|
enabled: !!session?.user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (investmentsError) {
|
||||||
|
throw "Error load investment data";
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectInvestments = useMemo(() => {
|
||||||
|
if (!investments) return {};
|
||||||
|
|
||||||
|
return investments.reduce((acc: { [key: string]: Investment[] }, investment: Investment) => {
|
||||||
|
const projectId = investment.project_id;
|
||||||
|
if (!acc[projectId]) {
|
||||||
|
acc[projectId] = [];
|
||||||
|
}
|
||||||
|
acc[projectId].push(investment);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}, [investments]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <LegacyLoader />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!session || !session?.user.id) {
|
||||||
|
throw "Can't load session!";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoadingInvestments) {
|
||||||
|
return <LegacyLoader />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3 lg:flex-row">
|
<div className="container max-w-screen-xl">
|
||||||
<div className="flex w-72 flex-col gap-2">
|
<span className="flex gap-2 items-center mt-4">
|
||||||
<Label>12 Hour</Label>
|
<Clock />
|
||||||
<DateTimePicker hourCycle={12} value={date12} onChange={setDate12} />
|
<p className="text-2xl font-bold">Schedule Meeting</p>
|
||||||
</div>
|
</span>
|
||||||
<div className="flex w-72 flex-col gap-2">
|
<Separator className="my-3" />
|
||||||
<Label>24 Hour</Label>
|
<div className="space-y-2">
|
||||||
<DateTimePicker hourCycle={24} value={date24} onChange={setDate24} />
|
{Object.entries(projectInvestments).map(([projectId, projectInvestments]) => (
|
||||||
|
<Card key={projectId}>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-xl">{projectInvestments[0].project_name}</CardTitle>
|
||||||
|
<CardDescription>{projectInvestments[0].project_short_description}</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Table>
|
||||||
|
<TableCaption>Investments for this project</TableCaption>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead className="w-[100px]">Investment Id</TableHead>
|
||||||
|
<TableHead>Invested At</TableHead>
|
||||||
|
<TableHead className="text-right">Amount</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{projectInvestments.map((investment) => (
|
||||||
|
<TableRow key={investment.id}>
|
||||||
|
<TableCell className="font-medium">{investment.id}</TableCell>
|
||||||
|
<TableCell>{investment.created_time}</TableCell>
|
||||||
|
<TableCell className="text-right">{investment.deal_amount}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
<TableFooter>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={2}>Total</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{projectInvestments
|
||||||
|
.reduce((total, investment) => total + (parseFloat(investment.deal_amount) || 0), 0)
|
||||||
|
.toLocaleString("en-US", { style: "currency", currency: "USD" })}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableFooter>
|
||||||
|
</Table>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setCurrentProjectName(projectInvestments[0].project_name);
|
||||||
|
setShowModal(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Schedule Meeting
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<MeetEventDialog
|
||||||
|
open={showModal}
|
||||||
|
onOpenChange={setShowModal}
|
||||||
|
session={session}
|
||||||
|
projectName={currentProjectName}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,6 +12,7 @@ export function LoginButton(props: { nextUrl?: string }) {
|
|||||||
provider: "google",
|
provider: "google",
|
||||||
options: {
|
options: {
|
||||||
redirectTo: `${location.origin}/auth/callback?next=${props.nextUrl || ""}`,
|
redirectTo: `${location.origin}/auth/callback?next=${props.nextUrl || ""}`,
|
||||||
|
scopes: "https://www.googleapis.com/auth/calendar",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,6 +12,7 @@ export function SignupButton(props: { nextUrl?: string }) {
|
|||||||
provider: "google",
|
provider: "google",
|
||||||
options: {
|
options: {
|
||||||
redirectTo: `${location.origin}/auth/callback?next=${props.nextUrl || ""}`,
|
redirectTo: `${location.origin}/auth/callback?next=${props.nextUrl || ""}`,
|
||||||
|
scopes: "https://www.googleapis.com/auth/calendar",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user