Skip to content

Commit

Permalink
feat: add null checks and widget
Browse files Browse the repository at this point in the history
  • Loading branch information
phukon committed Mar 19, 2024
1 parent dd409e9 commit f4bf9e6
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 7 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### Nuances
- Always use `finally` in async worksloads if a UI state depends on it!!. Otherwise, the UI will forever be stuck and never update after the async work is done.


### Cron Job
- A vercel cronjob triggers at 0000hrs UTC to set the word-count reference.
- A second cronjob from an EC2 instance triggers at 0530hrs IST or 0000hrs UTC for redundancy.
Expand Down
24 changes: 24 additions & 0 deletions src/app/api/userDetails/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { db } from "@/lib/db";
import { type NextRequest } from 'next/server'

export async function GET(request: NextRequest): Promise<Response> {
const searchParams = request.nextUrl.searchParams
const query = searchParams.get('id')

if (!query) {
return new Response("No id provided", {
status: 404,
});
}

const user = await db.user.findFirst({
where: {
id: query
}
})


return new Response(user?.name, {
status: 200,
});
}
27 changes: 24 additions & 3 deletions src/app/dash/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ import { AddDocumentButton } from "@/components/document/AddDocumentButton";
import { removeDocument } from "@/actions/removeDocument";
import { useToast } from "@/components/ui/use-toast";
import { themes } from "@/lib/graph";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";

