Skip to content
Draft
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
37 changes: 31 additions & 6 deletions apps/cursor/src/components/company/add-company-button.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,46 @@
"use client";

import { usePathname, useRouter } from "next/navigation";
import { parseAsBoolean, useQueryStates } from "nuqs";
import { useEffect, useState } from "react";
import { createClient } from "@/utils/supabase/client";
import { Button } from "../ui/button";

export function AddCompanyButton({ redirect }: { redirect?: boolean }) {
const router = useRouter();
const pathname = usePathname();
const supabase = createClient();
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);

const [_, setAddCompany] = useQueryStates({
addCompany: parseAsBoolean.withDefault(false),
redirect: parseAsBoolean.withDefault(redirect ?? false),
});

useEffect(() => {
async function getUser() {
const session = await supabase.auth.getSession();

setIsAuthenticated(Boolean(session.data.session));
}

getUser();
}, []);

const handleClick = () => {
// Adding a company requires an authenticated session: the save action is
// auth-guarded and the logo upload hits an auth-only storage policy. Send
// signed-out visitors to sign in instead of into a form that can't succeed.
if (!isAuthenticated) {
router.push(`/login?next=${pathname}`);
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Session loading redirects signed-in users

Medium Severity · Logic Bug

isAuthenticated starts as null while getSession runs, but handleClick treats any falsy value like a signed-out user and sends them to /login. A signed-in user who clicks Add company before the effect finishes can be redirected incorrectly.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6f9bbb8. Configure here.

}

setAddCompany({ addCompany: true, redirect });
};

return (
<Button
type="button"
variant="outline"
size="lg"
onClick={() => setAddCompany({ addCompany: true, redirect })}
>
<Button type="button" variant="outline" size="lg" onClick={handleClick}>
Add company
</Button>
);
Expand Down
58 changes: 43 additions & 15 deletions apps/cursor/src/components/upload-logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { PlusIcon } from "lucide-react";
import Image from "next/image";
import { type ChangeEvent, type DragEvent, useRef, useState } from "react";
import { toast } from "sonner";
import { createClient } from "@/utils/supabase/client";

interface UploadLogoProps {
Expand All @@ -23,33 +24,52 @@ export default function UploadLogo({

const handleFile = async (file: File) => {
if (!file.type.startsWith("image/")) {
toast.error("Please select an image file.");
return;
}

const MAX_FILE_SIZE = 1024 * 1024; // 1MB in bytes
if (file.size > MAX_FILE_SIZE) {
toast.error("Image must be smaller than 1MB.");
return;
}

const previousPreview = preview;
setIsUploading(true);

// Show an optimistic preview while the upload is in flight. It is reverted
// below if the upload fails so the UI never shows an image that wasn't
// actually saved.
const reader = new FileReader();
reader.onload = (e) => {
setPreview(e.target?.result as string);
};
reader.readAsDataURL(file);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Late FileReader restores failed preview

Medium Severity · Logic Bug

The optimistic FileReader preview runs in parallel with auth and storage upload. If upload fails or the user is unsigned, the code reverts preview, but a later reader.onload can still call setPreview with the local data URL, showing an image that was never saved.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6f9bbb8. Configure here.


try {
// Create preview
const reader = new FileReader();
reader.onload = (e) => {
const dataUrl = e.target?.result as string;
setPreview(dataUrl);
};
reader.readAsDataURL(file);

// Upload to Supabase Storage
const supabase = createClient();

// Uploading to the avatars bucket requires an authenticated session. The
// bucket's row-level security policy only allows the `authenticated`
// role, so without a session the request is sent as `anon` and Storage
// rejects it with "new row violates row-level security policy".
const {
data: { user },
} = await supabase.auth.getUser();

if (!user) {
toast.error("Please sign in to upload an image.");
setPreview(previousPreview);
return;
}

const fileExt = file.name.split(".").pop();
const fileName = `${Math.random().toString(36).substring(2)}.${fileExt}`;
const path = `${prefix}/${fileName}`;

const { data, error } = await supabase.storage
const { error } = await supabase.storage
.from("avatars")
.upload(`${prefix}/${fileName}`, file, {
.upload(path, file, {
cacheControl: "3600",
upsert: false,
});
Expand All @@ -58,16 +78,18 @@ export default function UploadLogo({
throw error;
}

// Get public URL
const {
data: { publicUrl },
} = supabase.storage
.from("avatars")
.getPublicUrl(`${prefix}/${fileName}`);
} = supabase.storage.from("avatars").getPublicUrl(path);

setPreview(publicUrl);
onUpload?.(publicUrl);
} catch (error) {
console.error("Error uploading file:", error);
toast.error(
error instanceof Error ? error.message : "Failed to upload image.",
);
setPreview(previousPreview);
} finally {
setIsUploading(false);
}
Expand Down Expand Up @@ -149,6 +171,12 @@ export default function UploadLogo({
<PlusIcon className="size-4" />
</div>
)}

{isUploading && (
<div className="absolute inset-0 flex items-center justify-center rounded-lg bg-black/50 text-[10px] font-medium text-white">
Uploading...
</div>
)}
</div>
);
}