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 numberDiff line numberDiff 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 DownExpand 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
Copy Markdown
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 DownExpand 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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>;
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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 numberDiff line numberDiff 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 DownExpand 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
Copy Markdown
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 DownExpand 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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>;
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 DownExpand 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
Copy Markdown
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 DownExpand 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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>;
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 DownExpand 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
Copy Markdown
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 DownExpand 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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>;
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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 numberDiff line numberDiff 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 DownExpand 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
Copy Markdown
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 DownExpand 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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>;
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 DownExpand 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
Copy Markdown
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 DownExpand 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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>;
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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 DownExpand 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
Copy Markdown
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 DownExpand 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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>;
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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 numberDiff line numberDiff 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 DownExpand 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
Copy Markdown
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 DownExpand 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 numberDiff line numberDiff 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
Copy Markdown
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
Copy Markdown
ContributorAuthor

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>;