const Dash = () => {
const { toast } = useToast();
Expand All @@ -35,7 +41,18 @@ const Dash = () => {
setIsupdating(true);
addContribution()
.then(() => setIsupdating(false))
.catch((e) => console.log(e));
.catch((e) => {
console.log(e);
toast({
title: "Couldn't perform an update!",
description:
"Did you recently delete documents before unlinking them? Check console for more info.",
variant: "destructive",
});
})
.finally(() => {
setIsupdating(false);
});
};
return (
// pt-[calc(10vh)]
Expand Down Expand Up @@ -142,7 +159,11 @@ const Dash = () => {
<div className="mt-8 md:px-12 ">
<div className="grid grid-cols-1 md:grid-cols-3 md:gap-y-8 gap-4 mt-4">
{kv.map(([key, value]: [string, Value]) => (
<Link key={key} href={`/note?id=${key}`} className="rounded-md p-2 group col-span-1">
<Link
key={key}
href={`/note?id=${key}`}
className="rounded-md p-2 group col-span-1"
>
<Card className="group-hover:scale-105 duration-150 ease-out ">
<CardHeader className="rounded-t-lg bg-gray-300 dark:bg-gray-800 group-hover:bg-stone-100 group-active:bg-stone-200 py-2">
<CardTitle className="text-sm font-semibold">
Expand Down
116 changes: 116 additions & 0 deletions src/app/embed/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { themes } from "@/lib/graph";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { DataStruct } from "@/types";
import { getUserData } from "@/components/graph/_getData";
import { getUserById } from "@/data/user";
import { drawContributions } from "@/lib/graph";

type GraphProps = {
isPreview: boolean;
themeName: keyof typeof themes;
username: string;
userData: DataStruct;
onThemeChange: (theme: keyof typeof themes) => void;
};

function EmbedGraph(props: GraphProps) {
const { isPreview, themeName, onThemeChange } = props;
const canvasRef = useRef(null);

const currentYear = new Date().getFullYear();

useEffect(() => {
if (canvasRef.current && props.userData) {
const filteredYears: any = props.userData.years.filter(
(year) => year.year === currentYear.toString()
);
const filteredData = {
years: filteredYears,
contributions: props.userData.contributions,
};
drawContributions(canvasRef.current, {
data: isPreview ? filteredData : props.userData,
username: props.username ?? "",
themeName: themeName,
footerText: "Clack ©2024",
wordCount: 0,
});
}
}, [props.userData, props.username, currentYear, isPreview, themeName]);

return (
<div className="">
<canvas className="w-full" ref={canvasRef}></canvas>
<div className="select-overlay absolute top-0 left-0 right-0 bottom-0 flex items-center justify-center opacity-0 hover:opacity-100 transition-opacity duration-300">
<Select onValueChange={(value: string) => onThemeChange(value as keyof typeof themes)}>
<SelectTrigger className="max-w-[100px]">
<SelectValue placeholder="Select Theme" />
</SelectTrigger>
<SelectContent>
{Object.keys(themes).map((theme) => (
<SelectItem key={theme} value={theme}>
{theme}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
);
}

export default function Page({ params }: { params: { id: string } }) {
const [selectedTheme, setSelectedTheme] = useState<keyof typeof themes>("solarizedDark");
// const [username, setUsername] = useState<string>("");
const [userData, setUserData] = useState<DataStruct>();

const handleThemeChange = (theme: keyof typeof themes) => {
setSelectedTheme(theme);
};

// const generateRandomNumber = (): void => {
// setKey(Math.floor(10000000 + Math.random() * 90000000));
// };

// const [key, setKey] = useState<number>(4234_2354);

useEffect(() => {
const fetchData = async () => {
try {
if (params.id) {
const userData = await getUserData(params.id);
setUserData(userData);
// const userResponse = await fetch(`${process.env.NEXT_PUBLIC_APP_URL}/api/userDetails?id=${params.id}`);
// const user = await userResponse.text();
// setUsername(user || "");
}
} catch (error) {
console.error("Error fetching user data:", error);
}
};

fetchData();
}, [params.id]);

return (
<>
{/* <button className="mb-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" onClick={generateRandomNumber}>Refresh</button> */}
<EmbedGraph
// key={key}
isPreview={true}
themeName={selectedTheme}
username="Your year progress"
userData={userData!}
onThemeChange={handleThemeChange}
/>
</>
);
}
2 changes: 1 addition & 1 deletion src/components/graph/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function Graph(props: GraphProps) {
try {
if (userId) {
const userData = await getUserData(userId); // Add nullish coalescing operator to provide a default value
console.log("DEUBG: /components/graph ", userData);
// console.log("DEUBG: /components/graph ", userData);
setUserData(userData);
}
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/graph/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ function drawMetaData(ctx: CanvasRenderingContext2D, opts: DrawMetadataOptions)
ctx.fillStyle = theme.text;
ctx.textBaseline = "hanging";
ctx.font = `20px '${fontFace}'`;
ctx.fillText(`@${username}`, canvasMargin, canvasMargin);
ctx.fillText(`${username}`, canvasMargin, canvasMargin);

// let totalContributions = opts.wordCount
// for (const year of data.years) {
Expand Down
9 changes: 8 additions & 1 deletion src/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import authConfig from "./auth.config";
import NextAuth from "next-auth";
import { DEFAULT_LOGIN_REDIRECT, apiAuthPrefix, publicRoutes, authRoutes } from "./routes";
import { DEFAULT_LOGIN_REDIRECT, apiAuthPrefix, publicRoutes, authRoutes, embedPrefix } from "./routes";

const { auth } = NextAuth(authConfig);

Expand All @@ -10,11 +10,18 @@ export default auth((req) => {
const isApiAuthRoute = nextUrl.pathname.startsWith(apiAuthPrefix);
const isPublicRoute = publicRoutes.includes(nextUrl.pathname);
const isAuthRoute = authRoutes.includes(nextUrl.pathname);
const isEmbedRoute = nextUrl.pathname.startsWith(embedPrefix);


if (isApiAuthRoute) {
return;
}


if (isEmbedRoute) {
return
}

if (isAuthRoute) {
if (isLoggedIn) {
return Response.redirect(new URL(DEFAULT_LOGIN_REDIRECT, nextUrl)); // always pass nextUrl when using redirect and new url constructor. this constructs a proper url with the hostname
Expand Down
6 changes: 5 additions & 1 deletion src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export const publicRoutes = [
"/tos",
"/link-google-doc",
"/link-notion-page",
"/tutorials"
"/tutorials",
"/userDetails"
];

/**
Expand All @@ -26,6 +27,9 @@ export const publicRoutes = [
*/
export const authRoutes = ["/auth/login", "/auth/register", "/auth/error", "/auth/reset", "/auth/new-password"];


export const embedPrefix = "/embed";

/**
* The prefix for API authentication routes.
* Routes that start with this prefix are used for API authentication purposes.
Expand Down

0 comments on commit f4bf9e6

Please sign in to comment.