From d4429488aa3ef3015d0128523747c1db42ec7496 Mon Sep 17 00:00:00 2001 From: Tiffany Uong Date: Sun, 1 Feb 2026 20:28:07 -0500 Subject: [PATCH 01/65] styled model --- .../src/app/_components/reviews/role-info.tsx | 84 +++++++++---------- 1 file changed, 39 insertions(+), 45 deletions(-) diff --git a/apps/web/src/app/_components/reviews/role-info.tsx b/apps/web/src/app/_components/reviews/role-info.tsx index 44b95c9a..b5143981 100644 --- a/apps/web/src/app/_components/reviews/role-info.tsx +++ b/apps/web/src/app/_components/reviews/role-info.tsx @@ -234,56 +234,50 @@ export function RoleInfo({ className, roleObj, onBack }: RoleCardProps) { )} +
+

On the Job

-
- - {averages.data && ( -
-
- - +
+ {/* work model */} +
+
+
+ Work model
+
placeholder
+
+
+ {/* donut chart */} +
+
-
- {perks && - Object.entries(perks).map( - ([perk, value]: [string, number]) => ( -
0.5 ? "text-[#141414]" : "text-[#7d7d7d]"}`} - > - {value > 0.5 ? ( - check mark - ) : ( - x mark - )} - - {perk} -
- ), - )} + {/* benefits */} +
+
Benefits
+ {perks && + Object.entries(perks).map(([perk, value]) => ( +
0.5 ? "text-[#141414]" : "text-[#7d7d7d]" + } + > + {perk} +
+ ))} +
+ {/* culture */} +
+
+ Company culture +
+
Based on
+
+ {averages.data?.averageSupervisorRating}
- )} - +
+
{averages.data && (
From 0f85ae96eadaaa886d5815f8e77f9177922733f1 Mon Sep 17 00:00:00 2001 From: Tiffany Uong Date: Thu, 5 Feb 2026 01:03:18 -0500 Subject: [PATCH 02/65] donut chart --- apps/web/package.json | 1 + .../app/_components/reviews/donut-chart.tsx | 63 +++++++++++++++++++ pnpm-lock.yaml | 30 ++++++++- 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/app/_components/reviews/donut-chart.tsx diff --git a/apps/web/package.json b/apps/web/package.json index f8cc79ac..be7b4655 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -26,6 +26,7 @@ "@trpc/server": "11.8.0", "bad-words": "^4.0.0", "dayjs": "^1.11.13", + "echarts": "^6.0.0", "fuse.js": "^7.0.0", "geist": "^1.3.0", "lucide-react": "^0.436.0", diff --git a/apps/web/src/app/_components/reviews/donut-chart.tsx b/apps/web/src/app/_components/reviews/donut-chart.tsx new file mode 100644 index 00000000..5cc1067f --- /dev/null +++ b/apps/web/src/app/_components/reviews/donut-chart.tsx @@ -0,0 +1,63 @@ +import React, { useEffect, useRef } from 'react'; +import * as echarts from 'echarts'; + + +type Data = { + value?: number; + name?: string; +}[]; + +interface DonutChartProps { + data: Data; + width?: string; + height?: string; +} + +const DonutChart: React.FC = ({ data, width = '100%', height = '400px' }) => { + const chartRef = useRef(null); + const chartInstance = useRef(null); + + + useEffect(() => { + if (chartRef.current) { + chartInstance.current = echarts.init(chartRef.current); + + const option: echarts.EChartsOption = { + tooltip: { show: false }, + legend: { left: '50%', + top: '20%',orient: 'vertical', itemWidth: 25, itemHeight: 25, icon: 'circle',selectedMode: false }, + series: [ + { + name: 'Access From', + type: 'pie', + radius: ['25%', '55%'], + center: ['20%', '40%'], + avoidLabelOverlap: false, + itemStyle: { + borderRadius: 10, + borderColor: '#fff', + borderWidth: 2 + }, + color: ['#edd8af', '#caedea', '#ddb4e0'], + label: { show: false, position: 'center' }, + emphasis: { + scale: false, + label: { show: false } + }, + labelLine: { show: false }, + data + } + ] + }; + + chartInstance.current.setOption(option); + } + return () => { + chartInstance.current?.dispose(); + }; + }, [data]); + + return
; +}; + +export default DonutChart; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cfbf35e6..ffe0bd5f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -176,6 +176,9 @@ importers: dayjs: specifier: ^1.11.13 version: 1.11.13 + echarts: + specifier: ^6.0.0 + version: 6.0.0 fuse.js: specifier: ^7.0.0 version: 7.0.0 @@ -3691,6 +3694,7 @@ packages: '@vercel/postgres@0.9.0': resolution: {integrity: sha512-WiI2g3+ce2g1u1gP41MoDj2DsMuQQ+us7vHobysRixKECGaLHpfTI7DuVZmHU087ozRAGr3GocSyqmWLLo+fig==} engines: {node: '>=14.6'} + deprecated: '@vercel/postgres is deprecated. You can either choose an alternate storage solution from the Vercel Marketplace if you want to set up a new database. Or you can follow this guide to migrate your existing Vercel Postgres db: https://neon.com/docs/guides/vercel-postgres-transition-guide' '@vitest/expect@2.1.9': resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} @@ -5084,6 +5088,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + echarts@6.0.0: + resolution: {integrity: sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -5688,16 +5695,17 @@ packages: glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} @@ -8719,6 +8727,7 @@ packages: tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me terser-webpack-plugin@5.3.10: resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} @@ -8855,6 +8864,9 @@ packages: tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + tslib@2.6.3: resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} @@ -9501,6 +9513,9 @@ packages: zod@3.24.2: resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} + zrender@6.0.0: + resolution: {integrity: sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -14570,6 +14585,11 @@ snapshots: eastasianwidth@0.2.0: {} + echarts@6.0.0: + dependencies: + tslib: 2.3.0 + zrender: 6.0.0 + ee-first@1.1.1: {} electron-to-chromium@1.4.832: {} @@ -19249,6 +19269,8 @@ snapshots: tslib@1.14.1: {} + tslib@2.3.0: {} + tslib@2.6.3: {} turbo-darwin-64@2.1.1: @@ -19976,4 +19998,8 @@ snapshots: zod@3.24.2: {} + zrender@6.0.0: + dependencies: + tslib: 2.3.0 + zwitch@2.0.4: {} From 826a8353f93209ff8f847109b6ce2716633fc3f0 Mon Sep 17 00:00:00 2001 From: Tiffany Uong Date: Thu, 5 Feb 2026 01:13:35 -0500 Subject: [PATCH 03/65] donut values and sort benefits --- .../src/app/_components/reviews/role-info.tsx | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/apps/web/src/app/_components/reviews/role-info.tsx b/apps/web/src/app/_components/reviews/role-info.tsx index b5143981..29457cde 100644 --- a/apps/web/src/app/_components/reviews/role-info.tsx +++ b/apps/web/src/app/_components/reviews/role-info.tsx @@ -25,6 +25,11 @@ import { ReviewCard } from "./review-card"; import ReviewSearchBar from "./review-search-bar"; import RoundBarGraph from "./round-bar-graph"; import type { ReviewType, RoleType } from "@cooper/db/schema"; +import DonutChart from './donut-chart'; +import { + calculateWorkModels +} from "~/utils/companyStatistics"; + interface RoleCardProps { className?: string; @@ -128,7 +133,21 @@ export function RoleInfo({ className, roleObj, onBack }: RoleCardProps) { review.interviewReview?.toLowerCase().includes(searchTerm.toLowerCase()); return ratingMatch && searchMatch; - }); +}); + + const workModels = calculateWorkModels(reviews.data); + +const topWorkModel = + workModels.reduce((max, m) => + Number(m.percentage) > Number(max?.percentage) ? m : max + , workModels[0])?.name; + + +const data = workModels.map(m => ({ + value: (m.percentage / 100) * m.count, + name: `${m.name} ${m.percentage}%` +})); + return (
Work model
-
placeholder
+
{topWorkModel}
+ {/* donut chart */} +
- {/* donut chart */}
@@ -255,7 +275,8 @@ export function RoleInfo({ className, roleObj, onBack }: RoleCardProps) {
Benefits
{perks && - Object.entries(perks).map(([perk, value]) => ( + Object.entries(perks).sort(([, a], [, b]) => Number(b > 0.5) - Number(a > 0.5)) + .map(([perk, value]) => (
Based on
- {averages.data?.averageSupervisorRating} + {Math.round((averages.data?.averageSupervisorRating ?? 0)*10)/10}
From 29990a7c45ad6ad84080849d72c697ac40e4dfa1 Mon Sep 17 00:00:00 2001 From: Tiffany Uong Date: Thu, 5 Feb 2026 01:20:28 -0500 Subject: [PATCH 04/65] fixed linting --- .../app/_components/reviews/donut-chart.tsx | 49 ++++++++++------- .../src/app/_components/reviews/role-info.tsx | 54 +++++++++---------- 2 files changed, 55 insertions(+), 48 deletions(-) diff --git a/apps/web/src/app/_components/reviews/donut-chart.tsx b/apps/web/src/app/_components/reviews/donut-chart.tsx index 5cc1067f..bd67ad90 100644 --- a/apps/web/src/app/_components/reviews/donut-chart.tsx +++ b/apps/web/src/app/_components/reviews/donut-chart.tsx @@ -1,6 +1,5 @@ -import React, { useEffect, useRef } from 'react'; -import * as echarts from 'echarts'; - +import React, { useEffect, useRef } from "react"; +import * as echarts from "echarts"; type Data = { value?: number; @@ -13,41 +12,51 @@ interface DonutChartProps { height?: string; } -const DonutChart: React.FC = ({ data, width = '100%', height = '400px' }) => { +const DonutChart: React.FC = ({ + data, + width = "100%", + height = "400px", +}) => { const chartRef = useRef(null); const chartInstance = useRef(null); - useEffect(() => { if (chartRef.current) { chartInstance.current = echarts.init(chartRef.current); const option: echarts.EChartsOption = { tooltip: { show: false }, - legend: { left: '50%', - top: '20%',orient: 'vertical', itemWidth: 25, itemHeight: 25, icon: 'circle',selectedMode: false }, + legend: { + left: "50%", + top: "20%", + orient: "vertical", + itemWidth: 25, + itemHeight: 25, + icon: "circle", + selectedMode: false, + }, series: [ { - name: 'Access From', - type: 'pie', - radius: ['25%', '55%'], - center: ['20%', '40%'], + name: "Access From", + type: "pie", + radius: ["25%", "55%"], + center: ["20%", "40%"], avoidLabelOverlap: false, itemStyle: { borderRadius: 10, - borderColor: '#fff', - borderWidth: 2 + borderColor: "#fff", + borderWidth: 2, }, - color: ['#edd8af', '#caedea', '#ddb4e0'], - label: { show: false, position: 'center' }, + color: ["#edd8af", "#caedea", "#ddb4e0"], + label: { show: false, position: "center" }, emphasis: { - scale: false, - label: { show: false } + scale: false, + label: { show: false }, }, labelLine: { show: false }, - data - } - ] + data, + }, + ], }; chartInstance.current.setOption(option); diff --git a/apps/web/src/app/_components/reviews/role-info.tsx b/apps/web/src/app/_components/reviews/role-info.tsx index 29457cde..75438786 100644 --- a/apps/web/src/app/_components/reviews/role-info.tsx +++ b/apps/web/src/app/_components/reviews/role-info.tsx @@ -25,11 +25,8 @@ import { ReviewCard } from "./review-card"; import ReviewSearchBar from "./review-search-bar"; import RoundBarGraph from "./round-bar-graph"; import type { ReviewType, RoleType } from "@cooper/db/schema"; -import DonutChart from './donut-chart'; -import { - calculateWorkModels -} from "~/utils/companyStatistics"; - +import DonutChart from "./donut-chart"; +import { calculateWorkModels } from "~/utils/companyStatistics"; interface RoleCardProps { className?: string; @@ -133,21 +130,19 @@ export function RoleInfo({ className, roleObj, onBack }: RoleCardProps) { review.interviewReview?.toLowerCase().includes(searchTerm.toLowerCase()); return ratingMatch && searchMatch; -}); + }); const workModels = calculateWorkModels(reviews.data); -const topWorkModel = - workModels.reduce((max, m) => - Number(m.percentage) > Number(max?.percentage) ? m : max - , workModels[0])?.name; - - -const data = workModels.map(m => ({ - value: (m.percentage / 100) * m.count, - name: `${m.name} ${m.percentage}%` -})); + const topWorkModel = workModels.reduce( + (max, m) => (Number(m.percentage) > Number(max?.percentage) ? m : max), + workModels[0], + )?.name; + const data = workModels.map((m) => ({ + value: (m.percentage / 100) * m.count, + name: `${m.name} ${m.percentage}%`, + })); return (
({ Work model
{topWorkModel}
- {/* donut chart */} + {/* donut chart */}
@@ -275,17 +270,18 @@ const data = workModels.map(m => ({
Benefits
{perks && - Object.entries(perks).sort(([, a], [, b]) => Number(b > 0.5) - Number(a > 0.5)) + Object.entries(perks) + .sort(([, a], [, b]) => Number(b > 0.5) - Number(a > 0.5)) .map(([perk, value]) => ( -
0.5 ? "text-[#141414]" : "text-[#7d7d7d]" - } - > - {perk} -
- ))} +
0.5 ? "text-[#141414]" : "text-[#7d7d7d]" + } + > + {perk} +
+ ))}
{/* culture */}
@@ -294,7 +290,9 @@ const data = workModels.map(m => ({
Based on
- {Math.round((averages.data?.averageSupervisorRating ?? 0)*10)/10} + {Math.round( + (averages.data?.averageSupervisorRating ?? 0) * 10, + ) / 10}
From 2e12cdeb8835e304b628f0fce1e7ff0df9598e8b Mon Sep 17 00:00:00 2001 From: Tiffany Uong Date: Thu, 5 Feb 2026 20:32:59 -0500 Subject: [PATCH 05/65] new modal container --- apps/web/src/app/_components/reviews/role-info.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/web/src/app/_components/reviews/role-info.tsx b/apps/web/src/app/_components/reviews/role-info.tsx index 75438786..4f495462 100644 --- a/apps/web/src/app/_components/reviews/role-info.tsx +++ b/apps/web/src/app/_components/reviews/role-info.tsx @@ -27,6 +27,7 @@ import RoundBarGraph from "./round-bar-graph"; import type { ReviewType, RoleType } from "@cooper/db/schema"; import DonutChart from "./donut-chart"; import { calculateWorkModels } from "~/utils/companyStatistics"; +import ModalContainer from "./modal"; interface RoleCardProps { className?: string; @@ -248,18 +249,17 @@ export function RoleInfo({ className, roleObj, onBack }: RoleCardProps) {
)} -
+

On the Job

{/* work model */}
-
+
Work model
{topWorkModel}
- {/* donut chart */}
@@ -268,7 +268,9 @@ export function RoleInfo({ className, roleObj, onBack }: RoleCardProps) { {/* benefits */}
-
Benefits
+
+ Benefits +
{perks && Object.entries(perks) .sort(([, a], [, b]) => Number(b > 0.5) - Number(a > 0.5)) @@ -285,7 +287,7 @@ export function RoleInfo({ className, roleObj, onBack }: RoleCardProps) {
{/* culture */}
-
+
Company culture
Based on
@@ -297,7 +299,7 @@ export function RoleInfo({ className, roleObj, onBack }: RoleCardProps) {
-
+
{averages.data && (
From 18dd37bdab31e057b1c6159ad342b5a5db844461 Mon Sep 17 00:00:00 2001 From: Tiffany Uong Date: Thu, 5 Feb 2026 20:34:19 -0500 Subject: [PATCH 06/65] modal --- apps/web/src/app/_components/reviews/modal.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 apps/web/src/app/_components/reviews/modal.tsx diff --git a/apps/web/src/app/_components/reviews/modal.tsx b/apps/web/src/app/_components/reviews/modal.tsx new file mode 100644 index 00000000..88d0dc42 --- /dev/null +++ b/apps/web/src/app/_components/reviews/modal.tsx @@ -0,0 +1,17 @@ +import React, { ReactNode } from "react"; + +interface ModalContainerProps { + children: ReactNode; +} + +const ModalContainer: React.FC = ({ children }) => { + return ( +
+ {children} +
+ ); +}; + +export default ModalContainer; From 6636d9d5e25394b2bd4cdf6a0ed20d9de097feca Mon Sep 17 00:00:00 2001 From: Tiffany Uong Date: Thu, 12 Feb 2026 21:21:52 -0500 Subject: [PATCH 07/65] separated role info from modal and replaced collapsable containers with new modal styling --- .../app/_components/reviews/donut-chart.tsx | 72 ------------ .../web/src/app/_components/reviews/modal.tsx | 5 +- .../src/app/_components/reviews/role-info.tsx | 103 +++++++++--------- 3 files changed, 52 insertions(+), 128 deletions(-) delete mode 100644 apps/web/src/app/_components/reviews/donut-chart.tsx diff --git a/apps/web/src/app/_components/reviews/donut-chart.tsx b/apps/web/src/app/_components/reviews/donut-chart.tsx deleted file mode 100644 index bd67ad90..00000000 --- a/apps/web/src/app/_components/reviews/donut-chart.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React, { useEffect, useRef } from "react"; -import * as echarts from "echarts"; - -type Data = { - value?: number; - name?: string; -}[]; - -interface DonutChartProps { - data: Data; - width?: string; - height?: string; -} - -const DonutChart: React.FC = ({ - data, - width = "100%", - height = "400px", -}) => { - const chartRef = useRef(null); - const chartInstance = useRef(null); - - useEffect(() => { - if (chartRef.current) { - chartInstance.current = echarts.init(chartRef.current); - - const option: echarts.EChartsOption = { - tooltip: { show: false }, - legend: { - left: "50%", - top: "20%", - orient: "vertical", - itemWidth: 25, - itemHeight: 25, - icon: "circle", - selectedMode: false, - }, - series: [ - { - name: "Access From", - type: "pie", - radius: ["25%", "55%"], - center: ["20%", "40%"], - avoidLabelOverlap: false, - itemStyle: { - borderRadius: 10, - borderColor: "#fff", - borderWidth: 2, - }, - color: ["#edd8af", "#caedea", "#ddb4e0"], - label: { show: false, position: "center" }, - emphasis: { - scale: false, - label: { show: false }, - }, - labelLine: { show: false }, - data, - }, - ], - }; - - chartInstance.current.setOption(option); - } - return () => { - chartInstance.current?.dispose(); - }; - }, [data]); - - return
; -}; - -export default DonutChart; diff --git a/apps/web/src/app/_components/reviews/modal.tsx b/apps/web/src/app/_components/reviews/modal.tsx index 88d0dc42..2d13ea6f 100644 --- a/apps/web/src/app/_components/reviews/modal.tsx +++ b/apps/web/src/app/_components/reviews/modal.tsx @@ -2,11 +2,12 @@ import React, { ReactNode } from "react"; interface ModalContainerProps { children: ReactNode; + title?: string; } -const ModalContainer: React.FC = ({ children }) => { +const ModalContainer: React.FC = ({ children, title }) => { return ( -
{children} diff --git a/apps/web/src/app/_components/reviews/role-info.tsx b/apps/web/src/app/_components/reviews/role-info.tsx index 66f1e1e8..ab7ca35e 100644 --- a/apps/web/src/app/_components/reviews/role-info.tsx +++ b/apps/web/src/app/_components/reviews/role-info.tsx @@ -19,13 +19,11 @@ import { calculateRatings } from "~/utils/reviewCountByStars"; import { CompanyPopup } from "../companies/company-popup"; import StarGraph from "../shared/star-graph"; import BarGraph from "./bar-graph"; -import CollapsableInfoCard from "./collapsable-info"; import InfoCard from "./info-card"; import { ReviewCard } from "./review-card"; import ReviewSearchBar from "./review-search-bar"; import RoundBarGraph from "./round-bar-graph"; import type { ReviewType, RoleType } from "@cooper/db/schema"; -import DonutChart from "./donut-chart"; import { calculateWorkModels } from "~/utils/companyStatistics"; import ModalContainer from "./modal"; import { CompareControls } from "../compare/compare-ui"; @@ -273,60 +271,57 @@ export function RoleInfo({ className, roleObj, onBack }: RoleCardProps) {
)} - -

On the Job

- -
- {/* work model */} -
-
-
- Work model + + {averages.data && ( +
+
+ +
-
{topWorkModel}
- -
-
-
-
- {/* benefits */} -
-
- Benefits -
- {perks && - Object.entries(perks) - .sort(([, a], [, b]) => Number(b > 0.5) - Number(a > 0.5)) - .map(([perk, value]) => ( -
0.5 ? "text-[#141414]" : "text-[#7d7d7d]" - } - > - {perk} -
- ))} -
- {/* culture */} -
-
- Company culture -
-
Based on
-
- {Math.round( - (averages.data?.averageSupervisorRating ?? 0) * 10, - ) / 10} +
+ {perks && + Object.entries(perks).map( + ([perk, value]: [string, number]) => ( +
0.5 ? "text-[#141414]" : "text-[#7d7d7d]"}`} + > + {value > 0.5 ? ( + check mark + ) : ( + x mark + )} + + {perk} +
+ ), + )}
-
-
+ )} {averages.data && (
- +
Pay range
@@ -388,11 +383,11 @@ export function RoleInfo({ className, roleObj, onBack }: RoleCardProps) { />
-
+
)}
- + {averages.data && (
)} -
+
- + {reviews.isSuccess && reviews.data.length === 0 && (

No reviews yet

@@ -471,7 +466,7 @@ export function RoleInfo({ className, roleObj, onBack }: RoleCardProps) { )}
)} -
+
From 9a8d770b494f837abcaff47cd0b3a386b619d042 Mon Sep 17 00:00:00 2001 From: Tiffany Uong Date: Thu, 12 Feb 2026 21:24:12 -0500 Subject: [PATCH 08/65] formatting --- .../app/(pages)/(dashboard)/(roles)/page.tsx | 20 ++-- .../src/app/(pages)/(protected)/layout.tsx | 1 + .../app/(pages)/(protected)/profile/page.tsx | 11 ++- .../(pages)/(protected)/review-form/page.tsx | 55 ++++++----- .../src/app/_components/auth/login-button.tsx | 3 +- .../_components/companies/company-about.tsx | 4 +- .../_components/companies/company-popup.tsx | 13 +-- .../_components/filters/dropdown-filter.tsx | 25 +++-- .../filters/dropdown-filters-bar.tsx | 9 +- .../app/_components/filters/filter-body.tsx | 31 +++--- .../_components/filters/sidebar-filter.tsx | 59 ++++++----- .../_components/filters/sidebar-section.tsx | 12 +-- .../form/sections/company-details-section.tsx | 14 +-- .../form/sections/interview-section.tsx | 9 +- .../_components/form/sections/pay-section.tsx | 28 +++--- .../form/sections/review-section.tsx | 8 +- .../app/_components/header/header-layout.tsx | 4 +- .../web/src/app/_components/header/header.tsx | 12 +-- .../onboarding/onboarding-form.tsx | 6 +- .../profile/profile-card-header.tsx | 11 ++- .../web/src/app/_components/reviews/modal.tsx | 3 +- .../new-review/existing-company-content.tsx | 90 ++++++++--------- .../app/_components/reviews/review-card.tsx | 26 ++--- .../_components/reviews/role-card-preview.tsx | 4 +- .../src/app/_components/reviews/role-info.tsx | 98 +++++++++---------- .../_components/themed/onboarding/input.tsx | 5 +- .../_components/themed/onboarding/select.tsx | 5 +- apps/web/src/utils/companyStatistics.ts | 1 + packages/api/src/router/profile.ts | 3 +- packages/db/src/schema.ts | 4 +- 30 files changed, 295 insertions(+), 279 deletions(-) diff --git a/apps/web/src/app/(pages)/(dashboard)/(roles)/page.tsx b/apps/web/src/app/(pages)/(dashboard)/(roles)/page.tsx index b2031bd8..a6ec922d 100644 --- a/apps/web/src/app/(pages)/(dashboard)/(roles)/page.tsx +++ b/apps/web/src/app/(pages)/(dashboard)/(roles)/page.tsx @@ -1,8 +1,8 @@ "use client"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { useRouter, useSearchParams } from "next/navigation"; import Image from "next/image"; +import { useRouter, useSearchParams } from "next/navigation"; import { ChevronDown } from "lucide-react"; import type { CompanyType, RoleType } from "@cooper/db/schema"; @@ -15,6 +15,7 @@ import { DropdownMenuTrigger, } from "@cooper/ui/dropdown-menu"; +import type { FilterState } from "~/app/_components/filters/types"; import { CompanyCardPreview } from "~/app/_components/companies/company-card-preview"; import CompanyInfo from "~/app/_components/companies/company-info"; import { useCompare } from "~/app/_components/compare/compare-context"; @@ -23,15 +24,14 @@ import { CompareControls, } from "~/app/_components/compare/compare-ui"; import DropdownFiltersBar from "~/app/_components/filters/dropdown-filters-bar"; +import RoleTypeSelector from "~/app/_components/filters/role-type-selector"; +import SidebarFilter from "~/app/_components/filters/sidebar-filter"; import LoadingResults from "~/app/_components/loading-results"; import NoResults from "~/app/_components/no-results"; import { RoleCardPreview } from "~/app/_components/reviews/role-card-preview"; import { RoleInfo } from "~/app/_components/reviews/role-info"; import SearchFilter from "~/app/_components/search/search-filter"; -import SidebarFilter from "~/app/_components/filters/sidebar-filter"; import { api } from "~/trpc/react"; -import RoleTypeSelector from "~/app/_components/filters/role-type-selector"; -import type { FilterState } from "~/app/_components/filters/types"; // Helper function to create URL-friendly slugs (still needed for URL generation) const createSlug = (text: string): string => { @@ -466,22 +466,22 @@ export default function Roles() { }; return ( -
+
-
+
{rolesAndCompanies.isSuccess && rolesAndCompanies.data.items.length > 0 && ( -
+
{/* RoleCardPreview List */}
-
+
Sort By{" "} diff --git a/apps/web/src/app/(pages)/(protected)/layout.tsx b/apps/web/src/app/(pages)/(protected)/layout.tsx index b98d768c..648d0bb4 100644 --- a/apps/web/src/app/(pages)/(protected)/layout.tsx +++ b/apps/web/src/app/(pages)/(protected)/layout.tsx @@ -2,6 +2,7 @@ import { redirect } from "next/navigation"; import { auth } from "@cooper/auth"; import { CustomToaster } from "@cooper/ui"; + import HeaderLayout from "~/app/_components/header/header-layout"; export default async function ProtectedLayour({ diff --git a/apps/web/src/app/(pages)/(protected)/profile/page.tsx b/apps/web/src/app/(pages)/(protected)/profile/page.tsx index 497abc67..d00e3c23 100644 --- a/apps/web/src/app/(pages)/(protected)/profile/page.tsx +++ b/apps/web/src/app/(pages)/(protected)/profile/page.tsx @@ -2,16 +2,17 @@ import { useEffect } from "react"; import Image from "next/image"; +import Link from "next/link"; import { redirect, useSearchParams } from "next/navigation"; -import ProfileCardHeader from "~/app/_components/profile/profile-card-header"; + +import { Button } from "@cooper/ui/button"; import FavoriteCompanySearch from "~/app/_components/profile/favorite-company-search"; import FavoriteRoleSearch from "~/app/_components/profile/favorite-role-search"; +import ProfileCardHeader from "~/app/_components/profile/profile-card-header"; import ProfileTabs from "~/app/_components/profile/profile-tabs"; import { ReviewCard } from "~/app/_components/reviews/review-card"; import { api } from "~/trpc/react"; -import { Button } from "@cooper/ui/button"; -import Link from "next/link"; export default function Profile() { const searchParams = useSearchParams(); @@ -88,8 +89,8 @@ export default function Profile() { } return ( -
-
+
+
-
-
-
Basic information
-
+
+
+
Basic information
+

{canReviewForTerm() ? (
-
+
On the job
-
+

-
Pay
-
+
Pay
+

-
+
Interview
-
+

-
+
Review and rate
-
+
@@ -329,7 +330,7 @@ export default function ReviewForm() { await form.handleSubmit(onSubmit)(); }} disabled={mutation.isPending} - className="bg-cooper-gray-550 hover:bg-cooper-gray-600 text-white rounded-lg px-8 py-3 text-lg font-semibold border-none" + className="bg-cooper-gray-550 hover:bg-cooper-gray-600 rounded-lg border-none px-8 py-3 text-lg font-semibold text-white" > {mutation.isPending ? "Submitting..." : "Submit review"} diff --git a/apps/web/src/app/_components/auth/login-button.tsx b/apps/web/src/app/_components/auth/login-button.tsx index aaf656b3..c97f8ab0 100644 --- a/apps/web/src/app/_components/auth/login-button.tsx +++ b/apps/web/src/app/_components/auth/login-button.tsx @@ -1,4 +1,5 @@ import Image from "next/image"; + import { signIn } from "@cooper/auth"; import { Button } from "@cooper/ui/button"; @@ -28,7 +29,7 @@ export default function LoginButton() { {/* Button for larger screens */}
) : (
)} - + -
+
@@ -61,7 +62,7 @@ export function CompanyPopup({
-
+
diff --git a/apps/web/src/app/_components/filters/dropdown-filter.tsx b/apps/web/src/app/_components/filters/dropdown-filter.tsx index 048861d6..8f7185e6 100644 --- a/apps/web/src/app/_components/filters/dropdown-filter.tsx +++ b/apps/web/src/app/_components/filters/dropdown-filter.tsx @@ -1,21 +1,20 @@ "use client"; import { useMemo, useState } from "react"; -import { ChevronDown, X } from "lucide-react"; -import { cn } from "@cooper/ui"; - import Image from "next/image"; +import { ChevronDown, X } from "lucide-react"; +import { cn } from "@cooper/ui"; +import { Button } from "@cooper/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuLabel, DropdownMenuTrigger, } from "@cooper/ui/dropdown-menu"; -import { Button } from "@cooper/ui/button"; -import FilterBody from "./filter-body"; import type { FilterOption, FilterVariant } from "./filter-body"; +import FilterBody from "./filter-body"; interface DropdownFilterProps { title: string; @@ -76,7 +75,7 @@ export default function DropdownFilter({ if (minRating === maxRating) return `${minRating}.0+ stars`; return ( -
+
{minRating}.0 - {maxRating}.0{" "} Star icon
@@ -98,10 +97,10 @@ export default function DropdownFilter({
diff --git a/apps/web/src/app/_components/filters/dropdown-filters-bar.tsx b/apps/web/src/app/_components/filters/dropdown-filters-bar.tsx index 6024438e..d43bbf66 100644 --- a/apps/web/src/app/_components/filters/dropdown-filters-bar.tsx +++ b/apps/web/src/app/_components/filters/dropdown-filters-bar.tsx @@ -1,13 +1,14 @@ "use client"; -import { useState, useEffect, useMemo } from "react"; -import { api } from "~/trpc/react"; +import { useEffect, useMemo, useState } from "react"; -import { industryOptions, jobTypeOptions } from "../onboarding/constants"; -import DropdownFilter from "./dropdown-filter"; import type { LocationType } from "@cooper/db/schema"; + import type { FilterState } from "./types"; +import { api } from "~/trpc/react"; import { prettyLocationName } from "~/utils/locationHelpers"; +import { industryOptions, jobTypeOptions } from "../onboarding/constants"; +import DropdownFilter from "./dropdown-filter"; interface DropdownFiltersBarProps { filters: FilterState; diff --git a/apps/web/src/app/_components/filters/filter-body.tsx b/apps/web/src/app/_components/filters/filter-body.tsx index d3176a03..9ea53649 100644 --- a/apps/web/src/app/_components/filters/filter-body.tsx +++ b/apps/web/src/app/_components/filters/filter-body.tsx @@ -4,8 +4,9 @@ import { useEffect, useMemo, useState } from "react"; import Image from "next/image"; import { cn } from "@cooper/ui"; -import { Checkbox } from "@cooper/ui/checkbox"; import Autocomplete from "@cooper/ui/autocomplete"; +import { Checkbox } from "@cooper/ui/checkbox"; + import { Input } from "../themed/onboarding/input"; export interface FilterOption { @@ -134,21 +135,21 @@ function FilterBodyRange({ }, [localMin, localMax]); return ( -
-
+
+
- +
-
-
-
+
+
$ @@ -158,7 +159,7 @@ function FilterBodyRange({ value={localMin} onChange={(e) => setLocalMin(e.target.value)} className={cn( - "h-9 border-cooper-gray-150 border-[1px] text-sm text-cooper-gray-400 pl-5", + "border-cooper-gray-150 h-9 border-[1px] pl-5 text-sm text-cooper-gray-400", rangeError ? "border-red-500" : "", )} onBlur={handleRangeApply} @@ -168,7 +169,7 @@ function FilterBodyRange({
-
+
$ @@ -178,7 +179,7 @@ function FilterBodyRange({ value={localMax} onChange={(e) => setLocalMax(e.target.value)} className={cn( - "h-9 border-cooper-gray-150 border-[1px] text-sm text-cooper-gray-400 pl-5", + "border-cooper-gray-150 h-9 border-[1px] pl-5 text-sm text-cooper-gray-400", rangeError ? "border-red-500" : "", )} onBlur={handleRangeApply} @@ -187,7 +188,7 @@ function FilterBodyRange({
- {rangeError &&

{rangeError}

} + {rangeError &&

{rangeError}

}
); } @@ -228,7 +229,7 @@ function FilterBodyRating({ }; return ( -
+
{[1, 2, 3, 4, 5].map((rating, index) => { const isInRange = rating >= minRating && @@ -240,7 +241,7 @@ function FilterBodyRating({ key={rating} onClick={() => handleRatingClick(rating)} className={cn( - "flex-1 py-[10px] px-5 flex items-center justify-center gap-1 transition-colors relative bg-cooper-gray-150", + "bg-cooper-gray-150 relative flex flex-1 items-center justify-center gap-1 px-5 py-[10px] transition-colors", isInRange ? "hover:bg-cooper-yellow-200 bg-cooper-yellow-400" : "hover:bg-cooper-yellow-200", @@ -335,7 +336,7 @@ function FilterBodyCheckbox({ /> )} -
+
{filteredOptions.map((option) => (
diff --git a/apps/web/src/app/_components/filters/sidebar-filter.tsx b/apps/web/src/app/_components/filters/sidebar-filter.tsx index ddb5a18b..9f4692ec 100644 --- a/apps/web/src/app/_components/filters/sidebar-filter.tsx +++ b/apps/web/src/app/_components/filters/sidebar-filter.tsx @@ -1,16 +1,21 @@ "use-client"; -import { useState, useEffect } from "react"; +import { useEffect, useState } from "react"; +import { ChevronRight } from "lucide-react"; + +import { cn } from "@cooper/ui"; +import { Button } from "@cooper/ui/button"; + +import type { FilterState } from "./types"; import { api } from "~/trpc/react"; -import { industryOptions } from "../onboarding/constants"; -import { jobTypeOptions, workModelOptions } from "../onboarding/constants"; import { prettyLocationName } from "~/utils/locationHelpers"; -import { Button } from "@cooper/ui/button"; +import { + industryOptions, + jobTypeOptions, + workModelOptions, +} from "../onboarding/constants"; import RoleTypeSelector from "./role-type-selector"; import SidebarSection from "./sidebar-section"; -import { ChevronRight } from "lucide-react"; -import { cn } from "@cooper/ui"; -import type { FilterState } from "./types"; interface SidebarFilterProps { isOpen: boolean; @@ -124,24 +129,24 @@ export default function SidebarFilter({ className={cn( "fixed inset-0 z-50 transition-opacity duration-200", isOpen - ? "opacity-100 bg-black/30 pointer-events-auto" - : "opacity-0 bg-black/0 pointer-events-none", + ? "pointer-events-auto bg-black/30 opacity-100" + : "pointer-events-none bg-black/0 opacity-0", )} onClick={onClose} >
e.stopPropagation()} > -
-
+
+
-
+
-
+
-
+
setSearchTerm(search)} /> -
+
-
+
-
+
{/* On the job subsection */}
- On the job + On the job
-
+
{/* Footer */} -
-
-
+
+
+