Skip to content

Commit

Permalink
feat: calendar download
Browse files Browse the repository at this point in the history
  • Loading branch information
Rahuletto committed Sep 6, 2024
1 parent ae3346d commit 8d704d8
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 11 deletions.
8 changes: 5 additions & 3 deletions app/academia/components/Timetable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ export default function Timetable() {
const infoIconRef = useRef<HTMLDivElement>(null);
const [currentDayOrder, setCurrentDayOrder] = useState<string | number>("1");

useEffect(() => {
if(!timetable && !timetableLoading && !timetableError) mutate()
}, [timetable, mutate, timetableLoading, timetableError, day])


useEffect(() => {
if (day) setCurrentDayOrder(day);
}, [day]);

useEffect(() => {
if(!timetable && !timetableLoading && !timetableError) mutate()
}, [timetable, mutate, timetableLoading, timetableError])

const toggleInfoPopup = () => setShowInfoPopup((e) => !e);
const isHoliday = day && typeof day === "string" && day.includes("No");
Expand Down
9 changes: 5 additions & 4 deletions app/calendar/components/CalendarGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ const weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
interface CalendarGridProps {
month: number;
days: Day[];
isDownload?: boolean;
}

export default function CalendarGrid({ days, month }: CalendarGridProps) {
export default function CalendarGrid({ days, month, isDownload }: CalendarGridProps) {
const todayRef = useRef<HTMLDivElement>(null);
const date = new Date().getDate();

Expand Down Expand Up @@ -42,12 +43,12 @@ export default function CalendarGrid({ days, month }: CalendarGridProps) {
key={index}
day={day}
isTomorrow={
date + 1 === Number(day.date) &&
isDownload ? false : (date + 1 === Number(day.date) &&
new Date().getMonth() === month &&
new Date().getHours() > 16
new Date().getHours() > 16)
}
isToday={
date === Number(day.date) && new Date().getMonth() === month
isDownload ? false : date === Number(day.date) && new Date().getMonth() === month
}
ref={date === Number(day.date) ? todayRef : null}
/>
Expand Down
13 changes: 11 additions & 2 deletions app/calendar/components/CalendarHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Refresh from "@/components/Refresh";
import { useRouter } from "next/navigation";
import React from "react";
import { FaArrowLeft, FaArrowRight } from "react-icons/fa";
import { LiaDownloadSolid } from "react-icons/lia";

interface CalendarHeaderProps {
mobile?: boolean;
Expand All @@ -17,6 +19,7 @@ export default function CalendarHeader({
current,
setCurrent,
}: CalendarHeaderProps) {
const router = useRouter()
return (
<>
<div
Expand Down Expand Up @@ -44,9 +47,15 @@ export default function CalendarHeader({
) : null}
</div>

<div className="px-3 py-3 dark:bg-dark-background-dark bg-light-background-light rounded-full">
<div className="px-3 py-3 dark:bg-dark-background-dark flex gap-3 bg-light-background-light rounded-full">
<Refresh type={{ mutateCalendar: true }} />

<button
tabIndex={0}
className={`rounded-full p-1 text-sm text-light-color opacity-60 transition duration-200 hover:bg-light-background-dark active:-rotate-45 dark:text-dark-color dark:hover:bg-dark-background-dark`}
onClick={() => router.push("/calendar/download")}
>
<LiaDownloadSolid />
</button>
</div>
</div>
</>
Expand Down
171 changes: 171 additions & 0 deletions app/calendar/download/api/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { ImageResponse } from "next/og";
// import CalendarGen from "@/components/Generators/Calendar";
import { Day } from "@/types/Calendar";

const weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];

export async function POST(request: Request) {
const geist = await fetch(
new URL("../../../../public/fonts/Geist.ttf", import.meta.url),
).then((res) => res.arrayBuffer());

try {
const calendar: any = await request.json();

if (!calendar)
return new Response(
JSON.stringify({
message:
"Hmm, An error occured while grabbing your calendar data. Logout and login again.",
fix: "Logout and retry. Its better be old expired cookies 🍪",
}),
{
status: 500,
headers: {
"content-type": "application/json",
},
},
);

const month = calendar.calendar.month;
const days = calendar.calendar.days;

const getFirstDayIndex = () => weekdays.indexOf(days[0].day);

return new ImageResponse(
(
<div tw="bg-[#0a0d12] flex flex-col items-center justify-center h-screen w-screen">
<h1 tw="text-3xl font-semibold text-white my-8">{month}</h1>
<div tw="max-w-[2000px] flex text-center font-bold bg-[#06090d]">
{weekdays.map((weekday) => (
<div
key={weekday}
tw="w-[285px] h-[56px] text-lg p-2 font-medium text-center text-white flex items-center justify-center"
>
{weekday}
</div>
))}
</div>
<div tw="bg-[#0a0d12] text-center flex flex-row flex-wrap max-w-[2000px] w-screen">
{Array.from({ length: getFirstDayIndex() }, (_, index) => (
<div tw="flex w-[285px] h-[390px]" key={`empty-${index}`} />
))}
{days
.filter((a: any) => a.dayOrder.length <= 1)
.map((day: Day, index: number) => (
<DayCell key={index} day={day} />
))}
</div>
</div>
),
{
width: 2100,
height: 2200,
fonts: [
{
name: "Geist",
data: geist,
style: "normal",
},
],
headers: {
"Accept-Encoding": "gzip, deflate, br, zstd",
"cache-control": "private, maxage=86400",
},
},
);
} catch (err: any) {
console.warn(err);
return new Response(
JSON.stringify({
error: err.stack,
}),
{
status: 500,
statusText: "Server Error",
},
);
}
}
export const runtime = "edge";

function DayCell({ day }: { day: Day }) {
const isErrorDay = day.dayOrder === "-";

return (
<div
aria-label={day.date}
title={`${day.date} - Day Order: ${day.dayOrder}`}
tw={
`${isErrorDay ? "bg-[#1D0C0C]" : ""} flex max-w-[285px] h-[370px] w-full flex-col justify-between border border-[#1E232B] p-4 items-end`
}
>
<DateDisplay
date={day.date}
day={day.day}
isToday={false}
isErrorDay={isErrorDay}
/>
<HolidayDisplay holiday={day.holiday || ""} isErrorDay={isErrorDay} />
<DayOrderDisplay dayOrder={day.dayOrder} isToday={false} />
</div>
);
}

interface DateDisplayProps {
date: string;
day: string;
isToday: boolean;
isErrorDay: boolean;
}

const DateDisplay: React.FC<DateDisplayProps> = ({
date,
day,
isToday,
isErrorDay,
}) => (
<div tw={`flex ${isErrorDay && !isToday ? "text-[#F75B5B]" : "text-white opacity-70"}`}>
<h2 tw="rounded-full flex text-right text-3xl font-bold">{date}</h2>
</div>
);

interface HolidayDisplayProps {
holiday: string | null;
isErrorDay: boolean;
}

const HolidayDisplay: React.FC<HolidayDisplayProps> = ({
holiday,
isErrorDay,
}) => {
if (!holiday) return null;
return (
<p
style={{ whiteSpace: "break-spaces" }}
tw={`text-right pr-1 -mb-0.5 -mx-2 text-base ${isErrorDay ? "text-[#F75B5B]" : "rounded-md border-l-2 border-r-0 border-[#7CB3EB] bg-[#1B1D2B] px-1 py-0.5 pl-2 text-[#7CB3EB] opacity-70"}`}
>
{holiday.replaceAll(",", ", ")}
</p>
);
};

interface DayOrderDisplayProps {
dayOrder: string;
isToday: boolean;
}

const DayOrderDisplay: React.FC<DayOrderDisplayProps> = ({
dayOrder,
isToday,
}) => {
if (dayOrder === "-") return null;
return (
<h2 tw="w-full pl-1 -mb-0.5 text-xl text-white opacity-70 font-medium text-left">
Day Order{" "}
<span tw={`ml-2 font-bold`}>
{dayOrder}
</span>
</h2>
);
};
63 changes: 63 additions & 0 deletions app/calendar/download/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"use client";
import Link from "@/components/Link";
import Loading from "@/components/States/Loading";
import { useCalendar } from "@/provider/CalendarProvider";
import Image from "next/image";
import React, { useEffect, useState } from "react";
import { FaArrowLeft } from "react-icons/fa";

export default function Calendar() {
const { calendar, isLoading, error } = useCalendar();

const [data, setData] = useState("");

useEffect(() => {
if (calendar && calendar[0]) {
fetch(`/calendar/download/api`, {
method: "POST",
body: JSON.stringify({
calendar: calendar?.[new Date().getMonth() % 5]
}),
headers: {
"Content-Type": "application/json",
}
})
.then((d) => d.blob())
.then((res: Blob | MediaSource) => {
const imageUrl = URL.createObjectURL(res);
setData(imageUrl);
});
}
}, [calendar]);
return (
<main className="flex h-screen w-screen flex-col items-center justify-center gap-12 bg-light-background-normal dark:bg-dark-background-normal">
{data ? (
<>
<Link
href="/academia"
style={{ padding: 12, borderRadius: 32 }}
className="absolute left-6 top-6 z-10 bg-light-error-background text-light-error-color dark:bg-dark-error-background dark:text-dark-error-color"
>
<FaArrowLeft />
</Link>
<Image
className="scale-70 rounded-3xl"
alt="calendar"
src={data}
width={800}
height={400}
/>
<a
href={data}
download={`calendar.png`}
className="w-fit transform rounded-xl bg-light-background-dark px-5 py-1 font-medium text-light-color transition-all duration-300 hover:scale-105 hover:opacity-80 dark:bg-dark-background-light dark:text-dark-color"
>
Download
</a>
</>
) : (
<Loading />
)}
</main>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import TimetableGen from "@/components/Generators/Timetable";

export async function POST(request: Request) {
const geist = await fetch(
new URL("../../public/fonts/Geist.ttf", import.meta.url),
new URL("../../../public/fonts/Geist.ttf", import.meta.url),
).then((res) => res.arrayBuffer());

try {
Expand Down
2 changes: 1 addition & 1 deletion app/timetable/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default function Timetable() {

useEffect(() => {
if (user?.reg && timetable) {
fetch(`/generate`, {
fetch(`/timetable/download`, {
method: "POST",
body: JSON.stringify({
table: timetable,
Expand Down

0 comments on commit 8d704d8

Please sign in to comment.