Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add combobox for locations #129

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"version": "0.2.0",
"configurations": [

{
"name": "Next.js",
"type": "node-terminal",
Expand Down
26 changes: 23 additions & 3 deletions apps/web/src/app/_components/combo-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
CommandItem,
CommandList,
} from "@cooper/ui/command";
import { FormLabel } from "@cooper/ui/form";

Check failure on line 14 in apps/web/src/app/_components/combo-box.tsx

View workflow job for this annotation

GitHub Actions / lint

'FormLabel' is defined but never used. Allowed unused vars must match /^_/u
import { Popover, PopoverContent, PopoverTrigger } from "@cooper/ui/popover";

export interface ComboBoxOption<T> {
Expand All @@ -26,6 +27,8 @@
currLabel: string;
onSelect: (option: string) => void;
triggerClassName?: string;
onChange?: (value: string) => void;
variant?: "default" | "form";
}

/**
Expand All @@ -41,9 +44,16 @@
currLabel,
onSelect,
triggerClassName,
onChange,
variant,
}: ComboBoxProps) {
const [isOpen, setIsOpen] = useState(false);

const styleVariant =
variant === "form"
? "flex h-16 w-full rounded-md border-[3px] border-cooper-blue-600 bg-white px-3 py-2 text-xl font-normal ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
: "";

return (
<Popover open={isOpen} onOpenChange={setIsOpen}>
<PopoverTrigger
Expand All @@ -57,15 +67,25 @@
variant="outline"
role="combobox"
aria-expanded={isOpen}
className="justify-between"
className={cn(
styleVariant,
"justify-between overflow-hidden text-ellipsis text-nowrap",
)}
>
{defaultLabel}
<span className="overflow-hidden text-ellipsis whitespace-nowrap">
{defaultLabel}
</span>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[400px] p-0">
<Command>
<CommandInput placeholder={searchPlaceholder} />
<CommandInput
placeholder={searchPlaceholder}
onChangeCapture={(e) =>
onChange && onChange((e.target as HTMLInputElement).value)
}
/>
<CommandEmpty>{searchEmpty}</CommandEmpty>
<CommandGroup>
<CommandList>
Expand Down
52 changes: 47 additions & 5 deletions apps/web/src/app/_components/form/sections/review-section.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { useEffect, useState } from "react";
import { useFormContext } from "react-hook-form";

import type { LocationType } from "@cooper/db/schema";
import {
FormControl,
FormField,
Expand All @@ -10,14 +12,40 @@
import { Input } from "@cooper/ui/input";
import { Textarea } from "@cooper/ui/textarea";

import type { ComboBoxOption } from "~/app/_components/combo-box";
import ComboBox from "~/app/_components/combo-box";
import { FormSection } from "~/app/_components/form/form-section";
import { api } from "~/trpc/server";

/**
* ReviewSection component renders form fields for writing a co-op review.
*/
export function ReviewSection({ textColor }: { textColor: string }) {
const form = useFormContext();

const [locations, setLocations] = useState<ComboBoxOption<string>[]>([]);
const [locationLabel, setLocationLabel] = useState<string>("");
const [searchTerm, setSearchTerm] = useState<string>("");

useEffect(() => {
const fetchLocations = async () => {
if (searchTerm.length === 2 || searchTerm.length === 3) {
const prefix = searchTerm.toLowerCase();

const locationsToUpdate = await api.location.getByPrefix({ prefix });

setLocations(
locationsToUpdate.map((location: LocationType) => ({
value: location.id,
label: location.city,
})),
);
}
};

fetchLocations();

Check failure on line 46 in apps/web/src/app/_components/form/sections/review-section.tsx

View workflow job for this annotation

GitHub Actions / lint

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
}, [searchTerm]);

return (
<FormSection title="Review" className={textColor}>
<FormField
Expand Down Expand Up @@ -53,13 +81,27 @@
render={({ field }) => (
<FormItem>
<FormLabel>Location</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
<ComboBox
variant="form"
defaultLabel={locationLabel || "Select location..."}
searchPlaceholder="Search location..."
searchEmpty="No location found."
valuesAndLabels={locations}
currLabel={locationLabel}
onChange={(value) => {
setSearchTerm(value);
field.onChange(value);
}}
onSelect={(currentValue) => {
setLocationLabel(
currentValue === locationLabel ? "" : currentValue,
);
field.onChange(currentValue);
}}
/>
</FormItem>
)}
/>
></FormField>
<FormField
control={form.control}
name="hourlyPay"
Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
profileRouter,
reviewRouter,
roleRouter,
locationRouter,
} from "./router";
import { createTRPCRouter } from "./trpc";

Expand All @@ -13,6 +14,7 @@ export const appRouter = createTRPCRouter({
role: roleRouter,
profile: profileRouter,
review: reviewRouter,
location: locationRouter,
});

// export type definition of API
Expand Down
3 changes: 2 additions & 1 deletion packages/api/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ import { companyRouter } from "./company";
import { profileRouter } from "./profile";
import { reviewRouter } from "./review";
import { roleRouter } from "./role";
import { locationRouter } from "./location";

export { authRouter, companyRouter, profileRouter, reviewRouter, roleRouter };
export { authRouter, companyRouter, profileRouter, reviewRouter, roleRouter, locationRouter };
31 changes: 31 additions & 0 deletions packages/api/src/router/location.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { TRPCRouterRecord } from "@trpc/server";
import { z } from "zod";

import { asc } from "@cooper/db";
import { CreateLocationSchema, Location } from "@cooper/db/schema";

import { protectedProcedure, publicProcedure } from "../trpc";

// doesn't work yet bc the schema is on my other laptop :D
export const locationRouter = {
list: publicProcedure.query(({ ctx }) => {
return ctx.db.query.Location.findMany({
orderBy: asc(Location.city),
});
}),

getByPrefix: publicProcedure
.input(z.object({ prefix: z.string() }))
.query(({ ctx, input }) => {
return ctx.db.query.Location.findMany({
where: (loc, { ilike }) => ilike(loc.city, `${input.prefix}%`),
orderBy: asc(Location.city),
});
}),

create: protectedProcedure
.input(CreateLocationSchema)
.mutation(({ ctx, input }) => {
return ctx.db.insert(Location).values(input);
}),
} satisfies TRPCRouterRecord;
11 changes: 11 additions & 0 deletions packages/db/drizzle/0004_low_steve_rogers.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS "location" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"city" varchar NOT NULL,
"state" varchar,
"country" varchar NOT NULL
);
--> statement-breakpoint
-- ALTER TABLE "company_request" RENAME COLUMN "location" TO "locationId" IF EXISTS "location";--> statement-breakpoint
-- ALTER TABLE "review" RENAME COLUMN "location" TO "locationId" IF EXISTS "location";--> statement-breakpoint
ALTER TABLE "company_request" ALTER COLUMN "locationId" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "company" DROP COLUMN IF EXISTS "location";
11 changes: 11 additions & 0 deletions packages/db/drizzle/0006_fantastic_inertia.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS "location" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"city" varchar NOT NULL,
"state" varchar,
"country" varchar NOT NULL
);
--> statement-breakpoint
ALTER TABLE "company_request" ADD COLUMN "locationId" varchar;--> statement-breakpoint
ALTER TABLE "review" ADD COLUMN "locationId" varchar;--> statement-breakpoint
ALTER TABLE "company_request" DROP COLUMN IF EXISTS "location";--> statement-breakpoint
ALTER TABLE "review" DROP COLUMN IF EXISTS "location";
Loading
Loading