From ba5021f3f1da7fae29af46b34bf82acd3c0d4807 Mon Sep 17 00:00:00 2001 From: banushi-a Date: Sun, 31 Mar 2024 22:55:52 -0400 Subject: [PATCH 01/12] feat: refactor Roles Page --- prisma/schema.prisma | 8 +- src/app/companies/page.tsx | 30 ++++- src/app/page.tsx | 3 - src/app/roles/page.tsx | 53 +++++--- src/components/header-layout.tsx | 8 +- src/components/header.tsx | 57 ++++++--- src/components/review-card-preview.tsx | 86 +++++++++++++ src/components/review-card.tsx | 165 +++++++++++++++++++++++++ src/components/role-review-card.tsx | 8 +- src/components/search-bar.tsx | 66 +++++----- src/components/search-filter.tsx | 117 ++---------------- src/server/api/routers/company.ts | 4 +- src/utils/stringHelpers.ts | 19 +++ tailwind.config.ts | 2 + 14 files changed, 425 insertions(+), 201 deletions(-) create mode 100644 src/components/review-card-preview.tsx create mode 100644 src/components/review-card.tsx create mode 100644 src/utils/stringHelpers.ts diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b8e5a24..8ce732b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -126,14 +126,14 @@ model Review { otherBenefits String? createdAt DateTime @default(now()) - role Role? @relation(fields: [roleId], references: [id], onDelete: Cascade) - roleId String? + role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) + roleId String profile Profile? @relation(fields: [profileId], references: [id]) profileId String? - company Company? @relation(fields: [companyId], references: [id], onDelete: Cascade) - companyId String? + company Company @relation(fields: [companyId], references: [id], onDelete: Cascade) + companyId String } enum Industry { diff --git a/src/app/companies/page.tsx b/src/app/companies/page.tsx index f1ab0a6..97b47a3 100644 --- a/src/app/companies/page.tsx +++ b/src/app/companies/page.tsx @@ -1,12 +1,32 @@ import HeaderLayout from "~/components/header-layout"; +import { RoleReviewCard } from "~/components/role-review-card"; import SearchFilter from "~/components/search-filter"; +import { api } from "~/trpc/server"; +import { unstable_noStore as noStore } from "next/cache"; export default async function Companies() { + /** + * FIXME: This is a temporary fix, figure out how to get build command working without noStore(); + * @returns A promise containing the roles from the database + */ + async function getRoles() { + noStore(); + const roles = await api.role.list.query(); + return roles; + } + + const roles = await getRoles(); + return ( -
- - - -
+ + +
+ {roles.map((role) => { + return ( + + ); + })} +
+
); } diff --git a/src/app/page.tsx b/src/app/page.tsx index ccb25a2..b9d8ed9 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,9 +6,6 @@ export default async function Home() {
-

- Search your dream co-op role! -

diff --git a/src/app/roles/page.tsx b/src/app/roles/page.tsx index d8b8c2a..f2d18ad 100644 --- a/src/app/roles/page.tsx +++ b/src/app/roles/page.tsx @@ -1,30 +1,45 @@ +"use client"; + +import { useState } from "react"; import HeaderLayout from "~/components/header-layout"; -import { RoleReviewCard } from "~/components/role-review-card"; +import { ReviewCard } from "~/components/review-card"; +import { ReviewCardPreview } from "~/components/review-card-preview"; import SearchFilter from "~/components/search-filter"; -import { api } from "~/trpc/server"; -import { unstable_noStore as noStore } from "next/cache"; +import { cn } from "~/lib/utils"; +import { api } from "~/trpc/react"; -export default async function Roles() { - /** - * FIXME: This is a temporary fix, figure out how to get build command working without noStore(); - * @returns A promise containing the roles from the database - */ - async function getRoles() { - noStore(); - const roles = await api.role.list.query(); - return roles; - } +export default function Roles() { + const reviews = api.review.list.useQuery(); - const roles = await getRoles(); + const [selectedReviewIdx, setSelectedReviewIdx] = useState(0); return ( -
- {roles.map((role) => { - return ; - })} -
+ {/* TODO: Loading animations */} + {reviews.data && ( +
+
+ {reviews.data.map((review, idx) => { + return ( +
setSelectedReviewIdx(idx)}> + +
+ ); + })} +
+
+ {reviews.data.length > 0 && reviews.data[0] && ( + + )} +
+
+ )}
); } diff --git a/src/components/header-layout.tsx b/src/components/header-layout.tsx index 851c777..d72ccf8 100644 --- a/src/components/header-layout.tsx +++ b/src/components/header-layout.tsx @@ -1,3 +1,5 @@ +"use client"; +import { SessionProvider } from "next-auth/react"; import { ReactNode } from "react"; import Header from "~/components/header"; @@ -9,8 +11,10 @@ import Header from "~/components/header"; export default function HeaderLayout({ children }: { children: ReactNode }) { return (
-
-
+ +
+ +
{children}
diff --git a/src/components/header.tsx b/src/components/header.tsx index 32f70f5..4100691 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -1,26 +1,29 @@ +"use client"; + import Image from "next/image"; import Link from "next/link"; import LoginButton from "~/components/login-button"; import LogoutButton from "./logout-button"; import { cn } from "~/lib/utils"; -import { getServerSession } from "next-auth"; -import { Button } from "./ui/button"; import { altivoFont } from "~/styles/font"; +import { useSession } from "next-auth/react"; +import { usePathname } from "next/navigation"; /** * This is the header component. (Probably) should use header-layout instead * @returns The header component for the website */ -export default async function Header() { - const session = await getServerSession(); +export default function Header() { + const session = useSession(); + const pathname = usePathname(); const outerWidth = "w-40"; return (
{/* Logo + cooper */} -
+
Logo Picture
- -

+ cooper +

+ + {/* Centered Links */} +
+ +

- cooper -

- -
- {/* Centered Links */} -
- -

Jobs

+ {" "} + Jobs{" "} + -

Companies

+

+ Companies +

{/* New Review + Profile */} @@ -56,8 +74,7 @@ export default async function Header() { outerWidth, )} > - - {session ? : } + {session.data ? : }
); diff --git a/src/components/review-card-preview.tsx b/src/components/review-card-preview.tsx new file mode 100644 index 0000000..74db23b --- /dev/null +++ b/src/components/review-card-preview.tsx @@ -0,0 +1,86 @@ +"use client"; +/* eslint-disable @next/next/no-img-element */ +import { cn } from "~/lib/utils"; +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "~/components/ui/card"; +import { ReviewCardStars } from "./review-card-stars"; + +import { Review } from "@prisma/client"; +import { truncateText } from "~/utils/stringHelpers"; +import { api } from "~/trpc/react"; + +// todo: add this attribution in a footer somewhere +// Logos provided by Clearbit + +type ReviewCardPreviewProps = { + className?: string; + reviewObj: Review; +}; + +export function ReviewCardPreview({ + className, + reviewObj, +}: ReviewCardPreviewProps) { + // ===== COMPANY DATA ===== // + const company = api.company.getById.useQuery({ + id: reviewObj.companyId, + }); + + // ===== ROLE DATA ===== // + const role = api.role.getById.useQuery({ id: reviewObj.roleId }); + + // Truncate Review Text + const reviewText = truncateText(reviewObj.textReview, 80); + + return ( + +
+ +
+ {company.data ? ( + {`Logo + ) : ( +
+ )} +
+ + {role.data && role.data.title} + +

+ {company.data && company.data.name} +

+
+
+
+ +
+

+ {reviewObj.reviewHeadline} +

+ +
+

{reviewText}

+
+
+ + see more... + +
+ ); +} diff --git a/src/components/review-card.tsx b/src/components/review-card.tsx new file mode 100644 index 0000000..47b0b3a --- /dev/null +++ b/src/components/review-card.tsx @@ -0,0 +1,165 @@ +"use client"; +/* eslint-disable @next/next/no-img-element */ +import { cn } from "~/lib/utils"; +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "~/components/ui/card"; +import { ReviewCardStars } from "./review-card-stars"; + +import { Review } from "@prisma/client"; +import { api } from "~/trpc/react"; +import { prettyWorkEnviornment, truncateText } from "~/utils/stringHelpers"; + +// todo: add this attribution in a footer somewhere +// Logos provided by Clearbit + +const InterviewDifficulty = [ + { des: "Very Easy", color: "text-[#4bc92e]" }, + { des: "Easy", color: "text-[#09b52b]" }, + { des: "Neither Easy Nor Difficult", color: "text-cooper-blue-400" }, + { des: "Difficult", color: "text-[#f27c38]" }, + { des: "Very Difficult", color: "text-[#f52536]" }, +]; + +type ReviewCardProps = { + className?: string; + reviewObj: Review; +}; + +export function ReviewCard({ className, reviewObj }: ReviewCardProps) { + // ===== COMPANY DATA ===== // + const company = api.company.getById.useQuery({ id: reviewObj.companyId }); + + // ===== ROLE DATA ===== // + const role = api.role.getById.useQuery({ id: reviewObj.roleId }); + + // Truncate Review Text + const reviewText = truncateText(reviewObj.textReview, 80); + + // Benefits + const benefits: string[] = []; + if (reviewObj.pto) benefits.push("Paid Time Off"); + if (reviewObj.federalHolidays) benefits.push("Federal Holidays Off"); + if (reviewObj.freeLunch) benefits.push("Free Lunch"); + if (reviewObj.freeTransport) benefits.push("Free Transporation"); + if (reviewObj.freeMerch) benefits.push("Free Merch"); + + return ( + +
+ +
+ {company.data ? ( + {`Logo + ) : ( +
+ )} +
+ + {role.data && role.data.title} + +

+ {company.data && company.data.name} +

+ {company.data && role.data && ( + + )} +
+
+
+ +

+ {reviewObj.reviewHeadline} +

+

{reviewText}

+
+ +

Ratings

+
+
+

Company Culture

+ +
+
+

Supervisor

+ +
+
+

Interview Rating

+ +
+
+
+ +

+ Interview Difficulty +

+

+ {InterviewDifficulty[reviewObj.interviewDifficulty - 1]?.des} +

+

{reviewObj.interviewReview}

+
+ +

Role Details

+
+
+

Location

+

{reviewObj.location}

+
+
+

Hourly Pay

+

${reviewObj.hourlyPay?.toString()}

+
+
+

Work Model

+

{prettyWorkEnviornment(reviewObj.workEnvironment)}

+
+
+

Drug Test?

+

{reviewObj.drugTest ? "Yes" : "No"}

+
+
+

Overtime Common?

+

{reviewObj.overtimeNormal ? "Yes" : "No"}

+
+
+
+ +

Benefits

+
+ {benefits.map((benefit) => { + return ( +
+ {benefit} +
+ ); + })} +
+

{reviewObj.otherBenefits}

+
+
+
+ ); +} diff --git a/src/components/role-review-card.tsx b/src/components/role-review-card.tsx index cfe7616..d9392f5 100644 --- a/src/components/role-review-card.tsx +++ b/src/components/role-review-card.tsx @@ -15,6 +15,7 @@ import { averageStarRating, mostCommonWorkEnviornment, } from "~/utils/reviewsAggregationHelpers"; +import { truncateText } from "~/utils/stringHelpers"; // todo: add this attribution in a footer somewhere // Logos provided by Clearbit @@ -31,10 +32,7 @@ export async function RoleReviewCard({ }: RoleReviewCardProps) { // ===== COMPANY DATA ===== // const company = await api.company.getById.query({ id: roleObj.companyId }); - const roleDescription = - roleObj.description && roleObj.description.length >= 210 - ? cn(roleObj.description.slice(0, 210), "...") - : roleObj.description; + const roleDescription = truncateText(roleObj.description || "", 150); const positionDate: string = formatDate(company.createdAt); // ===== REVIEW DATA ===== // @@ -48,7 +46,7 @@ export async function RoleReviewCard({ return ( ( - - -
- {/* SEARCH TEXT INPUT */} - - - +
+ ( + + +
+ {/* SEARCH TEXT INPUT */} + + + - -
-
-
- )} - /> + +
+ + + )} + /> +
); } diff --git a/src/components/search-filter.tsx b/src/components/search-filter.tsx index f259b5b..6451033 100644 --- a/src/components/search-filter.tsx +++ b/src/components/search-filter.tsx @@ -1,49 +1,16 @@ "use client"; -import Image from "next/image"; - import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import { Form, FormControl, FormField, FormItem } from "~/components/ui/form"; -import { Industry, WorkEnvironment, WorkTerm } from "@prisma/client"; -import { cn } from "~/lib/utils"; -import { buttonVariants } from "./ui/button"; +import { Form } from "~/components/ui/form"; import { SearchBar } from "./search-bar"; const formSchema = z.object({ searchText: z.string(), - searchIndustry: z.nativeEnum(Industry).optional(), - searchWorkTerm: z.nativeEnum(WorkTerm).optional(), - searchWorkModel: z.nativeEnum(WorkEnvironment).optional(), }); -/** - * Used to abstract the dropdowns in the search bar. - */ -const searchDropdown: { - name: "searchIndustry" | "searchWorkTerm" | "searchWorkModel"; - title: string; - enumObj: object; -}[] = [ - { - name: "searchIndustry", - title: "Industry", - enumObj: Industry, - }, - { - name: "searchWorkTerm", - title: "Work Term", - enumObj: WorkTerm, - }, - { - name: "searchWorkModel", - title: "Work Model", - enumObj: WorkEnvironment, - }, -]; - export type SearchFilterFormType = typeof formSchema; export default function SearchFilter() { @@ -51,9 +18,6 @@ export default function SearchFilter() { resolver: zodResolver(formSchema), defaultValues: { searchText: "", - searchIndustry: undefined, - searchWorkTerm: undefined, - searchWorkModel: undefined, }, }); @@ -62,77 +26,12 @@ export default function SearchFilter() { } return ( -
-
- -
- -
-
- {searchDropdown.map(({ name, title, enumObj }) => { - return ( - ( - -
- - - - {/* Triangle Down Icon */} - - - -
-
- )} - /> - ); - })} -
-
- -
+
+ +
+ +
+
+ ); } diff --git a/src/server/api/routers/company.ts b/src/server/api/routers/company.ts index 225348b..89f73ce 100644 --- a/src/server/api/routers/company.ts +++ b/src/server/api/routers/company.ts @@ -8,8 +8,8 @@ import { getByIdSchema, getByNameSchema } from "~/schema/misc"; import { TRPCError } from "@trpc/server"; export const companyRouter = createTRPCRouter({ - list: publicProcedure.query(({ ctx }) => { - return ctx.db.company.findMany(); + list: publicProcedure.query(async ({ ctx }) => { + return await ctx.db.company.findMany(); }), getByName: publicProcedure .input(getByNameSchema) diff --git a/src/utils/stringHelpers.ts b/src/utils/stringHelpers.ts new file mode 100644 index 0000000..2c59546 --- /dev/null +++ b/src/utils/stringHelpers.ts @@ -0,0 +1,19 @@ +import { WorkEnvironment } from "@prisma/client"; +import { cn } from "~/lib/utils"; + +export function truncateText(text: string, length: number): string { + return text && text.length >= length + ? cn(text.slice(0, length), "...") + : text; +} + +export function prettyWorkEnviornment(workEnviornment: WorkEnvironment) { + switch (workEnviornment) { + case "HYBRID": + return "Hybrid"; + case "INPERSON": + return "In-person"; + case "REMOTE": + return "Remote"; + } +} diff --git a/tailwind.config.ts b/tailwind.config.ts index 50edba4..881a891 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -27,6 +27,8 @@ const config = { "cooper-blue-400": "#5E8BAB", "cooper-blue-200": "#DEEAF8", "cooper-yellow-500": "#FFA400", + "cooper-pink-500": "#EA8FBA", + "cooper-green-500": "#619518", border: "hsl(var(--border))", input: "hsl(var(--input))", ring: "hsl(var(--ring))", From 6ff4c7070be3caa3b13d85aef599ff4be3ea4a83 Mon Sep 17 00:00:00 2001 From: banushi-a Date: Thu, 11 Apr 2024 18:01:17 -0400 Subject: [PATCH 02/12] feat: install combobox things --- package.json | 3 + pnpm-lock.yaml | 401 +++++++++++++++++++++++++++ src/components/header.tsx | 7 +- src/components/new-review-dialog.tsx | 211 ++++++++++++++ src/components/ui/command.tsx | 155 +++++++++++ src/components/ui/dialog.tsx | 122 ++++++++ src/components/ui/popover.tsx | 31 +++ 7 files changed, 929 insertions(+), 1 deletion(-) create mode 100644 src/components/new-review-dialog.tsx create mode 100644 src/components/ui/command.tsx create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/popover.tsx diff --git a/package.json b/package.json index f15ff73..0fb5b66 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,10 @@ "@next-auth/prisma-adapter": "^1.0.7", "@prisma/client": "^5.9.1", "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5", @@ -33,6 +35,7 @@ "@trpc/server": "^10.45.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", + "cmdk": "^1.0.0", "dayjs": "^1.11.10", "lucide-react": "^0.323.0", "next": "^14.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed6479d..883d81d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,12 +17,18 @@ dependencies: "@radix-ui/react-checkbox": specifier: ^1.0.4 version: 1.0.4(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + "@radix-ui/react-dialog": + specifier: ^1.0.5 + version: 1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) "@radix-ui/react-icons": specifier: ^1.3.0 version: 1.3.0(react@18.2.0) "@radix-ui/react-label": specifier: ^2.0.2 version: 2.0.2(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + "@radix-ui/react-popover": + specifier: ^1.0.7 + version: 1.0.7(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) "@radix-ui/react-radio-group": specifier: ^1.1.3 version: 1.1.3(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) @@ -56,6 +62,9 @@ dependencies: clsx: specifier: ^2.1.0 version: 2.1.0 + cmdk: + specifier: ^1.0.0 + version: 1.0.0(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) dayjs: specifier: ^1.11.10 version: 1.11.10 @@ -485,6 +494,46 @@ packages: engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } dev: true + /@floating-ui/core@1.6.0: + resolution: + { + integrity: sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==, + } + dependencies: + "@floating-ui/utils": 0.2.1 + dev: false + + /@floating-ui/dom@1.6.3: + resolution: + { + integrity: sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==, + } + dependencies: + "@floating-ui/core": 1.6.0 + "@floating-ui/utils": 0.2.1 + dev: false + + /@floating-ui/react-dom@2.0.8(react-dom@18.2.0)(react@18.2.0): + resolution: + { + integrity: sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==, + } + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + dependencies: + "@floating-ui/dom": 1.6.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@floating-ui/utils@0.2.1: + resolution: + { + integrity: sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==, + } + dev: false + /@hookform/resolvers@3.3.4(react-hook-form@7.50.1): resolution: { @@ -826,6 +875,30 @@ packages: "@babel/runtime": 7.23.9 dev: false + /@radix-ui/react-arrow@1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0): + resolution: + { + integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + dependencies: + "@babel/runtime": 7.23.9 + "@radix-ui/react-primitive": 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + "@types/react": 18.2.55 + "@types/react-dom": 18.2.19 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-checkbox@1.0.4(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0): resolution: { @@ -918,6 +991,43 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-dialog@1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0): + resolution: + { + integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + dependencies: + "@babel/runtime": 7.23.9 + "@radix-ui/primitive": 1.0.1 + "@radix-ui/react-compose-refs": 1.0.1(@types/react@18.2.55)(react@18.2.0) + "@radix-ui/react-context": 1.0.1(@types/react@18.2.55)(react@18.2.0) + "@radix-ui/react-dismissable-layer": 1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + "@radix-ui/react-focus-guards": 1.0.1(@types/react@18.2.55)(react@18.2.0) + "@radix-ui/react-focus-scope": 1.0.4(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + "@radix-ui/react-id": 1.0.1(@types/react@18.2.55)(react@18.2.0) + "@radix-ui/react-portal": 1.0.4(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + "@radix-ui/react-presence": 1.0.1(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + "@radix-ui/react-primitive": 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + "@radix-ui/react-slot": 1.0.2(@types/react@18.2.55)(react@18.2.0) + "@radix-ui/react-use-controllable-state": 1.0.1(@types/react@18.2.55)(react@18.2.0) + "@types/react": 18.2.55 + "@types/react-dom": 18.2.19 + aria-hidden: 1.2.4 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-remove-scroll: 2.5.5(@types/react@18.2.55)(react@18.2.0) + dev: false + /@radix-ui/react-direction@1.0.1(@types/react@18.2.55)(react@18.2.0): resolution: { @@ -963,6 +1073,49 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.55)(react@18.2.0): + resolution: + { + integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + dependencies: + "@babel/runtime": 7.23.9 + "@types/react": 18.2.55 + react: 18.2.0 + dev: false + + /@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0): + resolution: + { + integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + dependencies: + "@babel/runtime": 7.23.9 + "@radix-ui/react-compose-refs": 1.0.1(@types/react@18.2.55)(react@18.2.0) + "@radix-ui/react-primitive": 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + "@radix-ui/react-use-callback-ref": 1.0.1(@types/react@18.2.55)(react@18.2.0) + "@types/react": 18.2.55 + "@types/react-dom": 18.2.19 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-icons@1.3.0(react@18.2.0): resolution: { @@ -1016,6 +1169,77 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-popover@1.0.7(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0): + resolution: + { + integrity: sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + dependencies: + "@babel/runtime": 7.23.9 + "@radix-ui/primitive": 1.0.1 + "@radix-ui/react-compose-refs": 1.0.1(@types/react@18.2.55)(react@18.2.0) + "@radix-ui/react-context": 1.0.1(@types/react@18.2.55)(react@18.2.0) + "@radix-ui/react-dismissable-layer": 1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + "@radix-ui/react-focus-guards": 1.0.1(@types/react@18.2.55)(react@18.2.0) + "@radix-ui/react-focus-scope": 1.0.4(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + "@radix-ui/react-id": 1.0.1(@types/react@18.2.55)(react@18.2.0) + "@radix-ui/react-popper": 1.1.3(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + "@radix-ui/react-portal": 1.0.4(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + "@radix-ui/react-presence": 1.0.1(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + "@radix-ui/react-primitive": 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + "@radix-ui/react-slot": 1.0.2(@types/react@18.2.55)(react@18.2.0) + "@radix-ui/react-use-controllable-state": 1.0.1(@types/react@18.2.55)(react@18.2.0) + "@types/react": 18.2.55 + "@types/react-dom": 18.2.19 + aria-hidden: 1.2.4 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-remove-scroll: 2.5.5(@types/react@18.2.55)(react@18.2.0) + dev: false + + /@radix-ui/react-popper@1.1.3(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0): + resolution: + { + integrity: sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==, + } + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + dependencies: + "@babel/runtime": 7.23.9 + "@floating-ui/react-dom": 2.0.8(react-dom@18.2.0)(react@18.2.0) + "@radix-ui/react-arrow": 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + "@radix-ui/react-compose-refs": 1.0.1(@types/react@18.2.55)(react@18.2.0) + "@radix-ui/react-context": 1.0.1(@types/react@18.2.55)(react@18.2.0) + "@radix-ui/react-primitive": 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + "@radix-ui/react-use-callback-ref": 1.0.1(@types/react@18.2.55)(react@18.2.0) + "@radix-ui/react-use-layout-effect": 1.0.1(@types/react@18.2.55)(react@18.2.0) + "@radix-ui/react-use-rect": 1.0.1(@types/react@18.2.55)(react@18.2.0) + "@radix-ui/react-use-size": 1.0.1(@types/react@18.2.55)(react@18.2.0) + "@radix-ui/rect": 1.0.1 + "@types/react": 18.2.55 + "@types/react-dom": 18.2.19 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-portal@1.0.4(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0): resolution: { @@ -1294,6 +1518,24 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-use-rect@1.0.1(@types/react@18.2.55)(react@18.2.0): + resolution: + { + integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==, + } + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + "@types/react": + optional: true + dependencies: + "@babel/runtime": 7.23.9 + "@radix-ui/rect": 1.0.1 + "@types/react": 18.2.55 + react: 18.2.0 + dev: false + /@radix-ui/react-use-size@1.0.1(@types/react@18.2.55)(react@18.2.0): resolution: { @@ -1336,6 +1578,15 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/rect@1.0.1: + resolution: + { + integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==, + } + dependencies: + "@babel/runtime": 7.23.9 + dev: false + /@rushstack/eslint-patch@1.7.2: resolution: { @@ -1871,6 +2122,16 @@ packages: } dev: true + /aria-hidden@1.2.4: + resolution: + { + integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==, + } + engines: { node: ">=10" } + dependencies: + tslib: 2.6.2 + dev: false + /aria-query@5.3.0: resolution: { @@ -2389,6 +2650,24 @@ packages: engines: { node: ">=6" } dev: false + /cmdk@1.0.0(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0): + resolution: + { + integrity: sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==, + } + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + dependencies: + "@radix-ui/react-dialog": 1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + "@radix-ui/react-primitive": 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.55)(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + transitivePeerDependencies: + - "@types/react" + - "@types/react-dom" + dev: false + /color-convert@1.9.3: resolution: { @@ -2767,6 +3046,13 @@ packages: engines: { node: ">=8" } dev: true + /detect-node-es@1.1.0: + resolution: + { + integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==, + } + dev: false + /didyoumean@1.2.2: resolution: { @@ -3669,6 +3955,14 @@ packages: hasown: 2.0.1 dev: true + /get-nonce@1.0.1: + resolution: + { + integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==, + } + engines: { node: ">=6" } + dev: false + /get-stream@6.0.1: resolution: { @@ -4099,6 +4393,15 @@ packages: side-channel: 1.0.5 dev: true + /invariant@2.2.4: + resolution: + { + integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==, + } + dependencies: + loose-envify: 1.4.0 + dev: false + /is-array-buffer@3.0.4: resolution: { @@ -5859,6 +6162,67 @@ packages: } dev: true + /react-remove-scroll-bar@2.3.6(@types/react@18.2.55)(react@18.2.0): + resolution: + { + integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==, + } + engines: { node: ">=10" } + peerDependencies: + "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + dependencies: + "@types/react": 18.2.55 + react: 18.2.0 + react-style-singleton: 2.2.1(@types/react@18.2.55)(react@18.2.0) + tslib: 2.6.2 + dev: false + + /react-remove-scroll@2.5.5(@types/react@18.2.55)(react@18.2.0): + resolution: + { + integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==, + } + engines: { node: ">=10" } + peerDependencies: + "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + dependencies: + "@types/react": 18.2.55 + react: 18.2.0 + react-remove-scroll-bar: 2.3.6(@types/react@18.2.55)(react@18.2.0) + react-style-singleton: 2.2.1(@types/react@18.2.55)(react@18.2.0) + tslib: 2.6.2 + use-callback-ref: 1.3.2(@types/react@18.2.55)(react@18.2.0) + use-sidecar: 1.1.2(@types/react@18.2.55)(react@18.2.0) + dev: false + + /react-style-singleton@2.2.1(@types/react@18.2.55)(react@18.2.0): + resolution: + { + integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==, + } + engines: { node: ">=10" } + peerDependencies: + "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + dependencies: + "@types/react": 18.2.55 + get-nonce: 1.0.1 + invariant: 2.2.4 + react: 18.2.0 + tslib: 2.6.2 + dev: false + /react@18.2.0: resolution: { @@ -6965,6 +7329,43 @@ packages: punycode: 2.3.1 dev: true + /use-callback-ref@1.3.2(@types/react@18.2.55)(react@18.2.0): + resolution: + { + integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==, + } + engines: { node: ">=10" } + peerDependencies: + "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + dependencies: + "@types/react": 18.2.55 + react: 18.2.0 + tslib: 2.6.2 + dev: false + + /use-sidecar@1.1.2(@types/react@18.2.55)(react@18.2.0): + resolution: + { + integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==, + } + engines: { node: ">=10" } + peerDependencies: + "@types/react": ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + dependencies: + "@types/react": 18.2.55 + detect-node-es: 1.1.0 + react: 18.2.0 + tslib: 2.6.2 + dev: false + /use-sync-external-store@1.2.0(react@18.2.0): resolution: { diff --git a/src/components/header.tsx b/src/components/header.tsx index 4100691..147aa61 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -5,10 +5,13 @@ import Link from "next/link"; import LoginButton from "~/components/login-button"; import LogoutButton from "./logout-button"; +import { Button } from "./ui/button"; +import { NewReviewDialog } from "./new-review-dialog"; + import { cn } from "~/lib/utils"; import { altivoFont } from "~/styles/font"; import { useSession } from "next-auth/react"; -import { usePathname } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; /** * This is the header component. (Probably) should use header-layout instead @@ -74,6 +77,8 @@ export default function Header() { outerWidth, )} > + {/* TODO: only show this if the user is below the max number of reviews allowed */} + {session.data ? : }
diff --git a/src/components/new-review-dialog.tsx b/src/components/new-review-dialog.tsx new file mode 100644 index 0000000..a9ebe59 --- /dev/null +++ b/src/components/new-review-dialog.tsx @@ -0,0 +1,211 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "./ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "./ui/dialog"; +import { useRouter } from "next/navigation"; +import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; +import { Check, ChevronsUpDown } from "lucide-react"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "./ui/command"; +import { cn } from "~/lib/utils"; +import { api } from "~/trpc/react"; + +// God forgive me for whatever this component turned out to be + +export function NewReviewDialog() { + const router = useRouter(); + // State for the company combo box + const [companyOpen, setCompanyOpen] = useState(false); + const [companyLabel, setCompanyLabel] = useState(""); + + const companies = api.company.list.useQuery(); + const companyValuesAndLabels = companies.data + ? companies.data.map((company) => { + return { + value: company.id, + label: company.name, + }; + }) + : []; + + // State for the role combo box + const [roleOpen, setRoleOpen] = useState(false); + const [roleLabel, setRoleLabel] = useState(""); + + const roles = api.role.list.useQuery(); + const [roleValuesAndLabels, setRoleValuesAndLabels] = useState< + { + value: string; + label: string; + }[] + >([]); + + function updateAvailableRoles(newCompanyLabel: string) { + const companyId = + companyValuesAndLabels.find( + (company) => company.label === newCompanyLabel, + )?.value || ""; + const filteredRoleValuesAndLabels = roles.data + ? roles.data + .filter((role) => role.companyId === companyId) + .map((role) => { + return { + value: role.id, + label: role.title, + }; + }) + : []; + setRoleValuesAndLabels(filteredRoleValuesAndLabels); + } + + return ( + + + + + + + Create New Review + + Pick which company and role to create a review for, click next when + done. + + +
{ + event.preventDefault(); + const roleId = + roleValuesAndLabels.find((role) => role.label === roleLabel) + ?.value || ""; + router.push("/review?id=" + roleId); + }} + > +
+ {/* COMPANY COMBOBOX */} + + + + + + + + No company found. + + + {companyValuesAndLabels.map((company) => ( + { + setCompanyLabel( + currentValue === companyLabel ? "" : currentValue, + ); + updateAvailableRoles( + currentValue === companyLabel ? "" : currentValue, + ); + setRoleLabel(""); + console.log(roles); + setCompanyOpen(false); + }} + > + + {company.label} + + ))} + + + + + + {/* ROLES COMBOBOX */} + + + + + + + + No role found. + + + {roleValuesAndLabels.map((role) => ( + { + setRoleLabel( + currentValue === roleLabel ? "" : currentValue, + ); + // Close the combobox + setRoleOpen(false); + }} + > + + {role.label} + + ))} + + + + + +
+ + + +
+
+
+ ); +} diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx new file mode 100644 index 0000000..562f5bf --- /dev/null +++ b/src/components/ui/command.tsx @@ -0,0 +1,155 @@ +"use client"; + +import * as React from "react"; +import { type DialogProps } from "@radix-ui/react-dialog"; +import { Command as CommandPrimitive } from "cmdk"; +import { Search } from "lucide-react"; + +import { cn } from "~/lib/utils"; +import { Dialog, DialogContent } from "~/components/ui/dialog"; + +const Command = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Command.displayName = CommandPrimitive.displayName; + +interface CommandDialogProps extends DialogProps {} + +const CommandDialog = ({ children, ...props }: CommandDialogProps) => { + return ( + + + + {children} + + + + ); +}; + +const CommandInput = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( +
+ + +
+)); + +CommandInput.displayName = CommandPrimitive.Input.displayName; + +const CommandList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandList.displayName = CommandPrimitive.List.displayName; + +const CommandEmpty = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>((props, ref) => ( + +)); + +CommandEmpty.displayName = CommandPrimitive.Empty.displayName; + +const CommandGroup = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandGroup.displayName = CommandPrimitive.Group.displayName; + +const CommandSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +CommandSeparator.displayName = CommandPrimitive.Separator.displayName; + +const CommandItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); + +CommandItem.displayName = CommandPrimitive.Item.displayName; + +const CommandShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +CommandShortcut.displayName = "CommandShortcut"; + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +}; diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..d2415b2 --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,122 @@ +"use client"; + +import * as React from "react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; + +import { cn } from "~/lib/utils"; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}; diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx new file mode 100644 index 0000000..1a477d6 --- /dev/null +++ b/src/components/ui/popover.tsx @@ -0,0 +1,31 @@ +"use client"; + +import * as React from "react"; +import * as PopoverPrimitive from "@radix-ui/react-popover"; + +import { cn } from "~/lib/utils"; + +const Popover = PopoverPrimitive.Root; + +const PopoverTrigger = PopoverPrimitive.Trigger; + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; + +export { Popover, PopoverTrigger, PopoverContent }; From 96a37c94f4e8e7b78369555dbe8a0584dabebe87 Mon Sep 17 00:00:00 2001 From: banushi-a Date: Thu, 11 Apr 2024 20:38:39 -0400 Subject: [PATCH 03/12] fix: fix lint --- src/components/header.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/header.tsx b/src/components/header.tsx index 147aa61..c3178d5 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -84,6 +84,3 @@ export default function Header() { ); } -function useServerSession() { - throw new Error("Function not implemented."); -} From 537aef3235695271a4607062ec16e4430673cea2 Mon Sep 17 00:00:00 2001 From: banushi-a Date: Sat, 13 Apr 2024 22:15:19 -0400 Subject: [PATCH 04/12] refactor: change layout so that header isn't on every page --- src/app/{ => (dashboard)}/companies/page.tsx | 4 ++-- src/app/(dashboard)/layout.tsx | 10 ++++++++++ src/app/{ => (dashboard)}/roles/page.tsx | 4 ++-- 3 files changed, 14 insertions(+), 4 deletions(-) rename src/app/{ => (dashboard)}/companies/page.tsx (95%) create mode 100644 src/app/(dashboard)/layout.tsx rename src/app/{ => (dashboard)}/roles/page.tsx (97%) diff --git a/src/app/companies/page.tsx b/src/app/(dashboard)/companies/page.tsx similarity index 95% rename from src/app/companies/page.tsx rename to src/app/(dashboard)/companies/page.tsx index 97b47a3..a0f5c13 100644 --- a/src/app/companies/page.tsx +++ b/src/app/(dashboard)/companies/page.tsx @@ -18,7 +18,7 @@ export default async function Companies() { const roles = await getRoles(); return ( - + <>
{roles.map((role) => { @@ -27,6 +27,6 @@ export default async function Companies() { ); })}
-
+ ); } diff --git a/src/app/(dashboard)/layout.tsx b/src/app/(dashboard)/layout.tsx new file mode 100644 index 0000000..df3578c --- /dev/null +++ b/src/app/(dashboard)/layout.tsx @@ -0,0 +1,10 @@ +import "~/styles/globals.css"; +import HeaderLayout from "~/components/header-layout"; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return {children}; +} diff --git a/src/app/roles/page.tsx b/src/app/(dashboard)/roles/page.tsx similarity index 97% rename from src/app/roles/page.tsx rename to src/app/(dashboard)/roles/page.tsx index f2d18ad..a04a403 100644 --- a/src/app/roles/page.tsx +++ b/src/app/(dashboard)/roles/page.tsx @@ -14,7 +14,7 @@ export default function Roles() { const [selectedReviewIdx, setSelectedReviewIdx] = useState(0); return ( - + <> {/* TODO: Loading animations */} {reviews.data && ( @@ -40,6 +40,6 @@ export default function Roles() {
)} - + ); } From 0c1a404248f6a346c102b7c40757106a4ef1007c Mon Sep 17 00:00:00 2001 From: banushi-a Date: Sat, 13 Apr 2024 22:17:39 -0400 Subject: [PATCH 05/12] chore: remove unused imports --- src/app/(dashboard)/companies/page.tsx | 1 - src/app/(dashboard)/roles/page.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/src/app/(dashboard)/companies/page.tsx b/src/app/(dashboard)/companies/page.tsx index a0f5c13..181a6ac 100644 --- a/src/app/(dashboard)/companies/page.tsx +++ b/src/app/(dashboard)/companies/page.tsx @@ -1,4 +1,3 @@ -import HeaderLayout from "~/components/header-layout"; import { RoleReviewCard } from "~/components/role-review-card"; import SearchFilter from "~/components/search-filter"; import { api } from "~/trpc/server"; diff --git a/src/app/(dashboard)/roles/page.tsx b/src/app/(dashboard)/roles/page.tsx index a04a403..802cfca 100644 --- a/src/app/(dashboard)/roles/page.tsx +++ b/src/app/(dashboard)/roles/page.tsx @@ -1,7 +1,6 @@ "use client"; import { useState } from "react"; -import HeaderLayout from "~/components/header-layout"; import { ReviewCard } from "~/components/review-card"; import { ReviewCardPreview } from "~/components/review-card-preview"; import SearchFilter from "~/components/search-filter"; From eb278a42690b816ecb9a53601b4d71c2feb4ffc4 Mon Sep 17 00:00:00 2001 From: banushi-a Date: Sat, 13 Apr 2024 23:08:24 -0400 Subject: [PATCH 06/12] style: review Button Styles --- src/app/{ => (dashboard)}/page.tsx | 0 src/components/new-review-dialog.tsx | 4 +++- src/components/ui/command.tsx | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) rename src/app/{ => (dashboard)}/page.tsx (100%) diff --git a/src/app/page.tsx b/src/app/(dashboard)/page.tsx similarity index 100% rename from src/app/page.tsx rename to src/app/(dashboard)/page.tsx diff --git a/src/components/new-review-dialog.tsx b/src/components/new-review-dialog.tsx index a9ebe59..d5d1a77 100644 --- a/src/components/new-review-dialog.tsx +++ b/src/components/new-review-dialog.tsx @@ -76,7 +76,9 @@ export function NewReviewDialog() { return ( - + diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx index 562f5bf..4e82c87 100644 --- a/src/components/ui/command.tsx +++ b/src/components/ui/command.tsx @@ -117,7 +117,7 @@ const CommandItem = React.forwardRef< Date: Sat, 13 Apr 2024 23:54:23 -0400 Subject: [PATCH 07/12] refactor: refactored how combo-box worked --- src/components/combo-box.tsx | 62 ++++++++++ src/components/new-review-dialog.tsx | 173 ++++++++++----------------- 2 files changed, 127 insertions(+), 108 deletions(-) create mode 100644 src/components/combo-box.tsx diff --git a/src/components/combo-box.tsx b/src/components/combo-box.tsx new file mode 100644 index 0000000..648c8a3 --- /dev/null +++ b/src/components/combo-box.tsx @@ -0,0 +1,62 @@ +import { ChevronsUpDown } from "lucide-react"; +import { Button } from "./ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "./ui/command"; +import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; +import { SetStateAction } from "react"; + +export type ComboBoxOption = { + value: T; + label: string; +}; + +type ComboBoxProps = { + defaultLabel: string; + searchPlaceholder: string; + searchEmpty: string; + valuesAndLabels: ComboBoxOption[]; + optionToNode: (option: ComboBoxOption) => React.ReactNode; + isOpen: boolean; + setIsOpen: React.Dispatch>; +}; + +export default function ComboBox({ + defaultLabel, + searchPlaceholder, + searchEmpty, + valuesAndLabels, + optionToNode, + isOpen, + setIsOpen, +}: ComboBoxProps) { + return ( + + + + + + + + {searchEmpty} + + {valuesAndLabels.map(optionToNode)} + + + + + ); +} diff --git a/src/components/new-review-dialog.tsx b/src/components/new-review-dialog.tsx index d5d1a77..28aa9f1 100644 --- a/src/components/new-review-dialog.tsx +++ b/src/components/new-review-dialog.tsx @@ -12,18 +12,11 @@ import { DialogTrigger, } from "./ui/dialog"; import { useRouter } from "next/navigation"; -import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; -import { Check, ChevronsUpDown } from "lucide-react"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from "./ui/command"; +import { Check } from "lucide-react"; +import { CommandItem } from "./ui/command"; import { cn } from "~/lib/utils"; import { api } from "~/trpc/react"; +import ComboBox, { ComboBoxOption } from "./combo-box"; // God forgive me for whatever this component turned out to be @@ -49,10 +42,7 @@ export function NewReviewDialog() { const roles = api.role.list.useQuery(); const [roleValuesAndLabels, setRoleValuesAndLabels] = useState< - { - value: string; - label: string; - }[] + ComboBoxOption[] >([]); function updateAvailableRoles(newCompanyLabel: string) { @@ -99,103 +89,70 @@ export function NewReviewDialog() { >
{/* COMPANY COMBOBOX */} - - - - - - - - No company found. - - - {companyValuesAndLabels.map((company) => ( - { - setCompanyLabel( - currentValue === companyLabel ? "" : currentValue, - ); - updateAvailableRoles( - currentValue === companyLabel ? "" : currentValue, - ); - setRoleLabel(""); - console.log(roles); - setCompanyOpen(false); - }} - > - - {company.label} - - ))} - - - - - + + {company.label} + + )} + isOpen={companyOpen} + setIsOpen={setCompanyOpen} + /> {/* ROLES COMBOBOX */} - - - - - - - - No role found. - - - {roleValuesAndLabels.map((role) => ( - { - setRoleLabel( - currentValue === roleLabel ? "" : currentValue, - ); - // Close the combobox - setRoleOpen(false); - }} - > - - {role.label} - - ))} - - - - - + + {role.label} + + )} + isOpen={roleOpen} + setIsOpen={setRoleOpen} + />
- + Create New Review @@ -94,34 +99,19 @@ export function NewReviewDialog() { searchPlaceholder="Search company..." searchEmpty="No company found." valuesAndLabels={companyValuesAndLabels} - optionToNode={(company: ComboBoxOption) => ( - { - setCompanyLabel( - currentValue === companyLabel ? "" : currentValue, - ); - updateAvailableRoles( - currentValue === companyLabel ? "" : currentValue, - ); - setRoleLabel(""); - setCompanyOpen(false); - }} - > - - {company.label} - - )} isOpen={companyOpen} setIsOpen={setCompanyOpen} + currLabel={companyLabel} + onSelect={(currentValue) => { + setCompanyLabel( + currentValue === companyLabel ? "" : currentValue, + ); + updateAvailableRoles( + currentValue === companyLabel ? "" : currentValue, + ); + setRoleLabel(""); + setCompanyOpen(false); + }} /> {/* ROLES COMBOBOX */} ) => ( - { - setRoleLabel( - currentValue === roleLabel ? "" : currentValue, - ); - // Close the combobox - setRoleOpen(false); - }} - > - - {role.label} - - )} isOpen={roleOpen} setIsOpen={setRoleOpen} + currLabel={roleLabel} + onSelect={(currentValue) => { + setRoleLabel(currentValue === roleLabel ? "" : currentValue); + // Close the combobox + setRoleOpen(false); + }} /> From 609f2b67bb91f1ea0acad9763ef019a4a3e177f9 Mon Sep 17 00:00:00 2001 From: banushi-a Date: Sun, 14 Apr 2024 10:42:49 -0400 Subject: [PATCH 09/12] fix: remove + New Review Button when not signed in --- src/components/header.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/header.tsx b/src/components/header.tsx index ad10cad..cbf5fbe 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -10,7 +10,7 @@ import { NewReviewDialog } from "./new-review-dialog"; import { cn } from "~/lib/utils"; import { altivoFont } from "~/styles/font"; import { useSession } from "next-auth/react"; -import { usePathname, useRouter } from "next/navigation"; +import { usePathname } from "next/navigation"; /** * This is the header component. (Probably) should use header-layout instead @@ -77,7 +77,7 @@ export default function Header() { )} > {/* TODO: only show this if the user is below the max number of reviews allowed */} - + {session.data && } {session.data ? : } From b7c7084bf9fd2f65fb60588aaa8c34dad3d8d914 Mon Sep 17 00:00:00 2001 From: banushi-a Date: Sun, 14 Apr 2024 10:54:05 -0400 Subject: [PATCH 10/12] fix: resolve incorrect merge --- src/app/(dashboard)/roles/page.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/app/(dashboard)/roles/page.tsx b/src/app/(dashboard)/roles/page.tsx index 802cfca..59f606f 100644 --- a/src/app/(dashboard)/roles/page.tsx +++ b/src/app/(dashboard)/roles/page.tsx @@ -1,5 +1,6 @@ "use client"; +import { Review } from "@prisma/client"; import { useState } from "react"; import { ReviewCard } from "~/components/review-card"; import { ReviewCardPreview } from "~/components/review-card-preview"; @@ -10,7 +11,9 @@ import { api } from "~/trpc/react"; export default function Roles() { const reviews = api.review.list.useQuery(); - const [selectedReviewIdx, setSelectedReviewIdx] = useState(0); + const [selectedReview, setSelectedReview] = useState( + reviews.data ? reviews.data[0] : undefined, + ); return ( <> @@ -19,9 +22,9 @@ export default function Roles() { {reviews.data && (
- {reviews.data.map((review, idx) => { + {reviews.data.map((review) => { return ( -
setSelectedReviewIdx(idx)}> +
setSelectedReview(review)}>
{reviews.data.length > 0 && reviews.data[0] && ( - + )}
From 1f2e34ceb5ef0913c18cb07f30e9dfc2d61fda80 Mon Sep 17 00:00:00 2001 From: banushi-a Date: Sun, 14 Apr 2024 13:51:50 -0400 Subject: [PATCH 11/12] style: fix merge --- src/components/new-review-dialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/new-review-dialog.tsx b/src/components/new-review-dialog.tsx index bf8c80f..f8375e9 100644 --- a/src/components/new-review-dialog.tsx +++ b/src/components/new-review-dialog.tsx @@ -71,7 +71,7 @@ export function NewReviewDialog() { return ( - From 2fd2361f09bc7ebdaaf09713172c232e96dc0ea0 Mon Sep 17 00:00:00 2001 From: banushi-a Date: Sun, 14 Apr 2024 14:00:10 -0400 Subject: [PATCH 12/12] style: made lighter yellow --- src/components/new-review-dialog.tsx | 2 +- tailwind.config.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/new-review-dialog.tsx b/src/components/new-review-dialog.tsx index f8375e9..5c7065b 100644 --- a/src/components/new-review-dialog.tsx +++ b/src/components/new-review-dialog.tsx @@ -71,7 +71,7 @@ export function NewReviewDialog() { return ( - diff --git a/tailwind.config.ts b/tailwind.config.ts index 4e2e80d..c0f1553 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -29,6 +29,7 @@ const config = { "cooper-blue-600": "#436F8E", "cooper-blue-400": "#5E8BAB", "cooper-blue-200": "#DEEAF8", + "cooper-yellow-300": "#FFBF47", "cooper-yellow-500": "#FFA400", "cooper-yellow-600": "#AFA800", "cooper-pink-500": "#EA8FBA",