Skip to content

Commit 4b53989

Browse files
feat: add company logo upload functionality to job creation form (#1283)
- Implemented file upload for company logo with size validation (max 1MB). - Integrated S3 upload functionality and error handling with Sentry for logging. - Updated job schema to include optional companyLogo field. - Enhanced user feedback with toast notifications during upload process.
1 parent 8c3c8b5 commit 4b53989

File tree

2 files changed

+71
-3
lines changed

2 files changed

+71
-3
lines changed

app/(app)/jobs/create/_client.tsx

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,15 @@ import { Strong, Text } from "@/components/ui-components/text";
2424
import { Textarea } from "@/components/ui-components/textarea";
2525
import { saveJobsInput, saveJobsSchema } from "@/schema/job";
2626
import { FEATURE_FLAGS, isFlagEnabled } from "@/utils/flags";
27+
import { uploadFile } from "@/utils/s3helpers";
28+
import { getUploadUrl } from "@/app/actions/getUploadUrl";
2729
import { zodResolver } from "@hookform/resolvers/zod";
2830
import Image from "next/image";
2931
import { notFound } from "next/navigation";
3032
import React, { useRef, useState } from "react";
3133
import { Controller, SubmitHandler, useForm } from "react-hook-form";
34+
import * as Sentry from "@sentry/nextjs";
35+
import { toast } from "sonner";
3236

3337
export default function Content() {
3438
const {
@@ -54,8 +58,70 @@ export default function Content() {
5458
const flagEnabled = isFlagEnabled(FEATURE_FLAGS.JOBS);
5559
const fileInputRef = useRef<HTMLInputElement>(null);
5660
const [imgUrl, setImgUrl] = useState<string | null>(null);
61+
const [uploadStatus, setUploadStatus] = useState<
62+
"idle" | "loading" | "success" | "error"
63+
>("idle");
5764
const onSubmit: SubmitHandler<saveJobsInput> = (values) => {
58-
console.log(values);
65+
const formData = {
66+
...values,
67+
companyLogo: imgUrl || undefined,
68+
};
69+
console.log(formData);
70+
};
71+
72+
const handleLogoUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
73+
if (uploadStatus === "loading") {
74+
return toast.info("Upload in progress, please wait...");
75+
}
76+
77+
if (e.target.files && e.target.files.length > 0) {
78+
setUploadStatus("loading");
79+
80+
const file = e.target.files[0];
81+
const { size, type } = file;
82+
83+
if (size > 1048576) {
84+
setUploadStatus("error");
85+
return toast.error("File size too big (max 1MB).");
86+
}
87+
88+
try {
89+
const res = await getUploadUrl({
90+
size,
91+
type,
92+
uploadType: "uploads",
93+
});
94+
95+
const signedUrl = res?.data;
96+
97+
if (!signedUrl) {
98+
setUploadStatus("error");
99+
return toast.error(
100+
"Something went wrong uploading the logo, please retry.",
101+
);
102+
}
103+
104+
const { fileLocation } = await uploadFile(signedUrl, file);
105+
if (!fileLocation) {
106+
setUploadStatus("error");
107+
return toast.error(
108+
"Something went wrong uploading the logo, please retry.",
109+
);
110+
}
111+
112+
setUploadStatus("success");
113+
setImgUrl(fileLocation);
114+
toast.success("Company logo uploaded successfully!");
115+
} catch (error) {
116+
setUploadStatus("error");
117+
toast.error(
118+
error instanceof Error
119+
? error.message
120+
: "An error occurred while uploading the logo.",
121+
);
122+
Sentry.captureException(error);
123+
}
124+
}
59125
};
60126
if (!flagEnabled) {
61127
notFound();
@@ -89,15 +155,16 @@ export default function Content() {
89155
onClick={() => {
90156
fileInputRef.current?.click();
91157
}}
158+
disabled={uploadStatus === "loading"}
92159
>
93-
Change Logo
160+
{uploadStatus === "loading" ? "Uploading..." : "Change Logo"}
94161
</Button>
95162
<Input
96163
type="file"
97164
id="file-input"
98165
name="company-logo"
99166
accept="image/png, image/gif, image/jpeg"
100-
onChange={() => {}}
167+
onChange={handleLogoUpload}
101168
className="hidden"
102169
ref={fileInputRef}
103170
/>

schema/job.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const saveJobsSchema = z.object({
2323
.url("Provide a valid url")
2424
.optional()
2525
.or(z.literal("")),
26+
companyLogo: z.string().optional(),
2627
remote: z.boolean().optional().default(false),
2728
relocation: z.boolean().optional().default(false),
2829
visa_sponsorship: z.boolean().optional().default(false),

0 commit comments

Comments
 (0)