From a86b098cc1719517d24162c904f8c5337ecc8ac3 Mon Sep 17 00:00:00 2001 From: Nikita Choudhary Date: Wed, 27 May 2026 13:41:47 +0530 Subject: [PATCH] Added today's plan note wedge component --- package-lock.json | 20 --- src/app/api/daily-note/route.ts | 121 +++++++++++++++++ src/app/dashboard/page.tsx | 2 + src/app/page.tsx | 1 - src/components/DailyNoteWidget.tsx | 126 ++++++++++++++++++ .../20260515000002_add_daily_notes.sql | 10 ++ supabase/schema.sql | 11 ++ 7 files changed, 270 insertions(+), 21 deletions(-) create mode 100644 src/app/api/daily-note/route.ts create mode 100644 src/components/DailyNoteWidget.tsx create mode 100644 supabase/migrations/20260515000002_add_daily_notes.sql diff --git a/package-lock.json b/package-lock.json index f69174db7..918443c02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,7 +69,6 @@ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "license": "MIT", - "peer": true, "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" @@ -80,7 +79,6 @@ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -938,7 +936,6 @@ "integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "playwright": "1.49.1" }, @@ -1418,7 +1415,6 @@ "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.106.1.tgz", "integrity": "sha512-gP4HurGkGu7Z3xoOCjtAI17BKKp7jpsmwY0Ssbsks9XQRzJ7ZhK7LxfLdBSYgUdgZCQgjRK+Mr7+cl4Gxrk0Rw==", "license": "MIT", - "peer": true, "dependencies": { "@supabase/auth-js": "2.106.1", "@supabase/functions-js": "2.106.1", @@ -1540,7 +1536,6 @@ "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1571,7 +1566,6 @@ "integrity": "sha512-ch0qJdr2JY0r04NXSprbK6TXOgnaJ1Tz23fm5W+z0/CBah6BSBc3n96h7K9GOtwh0HrilNWHIBzE1Ko4Dcw/Wg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -2316,7 +2310,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2792,7 +2785,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -3773,7 +3765,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -3942,7 +3933,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -5469,7 +5459,6 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -5541,7 +5530,6 @@ "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.2.1.tgz", "integrity": "sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.28.6", "fast-png": "^6.2.0", @@ -5905,7 +5893,6 @@ "resolved": "https://registry.npmjs.org/next/-/next-14.2.35.tgz", "integrity": "sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig==", "license": "MIT", - "peer": true, "dependencies": { "@next/env": "14.2.35", "@swc/helpers": "0.5.5", @@ -6578,7 +6565,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", @@ -6749,7 +6735,6 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.2.tgz", "integrity": "sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ==", "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -6840,7 +6825,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -6853,7 +6837,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -7886,7 +7869,6 @@ "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -8045,7 +8027,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8245,7 +8226,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/src/app/api/daily-note/route.ts b/src/app/api/daily-note/route.ts new file mode 100644 index 000000000..48ce96276 --- /dev/null +++ b/src/app/api/daily-note/route.ts @@ -0,0 +1,121 @@ +import { NextResponse ,NextRequest} from "next/server"; +import { supabaseAdmin } from "@/lib/supabase"; +import { getToken } from "next-auth/jwt"; + +export async function GET(req: NextRequest){ + + try{ + const token = await getToken({ + req, + secret: process.env.NEXTAUTH_SECRET, + }); + console.log(token) + + const userId = token?.githubId; + + if(!userId){ + return NextResponse.json( + {error: `Unauthorized`}, + {status: 400} + ); + } + + const today = new Date(); + const todayDate = today.toISOString().split("T")[0]; + + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() -1); + + const yesterdayDate = yesterday.toISOString().split("T")[0]; + + const {data : todayData} = await supabaseAdmin + .from("daily_notes") + .select("*") + .eq("user_id",userId) + .eq("date",todayDate) + .single(); + + const {data : yesterdayData} = await supabaseAdmin + .from("daily_notes") + .select("*") + .eq("user_id",userId) + .eq("date",yesterdayDate) + .single(); + console.log(todayData,yesterdayData); + return NextResponse.json({ + todayNote: todayData?.note || "", + yesterdayNote: yesterdayData?.note || "", + }); + + }catch(error){ + return NextResponse.json( + {error: `Something went wrong `}, + {status: 500} + ); + + } +} + +export async function POST(req: NextRequest) { + try{ + + const token = await getToken({ + req, + secret: process.env.NEXTAUTH_SECRET, + }); + console.log(token) + + const userId = token?.githubId; + const body = await req.json(); + console.log(body); + const { note} = body; + if(!userId){ + console.log("no user id"); + return NextResponse.json( + {error: "User id is requied "}, + {status: 400} + ); + } + if(!note || !note.trim()){ + console.log("no note "); + return NextResponse.json( + {error: "Note cannot be empty"}, + {status: 400} + ); + } + if(note.length >280){ + console.log("max len "); + return NextResponse.json( + {error: "Maximum 280 characters allowed"}, + {status: 400} + ); + } + const today = new Date().toISOString().split("T")[0]; + const {data, error} = await supabaseAdmin + .from("daily_notes") + .upsert({ + user_id: userId, + date: today, + note: note.trim(), + },{ + onConflict:"user_id,date" + }) + .select() + .single(); + if(error){ + console.log(error.message); + return NextResponse.json( + {error: error.message}, + {status:500} + ); + } + console.log(data); + return NextResponse.json(data); + }catch(error){ + console.log(error); + return NextResponse.json( + {error: "Something went wrong"}, + {status:500} + ); + } +} \ No newline at end of file diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 159cf9e28..044d418c1 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -30,6 +30,7 @@ import RecentActivity from "@/components/RecentActivity"; import { authOptions } from "@/lib/auth"; import { getServerSession } from "next-auth"; import { redirect } from "next/navigation"; +import DailyNoteWidget from "@/components/DailyNoteWidget"; export default async function DashboardPage() { const session = await getServerSession(authOptions); @@ -77,6 +78,7 @@ export default async function DashboardPage() {
+
diff --git a/src/app/page.tsx b/src/app/page.tsx index b4ca229a3..da779f608 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -69,7 +69,6 @@ async function fetchRepoStats(): Promise { export default async function HomePage() { const session = await getServerSession(authOptions); - if (session) { redirect("/dashboard"); } diff --git a/src/components/DailyNoteWidget.tsx b/src/components/DailyNoteWidget.tsx new file mode 100644 index 000000000..77388c489 --- /dev/null +++ b/src/components/DailyNoteWidget.tsx @@ -0,0 +1,126 @@ +"use client"; + +import { useEffect, useState } from "react"; + +export default function DailyNoteWidget(){ + + const [loading,setLoading] = useState(false); + const [note,setNote] = useState(""); + const [yesterdayNote, setYesterdayNote] = useState(""); + const [showYesterday,setShowYesterday] = useState(false); + + useEffect(()=>{ + + const fetchNotes = async ()=>{ + + try{ + setLoading(true); + + const response = await fetch("/api/daily-note"); + + const data = await response.json(); + setNote(data.todayNote||""); + setYesterdayNote(data.yesterdayNote ||"" ); + + }catch(error){ + console.error("Failed to fetch notes"); + }finally{ + setLoading(false); + } + + }; + fetchNotes(); + },[]); + + +// auto save with debounce +const debounceFunction = async()=>{ + + if(!note.trim()) return ; + + try{ + await fetch("/api/daily-note",{ + method:"POST", + headers:{ + "Content-Type": "application/json", + }, + body: JSON.stringify({ + note, + }), + }); + }catch(error){ + console.error("Failed to save note"); + } +} +useEffect(()=>{ + + const timeout= setTimeout(()=>{ + debounceFunction(); + },500); + return ()=>clearTimeout(timeout); +},[note]); + + +if (loading) { + return ( +
+
+
+
+
+
+
+ ); +} + + return( + +
+
+

+ Today's Plan` ` +

+ +

Plan your coding session.

+
+
+ + +