From 50fb43d17a12c3a25b368db47143a01e981a45d3 Mon Sep 17 00:00:00 2001 From: krilklem Date: Mon, 15 Dec 2025 20:27:47 +0000 Subject: [PATCH 1/5] feat: Update state with effect to gather all feedback for user --- package-lock.json | 30 +++++++++++++------ src/App.tsx | 3 ++ src/components/FeedbackGallery.tsx | 48 ++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 src/components/FeedbackGallery.tsx diff --git a/package-lock.json b/package-lock.json index f863de1..8559744 100644 --- a/package-lock.json +++ b/package-lock.json @@ -150,6 +150,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -499,6 +500,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -545,6 +547,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -2108,8 +2111,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -2232,6 +2234,7 @@ "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -2242,6 +2245,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -2339,6 +2343,7 @@ "integrity": "sha512-N9lBGA9o9aqb1hVMc9hzySbhKibHmB+N3IpoShyV6HyQYRGIhlrO5rQgttypi+yEeKsKI4idxC8Jw6gXKD4THA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.49.0", "@typescript-eslint/types": "8.49.0", @@ -2701,6 +2706,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3039,6 +3045,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -3692,8 +3699,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dunder-proto": { "version": "1.0.1", @@ -3764,6 +3770,7 @@ "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" @@ -3911,6 +3918,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5618,7 +5626,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -6655,6 +6662,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -6691,6 +6699,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -6745,7 +6754,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -6761,7 +6769,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -6838,6 +6845,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.1.tgz", "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -6847,6 +6855,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.1.tgz", "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -6859,6 +6868,7 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.68.0.tgz", "integrity": "sha512-oNN3fjrZ/Xo40SWlHf1yCjlMK417JxoSJVUXQjGdvdRCU07NTFei1i1f8ApUAts+IVh14e4EdakeLEA+BEAs/Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -6875,8 +6885,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-refresh": { "version": "0.18.0", @@ -7668,6 +7677,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7804,6 +7814,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.7.tgz", "integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -8153,6 +8164,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/App.tsx b/src/App.tsx index 11bcafe..89bfd60 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,7 @@ import "./App.css"; import CreateUser from "./components/Create-User"; import LoginUser from "./components/Login-User"; import JobForm from "./components/JobForm"; +import Feedback from "./components/FeedbackGallery"; import { Button } from "./components/ui/button"; type Theme = "light" | "dark"; @@ -71,9 +72,11 @@ function App() {
+ Feedback } /> } /> + } /> } />
diff --git a/src/components/FeedbackGallery.tsx b/src/components/FeedbackGallery.tsx new file mode 100644 index 0000000..8bc4967 --- /dev/null +++ b/src/components/FeedbackGallery.tsx @@ -0,0 +1,48 @@ +/* eslint-disable linebreak-style */ + +import supabase from "@/lib/supabaseClient"; +import { useState, useEffect } from "react"; + +type FeedbackElement = { + job_description: string | null; + gen_feedback: { + matchScore: number; + presentKeywords: string[]; + missingKeywords: string[]; + recommendations: string[]; + } | null; +} + +export default function Feedback() { + const [message, setMessage] = useState(null); + const [feedback, setFeedback] = useState([]); + + useEffect(() => { + async function fetchFeedback() { + const { data: { user }, } = await supabase.auth.getUser(); + if ( !user ) { + return setMessage("You must log in to view saved feedback"); + } + + const { data: allFeedback, error: feedbackError } = await supabase.from("jobs").select("job_description, gen_feedback"); + if ( feedbackError ) { + return setMessage("There has been an error while retrieving feedback records"); + } + + const parsed = allFeedback.map(feedback => ({job_description: feedback.job_description, gen_feedback: JSON.parse(feedback.gen_feedback ?? ""),})); + setFeedback(parsed); + + console.log(feedback); + + // let feed = JSON.parse(feedback[1].gen_feedback); + // console.log(feed); + // console.log(feedback[1].job_description); + } + fetchFeedback(); + }, []); + + + return ( + <> + ); +} From 7aaa3ddb5def6075b1a29e426dbf6923f1b56373 Mon Sep 17 00:00:00 2001 From: krilklem Date: Mon, 15 Dec 2025 20:51:49 +0000 Subject: [PATCH 2/5] feat: Display all feedback using result card mirroring the one on the analysis page --- src/components/FeedbackGallery.tsx | 24 +++++----- src/components/ResultCard.tsx | 76 ++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 13 deletions(-) create mode 100644 src/components/ResultCard.tsx diff --git a/src/components/FeedbackGallery.tsx b/src/components/FeedbackGallery.tsx index 8bc4967..e6a4265 100644 --- a/src/components/FeedbackGallery.tsx +++ b/src/components/FeedbackGallery.tsx @@ -1,7 +1,6 @@ -/* eslint-disable linebreak-style */ - import supabase from "@/lib/supabaseClient"; import { useState, useEffect } from "react"; +import ResultCard from "./ResultCard"; type FeedbackElement = { job_description: string | null; @@ -15,7 +14,7 @@ type FeedbackElement = { export default function Feedback() { const [message, setMessage] = useState(null); - const [feedback, setFeedback] = useState([]); + const [allFeedback, setAllFeedback] = useState([]); useEffect(() => { async function fetchFeedback() { @@ -24,25 +23,24 @@ export default function Feedback() { return setMessage("You must log in to view saved feedback"); } - const { data: allFeedback, error: feedbackError } = await supabase.from("jobs").select("job_description, gen_feedback"); + const { data: feedback, error: feedbackError } = await supabase.from("jobs").select("job_description, gen_feedback"); if ( feedbackError ) { return setMessage("There has been an error while retrieving feedback records"); } - const parsed = allFeedback.map(feedback => ({job_description: feedback.job_description, gen_feedback: JSON.parse(feedback.gen_feedback ?? ""),})); - setFeedback(parsed); - - console.log(feedback); - - // let feed = JSON.parse(feedback[1].gen_feedback); - // console.log(feed); - // console.log(feedback[1].job_description); + const parsed = feedback.map(f => ({job_description: f.job_description, gen_feedback: JSON.parse(f.gen_feedback ?? ""),})); + setAllFeedback(parsed); } fetchFeedback(); }, []); return ( - <> + <> + {message &&

{message}

} + {allFeedback.length > 0 && +
    {allFeedback.map(f =>
  • )}
+ } + ); } diff --git a/src/components/ResultCard.tsx b/src/components/ResultCard.tsx new file mode 100644 index 0000000..6e38f83 --- /dev/null +++ b/src/components/ResultCard.tsx @@ -0,0 +1,76 @@ +export default function ResultCard ({ jobDescription, feedback }) { + + return ( +
+
+
+
+

Job description

+
+
+
+ {jobDescription?.trim() || "—"} +
+
+
+
+ +
+
+
+

AI insights

+
+ +
+
+

Keywords found in resume

+ {feedback.presentKeywords.length ? ( +
    + {feedback.presentKeywords.map((item, idx) => ( +
  • - {item}
  • + ))} +
+ ) : ( +

No matches found.

+ )} +
+ +
+

Missing keywords

+ {feedback.missingKeywords.length ? ( +
    + {feedback.missingKeywords.map((item, idx) => ( +
  • - {item}
  • + ))} +
+ ) : ( +

+ No missing keywords returned by backend. +

+ )} +
+ +
+

Recommendations

+ {feedback.recommendations.length ? ( +
    + {feedback.recommendations.map((item, idx) => ( +
  • - {item}
  • + ))} +
+ ) : ( +

No recommendations received.

+ )} +
+
+
+
+
+ ); +} \ No newline at end of file From 6bc8505e99e6ac4d8f796216fc4bab22f8c6caa3 Mon Sep 17 00:00:00 2001 From: krilklem Date: Mon, 15 Dec 2025 21:07:44 +0000 Subject: [PATCH 3/5] fix: Add prop types --- src/components/FeedbackGallery.tsx | 2 +- src/components/ResultCard.tsx | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/components/FeedbackGallery.tsx b/src/components/FeedbackGallery.tsx index e6a4265..ecc2306 100644 --- a/src/components/FeedbackGallery.tsx +++ b/src/components/FeedbackGallery.tsx @@ -39,7 +39,7 @@ export default function Feedback() { <> {message &&

{message}

} {allFeedback.length > 0 && -
    {allFeedback.map(f =>
  • )}
+
    {allFeedback.map((item, index) =>
  • )}
} ); diff --git a/src/components/ResultCard.tsx b/src/components/ResultCard.tsx index 6e38f83..fe34a38 100644 --- a/src/components/ResultCard.tsx +++ b/src/components/ResultCard.tsx @@ -1,4 +1,16 @@ -export default function ResultCard ({ jobDescription, feedback }) { +type generatedFeedback = { + matchScore: number; + presentKeywords: string[]; + missingKeywords: string[]; + recommendations: string[]; +} + +type FeedbackElement = { + jobDescription: string | null; + feedback: generatedFeedback | null; +} + +export default function ResultCard ({ jobDescription, feedback }: FeedbackElement) { return (
@@ -30,7 +42,7 @@ export default function ResultCard ({ jobDescription, feedback }) {

Keywords found in resume

- {feedback.presentKeywords.length ? ( + {feedback?.presentKeywords.length ? (
    {feedback.presentKeywords.map((item, idx) => (
  • - {item}
  • @@ -43,7 +55,7 @@ export default function ResultCard ({ jobDescription, feedback }) {

    Missing keywords

    - {feedback.missingKeywords.length ? ( + {feedback?.missingKeywords.length ? (
      {feedback.missingKeywords.map((item, idx) => (
    • - {item}
    • @@ -58,7 +70,7 @@ export default function ResultCard ({ jobDescription, feedback }) {

      Recommendations

      - {feedback.recommendations.length ? ( + {feedback?.recommendations.length ? (
        {feedback.recommendations.map((item, idx) => (
      • - {item}
      • From 97380314878e19d9562fc23e24b2c630e29ae494 Mon Sep 17 00:00:00 2001 From: krilklem Date: Mon, 15 Dec 2025 22:59:25 +0000 Subject: [PATCH 4/5] feat: Track auth state to change header links appropriate, show link to feedback if user is singed in --- src/App.tsx | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 89bfd60..059c418 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { Routes, Route, Link } from "react-router-dom"; import { Moon, Sun } from "lucide-react"; - +import supabase from "@/lib/supabaseClient"; import "./App.css"; import CreateUser from "./components/Create-User"; import LoginUser from "./components/Login-User"; @@ -25,6 +25,7 @@ function getStoredTheme(): Theme { function App() { const [theme, setTheme] = useState(() => getStoredTheme()); + const [isSignedIn, setIsSignedIn] = useState(false); useEffect(() => { const root = document.documentElement; @@ -37,6 +38,25 @@ function App() { } }, [theme]); + useEffect(() => { + async function getSignedStatus() { + const { data: { user }, } = await supabase.auth.getUser(); + setIsSignedIn(!!user); + } + + const { data: { subscription} } = supabase.auth.onAuthStateChange((_event, session) => { + setIsSignedIn(!!session?.user); + }); + + getSignedStatus(); + + return () => { subscription.unsubscribe() }; + }, []); + + const handleLogOut = async () => { + await supabase.auth.signOut(); + }; + const toggleTheme = () => setTheme((prev) => (prev === "dark" ? "light" : "dark")); @@ -48,11 +68,11 @@ function App() {
      We highlight what matters
      - Join to us + {isSignedIn ? Past feedback : Join to us} - Sign In + {isSignedIn ? Log out : Sign In}
      We highlight what matters
      - {isSignedIn ? Past feedback : Join to us} + {isSignedIn ? ( + Past feedback + ) : ( + Join to us + )} - {isSignedIn ? Log out : Sign In} + {isSignedIn ? ( + + Log out + + ) : ( + Sign In + )}