Skip to content

Commit eab47de

Browse files
committedFeb 1, 2025·
upload fix
1 parent 7957fba commit eab47de

File tree

8 files changed

+78
-49
lines changed

8 files changed

+78
-49
lines changed
 

‎src/app/actions.ts

+4
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ export async function createJob(formData: FormData) {
160160
throw new Error("Failed to create job");
161161
}
162162

163+
await updateBySlackId("user", session.user.id, {
164+
has_ever_submitted: true,
165+
});
166+
163167
revalidatePath("/dashboard/jobs");
164168
return { success: true, jobId: result.id };
165169
}

‎src/app/api/jobs/[jobId]/upload/route.ts

+22-6
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,44 @@ export async function POST(
1717
if (!job) {
1818
return new NextResponse("Job not found", { status: 404 });
1919
}
20-
if (job["(auto)(creator)slack_id"]?.[0] !== session.user.id) {
21-
return new NextResponse("Unauthorized", { status: 403 });
22-
}
2320

2421
// Get the file and metadata from the request
2522
const formData = await request.formData();
2623
const file = formData.get("file") as File;
2724
const isMain = formData.get("isMain") === "true";
28-
const fileType = formData.get("fileType") as "stl" | "image";
25+
const fileType = formData.get("fileType") as
26+
| "stl"
27+
| "image"
28+
| "fulfillment_photo";
29+
30+
if (
31+
["stl", "image"].includes(fileType)
32+
? job["(auto)(creator)slack_id"]?.[0] !== session.user.id
33+
: job["(auto)(assigned_printer)slack_id"]?.[0] !== session.user.id
34+
) {
35+
return new NextResponse("Unauthorized", { status: 403 });
36+
}
2937

3038
if (!file) {
3139
return new NextResponse("No file provided", { status: 400 });
3240
}
33-
if (!fileType || !["stl", "image"].includes(fileType)) {
41+
if (
42+
!fileType ||
43+
!["stl", "image", "fulfillment_photo"].includes(fileType)
44+
) {
3445
return new NextResponse("Invalid file type", { status: 400 });
3546
}
3647

3748
// Convert file to buffer for Airtable upload
3849
const buffer = await file.arrayBuffer();
3950

4051
// Upload to Airtable
41-
const uploadEndpoint = fileType === "stl" ? "stls" : "user_images";
52+
// const uploadEndpoint = fileType === "stl" ? "stls" : "user_images";
53+
const uploadEndpoint = {
54+
stl: "stls",
55+
image: "user_images",
56+
fulfillment_photo: "fulfilment_photo",
57+
}[fileType];
4258
const response = await fetch(
4359
`https://content.airtable.com/v0/${process.env.AIRTABLE_BASE_ID}/${job.id}/${uploadEndpoint}/uploadAttachment`,
4460
{

‎src/app/dashboard/jobs/[jobId]/client-components.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export const PrinterDetails = ({
164164
</div>
165165
)}
166166
{user.region_coordinates && (
167-
<div className="flex items-center gap-2 text-sm text-muted-foreground">
167+
<div className="flex items-center gap-2 text-sm">
168168
<MapPin className="h-4 w-4" />
169169
<span>Region available</span>
170170
</div>

‎src/app/dashboard/jobs/[jobId]/states/actions.ts

+2-10
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,7 @@ export async function completePrinting(
6565
throw new Error("Failed to complete printing");
6666
}
6767

68-
export async function markFulfilled(
69-
jobId: string,
70-
fulfillmentPhoto: FulfillmentPhoto
71-
) {
68+
export async function markFulfilled(jobId: string, description?: string) {
7269
const session = await auth();
7370
if (!session?.user?.id) {
7471
throw new Error("Not authenticated");
@@ -83,14 +80,9 @@ export async function markFulfilled(
8380
throw new Error("Not authorized to mark this job as fulfilled");
8481
}
8582

86-
// TODO: Upload fulfillment photo using existing upload logic
87-
// const uploadFormData = new FormData();
88-
// uploadFormData.append("file", fulfillmentPhoto.file);
89-
// uploadFormData.append("fileType", "fulfillment_photo");
90-
// uploadFormData.append("description", fulfillmentPhoto.description || "");
91-
9283
const success = await updateBySlackId("job", jobId, {
9384
status: "fulfilled_awaiting_confirmation" as JobStatusType,
85+
handoff_comments: description,
9486
});
9587

9688
if (success) {

‎src/app/dashboard/jobs/[jobId]/states/fulfillment-buttons.tsx

+14-3
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,21 @@ export function MarkFulfilledButton({
5454

5555
try {
5656
setIsLoading(true);
57-
await markFulfilled(jobId, {
58-
file: selectedFile,
59-
description,
57+
await markFulfilled(jobId, description);
58+
59+
const uploadFormData = new FormData();
60+
uploadFormData.append("file", selectedFile);
61+
uploadFormData.append("fileType", "fulfillment_photo");
62+
63+
const response = await fetch(`/api/jobs/${jobId}/upload`, {
64+
method: "POST",
65+
body: uploadFormData,
6066
});
67+
68+
if (!response.ok) {
69+
throw new Error(`Failed to upload fulfillment photo`);
70+
}
71+
6172
toast.success("Job marked as fulfilled", {
6273
description: "Waiting for the submitter to confirm receipt.",
6374
});

‎src/components/ui/button.tsx

+23-21
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,59 @@
1-
import * as React from "react"
2-
import { Slot } from "@radix-ui/react-slot"
3-
import { cva, type VariantProps } from "class-variance-authority"
4-
5-
import { cn } from "@/lib/utils"
1+
import * as React from "react";
2+
import { Slot } from "@radix-ui/react-slot";
3+
import { cva, type VariantProps } from "class-variance-authority";
4+
import { cn } from "@/lib/utils";
65

76
const buttonVariants = cva(
8-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
7+
"inline-flex items-center justify-center whitespace-nowrap text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-zinc-950 disabled:pointer-events-none disabled:opacity-50 dark:focus-visible:ring-zinc-300 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
98
{
109
variants: {
1110
variant: {
1211
default:
13-
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
12+
"border border-transparent bg-zinc-900 text-zinc-50 shadow hover:bg-zinc-900/90 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-50/90",
1413
destructive:
15-
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
14+
"bg-red-500 text-zinc-50 shadow-sm hover:bg-red-500/90 dark:bg-red-900 dark:text-zinc-50 dark:hover:bg-red-900/90",
15+
"destructive-outline":
16+
"border border-red-200 bg-white shadow-sm hover:bg-red-100 hover:text-red-900 dark:border-red-800 dark:bg-red-950 dark:hover:bg-red-800 dark:hover:text-red-50",
1617
outline:
17-
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18+
"border border-zinc-200 bg-white shadow-sm hover:bg-zinc-100 hover:text-zinc-900 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:bg-zinc-800 dark:hover:text-zinc-50",
1819
secondary:
19-
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20-
ghost: "hover:bg-accent hover:text-accent-foreground",
21-
link: "text-primary underline-offset-4 hover:underline",
20+
"bg-zinc-100 text-zinc-900 shadow-sm hover:bg-zinc-100/80 dark:bg-zinc-800 dark:text-zinc-50 dark:hover:bg-zinc-800/80",
21+
ghost:
22+
"hover:bg-zinc-100 hover:text-zinc-900 dark:hover:bg-zinc-800 dark:hover:text-zinc-50",
23+
link: "text-zinc-900 underline-offset-4 hover:underline dark:text-zinc-50",
2224
},
2325
size: {
24-
default: "h-9 px-4 py-2",
26+
default: "h-9 rounded-xl px-4 py-2",
2527
sm: "h-8 rounded-md px-3 text-xs",
2628
lg: "h-10 rounded-md px-8",
27-
icon: "h-9 w-9",
29+
icon: "h-9 w-9 rounded-md",
2830
},
2931
},
3032
defaultVariants: {
3133
variant: "default",
3234
size: "default",
3335
},
3436
}
35-
)
37+
);
3638

3739
export interface ButtonProps
3840
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
3941
VariantProps<typeof buttonVariants> {
40-
asChild?: boolean
42+
asChild?: boolean;
4143
}
4244

4345
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
4446
({ className, variant, size, asChild = false, ...props }, ref) => {
45-
const Comp = asChild ? Slot : "button"
47+
const Comp = asChild ? Slot : "button";
4648
return (
4749
<Comp
4850
className={cn(buttonVariants({ variant, size, className }))}
4951
ref={ref}
5052
{...props}
5153
/>
52-
)
54+
);
5355
}
54-
)
55-
Button.displayName = "Button"
56+
);
57+
Button.displayName = "Button";
5658

57-
export { Button, buttonVariants }
59+
export { Button, buttonVariants };

‎src/components/ui/input.tsx

+10-8
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
1-
import * as React from "react"
1+
import * as React from "react";
2+
import { cn } from "@/lib/utils";
23

3-
import { cn } from "@/lib/utils"
4+
export interface InputProps
5+
extends React.InputHTMLAttributes<HTMLInputElement> {}
46

5-
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
7+
const Input = React.forwardRef<HTMLInputElement, InputProps>(
68
({ className, type, ...props }, ref) => {
79
return (
810
<input
911
type={type}
1012
className={cn(
11-
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
13+
"flex h-9 w-full rounded-md border border-input bg-card px-3 py-1 text-card-foreground hover:border-primary/40 focus-visible:border-transparent shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
1214
className
1315
)}
1416
ref={ref}
1517
{...props}
1618
/>
17-
)
19+
);
1820
}
19-
)
20-
Input.displayName = "Input"
21+
);
22+
Input.displayName = "Input";
2123

22-
export { Input }
24+
export { Input };

‎src/lib/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ export const JobSchema = z.object({
6868

6969
fulfilment_photo: z.array(AirtableAttachmentSchema).optional(),
7070
gcode_files: z.array(AirtableAttachmentSchema).optional(),
71+
72+
handoff_comments: z.string().optional(),
7173
});
7274

7375
// Printer table schema

0 commit comments

Comments
 (0)