Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 121 additions & 42 deletions app/(app)/jobs/create/_client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import {
CheckboxGroup,
} from "@/components/ui-components/checkbox";
import { Divider } from "@/components/ui-components/divider";
import { Description, Field, Label } from "@/components/ui-components/fieldset";
import {
Description,
ErrorMessage,
Field,
Label,
} from "@/components/ui-components/fieldset";
import { Heading, Subheading } from "@/components/ui-components/heading";
import { Input } from "@/components/ui-components/input";
import {
Expand All @@ -17,22 +22,50 @@ import {
} from "@/components/ui-components/radio";
import { Strong, Text } from "@/components/ui-components/text";
import { Textarea } from "@/components/ui-components/textarea";
import { saveJobsInput, saveJobsSchema } from "@/schema/job";
import { FEATURE_FLAGS, isFlagEnabled } from "@/utils/flags";
import { zodResolver } from "@hookform/resolvers/zod";
import Image from "next/image";
import { notFound } from "next/navigation";
import React, { useRef, useState } from "react";
import { Controller, SubmitHandler, useForm } from "react-hook-form";

export default function Content() {
const {
register,
handleSubmit,
reset,
control,
formState: { errors, isSubmitting },
} = useForm<saveJobsInput>({
resolver: zodResolver(saveJobsSchema),
defaultValues: {
companyName: "",
jobTitle: "",
jobDescription: "",
jobLocation: "",
applicationUrl: "",
remote: false,
relocation: false,
visa_sponsorship: false,
jobType: "full-time",
},
});
const flagEnabled = isFlagEnabled(FEATURE_FLAGS.JOBS);
const fileInputRef = useRef<HTMLInputElement>(null);
const [imgUrl, setImgUrl] = useState<string | null>(null);

const onSubmit: SubmitHandler<saveJobsInput> = (values) => {
console.log(values);
};
if (!flagEnabled) {
notFound();
}

return (
<form className="mx-auto max-w-4xl p-3 pt-8 sm:px-4">
<form
className="mx-auto max-w-4xl p-3 pt-8 sm:px-4"
onSubmit={handleSubmit(onSubmit)}
>
<Heading level={1}>Post a job</Heading>
<Divider className="my-10 mt-6" />
<section className="grid gap-x-8 gap-y-6 sm:grid-cols-2">
Expand Down Expand Up @@ -89,9 +122,12 @@ export default function Content() {
type="text"
placeholder="Pixel Pulse Studios"
autoComplete="given-company-name"
{...register("companyName")}
/>
{errors?.companyName && (
<ErrorMessage>{errors.companyName.message}</ErrorMessage>
)}
</Field>
{/* Add error part after validation here */}
</section>

<Divider className="my-10" soft />
Expand All @@ -107,9 +143,12 @@ export default function Content() {
type="text"
placeholder="Reality Architect"
autoComplete="given-job-title"
{...register("jobTitle")}
/>
{errors?.jobTitle && (
<ErrorMessage>{errors.jobTitle.message}</ErrorMessage>
)}
</Field>
{/* Add error part after validation here */}
</section>

<Divider className="my-10" soft />
Expand All @@ -123,11 +162,13 @@ export default function Content() {
<Textarea
id="job-description"
placeholder="As a Reality Architect, you'll be at the forefront of creating immersive mixed reality experiences that blur the line between the digital and physical..."
resizable={false}
rows={3}
{...register("jobDescription")}
/>
{errors?.jobDescription && (
<ErrorMessage>{errors.jobDescription.message}</ErrorMessage>
)}
</Field>
{/* Add error part after validation here */}
</section>

<Divider className="my-10" soft />
Expand All @@ -140,23 +181,46 @@ export default function Content() {
</Text>
</div>
<Field>
<Input placeholder="Dublin (2 days in the office per week)" />
<Input
placeholder="Dublin (2 days in the office per week)"
{...register("jobLocation")}
/>
<CheckboxGroup className="mt-3">
<CheckboxField>
<Checkbox name="remote" value="is_remote" />
<Controller
name="remote"
control={control}
render={({ field }) => (
<Checkbox checked={field.value} onChange={field.onChange} />
)}
/>
<Label>Work is remote</Label>
</CheckboxField>
<CheckboxField>
<Checkbox name="relocation" value="is_relocation_package" />
<Controller
name="relocation"
control={control}
render={({ field }) => (
<Checkbox checked={field.value} onChange={field.onChange} />
)}
/>
<Label>Relocation package given</Label>
</CheckboxField>
<CheckboxField>
<Checkbox name="visa" value="is_visa_sponsored" />
<Controller
name="visa_sponsorship"
control={control}
render={({ field }) => (
<Checkbox checked={field.value} onChange={field.onChange} />
)}
/>
<Label>Visa sponsorship provided</Label>
</CheckboxField>
</CheckboxGroup>
{errors?.jobLocation && (
<ErrorMessage>{errors.jobLocation.message}</ErrorMessage>
)}
</Field>
{/* Add error part after validation here */}
</section>

<Divider className="my-10" soft />
Expand All @@ -172,9 +236,12 @@ export default function Content() {
type="text"
autoComplete="url"
placeholder="https://example.com"
{...register("applicationUrl")}
/>
{errors?.applicationUrl && (
<ErrorMessage>{errors.applicationUrl.message}</ErrorMessage>
)}
Comment on lines +239 to +243
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Good implementation of application URL and job type fields, but missing error handling for job type

The changes to the application URL input and job type radio group are well-implemented:

  1. The application URL input is correctly registered with react-hook-form.
  2. The job type radio group uses the Controller component, which is appropriate for this type of form control.
  3. Error handling has been added for the application URL field.

However, error handling for the job type field is missing. Consider adding error display for the job type field to maintain consistency with other form fields.

Add error handling for the job type field:

{errors?.jobType && (
  <ErrorMessage>{errors.jobType.message}</ErrorMessage>
)}

Also applies to: 255-289

</Field>
{/* Add error part after validation here */}
</section>

<Divider className="my-10" soft />
Expand All @@ -185,34 +252,42 @@ export default function Content() {
<Text>Full-time, part-time or freelancer</Text>
</div>
<Field>
<RadioGroup defaultValue="full_time">
<RadioField>
<Radio value="full_time" />
<Label>Full-time (€150)</Label>
<Description>Salaried Position</Description>
</RadioField>
<RadioField>
<Radio value="part_time" />
<Label>Part-time (€100)</Label>
<Description>
Salaried position but less than 4 days per week
</Description>
</RadioField>
<RadioField>
<Radio value="freelancer" />
<Label>Freelancer (€100)</Label>
<Description>Shorter-term usually or fixed term/job</Description>
</RadioField>
<RadioField>
<Radio value="other_role_type" />
<Label>Other (€100)</Label>
<Description>
Looking for a co-founder or something else we haven’t thought of
</Description>
</RadioField>
</RadioGroup>
<Controller
name="jobType"
control={control}
render={({ field }) => (
<RadioGroup value={field.value} onChange={field.onChange}>
<RadioField>
<Radio value="full-time" />
<Label>Full-time (€150)</Label>
<Description>Salaried Position</Description>
</RadioField>
<RadioField>
<Radio value="part-time" />
<Label>Part-time (€100)</Label>
<Description>
Salaried position but less than 4 days per week
</Description>
</RadioField>
<RadioField>
<Radio value="freelancer" />
<Label>Freelancer (€100)</Label>
<Description>
Shorter-term usually or fixed term/job
</Description>
</RadioField>
<RadioField>
<Radio value="other" />
<Label>Other (€100)</Label>
<Description>
Looking for a co-founder or something else we haven’t
thought of
</Description>
</RadioField>
</RadioGroup>
)}
/>
</Field>
{/* Add error part after validation here */}
</section>

<Divider className="my-10" soft />
Expand Down Expand Up @@ -252,13 +327,17 @@ export default function Content() {
practices.
</Text>
</div>
{/* Add error part after validation here */}
</section>

<Divider className="my-10" soft />

<div className="flex justify-end">
<Button className="rounded-md" color="pink">
<Button
className="rounded-md"
color="pink"
type="submit"
disabled={isSubmitting}
>
Submit and checkout
</Button>
</div>
Expand Down
32 changes: 32 additions & 0 deletions schema/job.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import z from "zod";

export const saveJobsSchema = z.object({
companyName: z
.string()
.min(1, "Company name should contain atleast 1 character")
.max(50, "Company name should contain atmost 50 characters"),
jobTitle: z
.string()
.min(3, "Job title should contain atleast 3 character")
.max(50, "Job title should contain atmost 50 characters"),
jobDescription: z
.string()
.min(100, "Job Description should contain atleast 100 characters")
.max(2000, "Job Description should contain atmost 2000 characters")
.optional(),
jobLocation: z
.string()
.min(3, "Location should contain atleast 3 characters")
.max(40, "Max location length is 40 characters."),
applicationUrl: z
.string()
.url("Provide a valid url")
.optional()
.or(z.literal("")),
remote: z.boolean().optional().default(false),
relocation: z.boolean().optional().default(false),
visa_sponsorship: z.boolean().optional().default(false),
jobType: z.enum(["full-time", "part-time", "freelancer", "other"]),
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would love some thoughts on if you think all of these are sensible too? 🦾

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think yes, these should also be populated. As a result these values can be shown in the jobs page with their associated jobs.
A sample shown below present on YC jobs page

image


export type saveJobsInput = z.TypeOf<typeof saveJobsSchema>;