Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
848bbe0
feat: add UploadImage component for image upload
maeEsp Jan 24, 2026
e0e511d
feat: add upload functionality for event banner and thumbnail images
mugikarl Jan 24, 2026
a81eaca
feat: add upload section for ticket thumbnail in CreateTicket component
mugikarl Jan 26, 2026
7f3817c
Merge branch 'main' of https://github.com/SAMAHAN-Systems-Development…
maeEsp Feb 5, 2026
cdd5cf2
feat: implement upload functionality for event banners and thumbnails
mugikarl Feb 8, 2026
ad51cab
feat: enhance upload components and improve code formatting
mugikarl Feb 8, 2026
b8a35e0
Merge branch 'feat/39-media-asset-upload-section' of https://github.c…
mugikarl Feb 8, 2026
7c5ce68
Merge branch 'main' of https://github.com/SAMAHAN-Systems-Development…
maeEsp Feb 8, 2026
a2761fb
feat: add upload functionality for event banners and thumbnails, enha…
mugikarl Feb 15, 2026
d677d06
Merge branch 'feat/39-media-asset-upload-section' of https://github.c…
mugikarl Feb 15, 2026
e131c77
feat: implement image upload and delete functionality for event banne…
mugikarl Feb 21, 2026
9c6385d
feat: remove unused preview URL state and related logic from upload m…
mugikarl Feb 21, 2026
d05f793
Merge branch 'main' of https://github.com/SAMAHAN-Systems-Development…
maeEsp Feb 22, 2026
f498dff
Merge branch 'feat/39-media-asset-upload-section' of https://github.c…
maeEsp Feb 22, 2026
2f13128
Fix banner and thumbnail removal logic to use empty strings instead o…
maeEsp Feb 22, 2026
f596dc1
chore: re-push changes due to failed push from slow internet
maeEsp Feb 22, 2026
6680fce
feat: update ticket thumbnail removal logic to use undefined instead …
mugikarl Feb 26, 2026
0b2f177
Merge branch 'feat/39-media-asset-upload-section' of https://github.c…
mugikarl Feb 26, 2026
513d233
fix: update banner and thumbnail removal logic to use empty strings i…
mugikarl Feb 26, 2026
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
486 changes: 398 additions & 88 deletions app/(main)/events/[id]/page.tsx

Large diffs are not rendered by default.

159 changes: 159 additions & 0 deletions components/features/events/upload-banner-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
"use client";

import { X } from "lucide-react";
import { Button } from "@/components/ui/button";
import UploadImage, { type UploadData } from "@/components/shared/upload-image";
import { useState } from "react";
import { deleteAsset } from "@/lib/api/services/assetService";

interface UploadBannerModalProps {
isOpen: boolean;
onClose: () => void;
onSubmit?: (uploadData: UploadData) => Promise<void>;
existingImageKey?: string; // Key of existing image to replace
}

export function UploadBannerModal({
isOpen,
onClose,
onSubmit,
existingImageKey,
}: UploadBannerModalProps) {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [uploadError, setUploadError] = useState<string | null>(null);
const [isUploading, setIsUploading] = useState(false);

if (!isOpen) return null;

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const handleFileSelect = (file: File, _preview: string) => {
console.log("Banner file selected:", file.name);
setSelectedFile(file);
setUploadError(null);
};

const handleUploadError = (error: string) => {
console.error("Upload error:", error);
setUploadError(error);
};

const handleSubmit = async () => {
if (!selectedFile) return;

setIsUploading(true);
try {
// Delete old image if replacing
if (existingImageKey) {
try {
await deleteAsset(existingImageKey);
console.log("Old banner deleted:", existingImageKey);
} catch (error) {
console.error("Failed to delete old banner:", error);
// Continue with upload even if delete fails
}
}

// Upload new image
const { uploadEventBanner } = await import(
"@/lib/api/services/assetService"
);

const uploadResult = await uploadEventBanner(selectedFile);

const uploadData: UploadData = {
url: uploadResult.url,
key: uploadResult.key,
bucket: uploadResult.bucket,
fileName: selectedFile.name,
fileSize: selectedFile.size,
fileType: selectedFile.type,
};

if (onSubmit) {
await onSubmit(uploadData);
}
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "Failed to upload banner";
setUploadError(errorMessage);
console.error("Banner upload failed:", error);
} finally {
setIsUploading(false);
}
};

const handleClose = () => {
setSelectedFile(null);
setUploadError(null);
setIsUploading(false);
onClose();
};

return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-2 sm:p-4">
<div className="bg-white rounded-lg shadow-xl w-full max-w-sm sm:max-w-lg md:max-w-2xl">
{/* Header */}
<div className="flex items-center justify-between p-2 sm:p-4 md:p-6 border-gray-200">
<h2 className="text-2xl sm:text-2xl md:text-2xl font-semibold text-gray-900">
Upload Event Banner
</h2>
<button
onClick={handleClose}
className="text-gray-400 hover:text-gray-600 transition-colors flex-shrink-0"
>
<X className="h-3 w-3 sm:h-4 sm:w-4 md:h-5 md:w-5" />
</button>
</div>

{/* Content */}
<div className="p-2 sm:p-4 md:p-6 space-y-1 sm:space-y-2 md:space-y-4">
<p className="text-[10px] sm:text-xs md:text-base text-gray-700">
Select a file to upload and click the submit button
</p>

<div className="text-[9px] sm:text-[10px] md:text-sm text-gray-500 space-y-0 sm:space-y-0.5">
<p>Maximum file size: 10 MB</p>
<p>Accepted file types: png, jpg, jpeg</p>
<p>Note: For optimal display, your image should have a 1:1 ratio</p>
</div>

{/* Upload Image Component */}
<div className="mt-1 sm:mt-2 md:mt-4">
<UploadImage
uploadType="event-banner"
immediateUpload={false}
onFileSelect={handleFileSelect}
onUploadError={handleUploadError}
acceptedTypes={[
"image/png",
"image/jpeg",
"image/jpg",
"image/webp",
]}
/>
</div>

{/* Error Message */}
{uploadError && (
<div className="flex items-start gap-1 sm:gap-1.5 md:gap-3 p-1.5 sm:p-2 md:p-4 bg-red-50 border border-red-300 rounded-md md:rounded-lg">
<p className="text-[9px] sm:text-[10px] md:text-sm text-red-800 leading-tight">
{uploadError}
</p>
</div>
)}
</div>

{/* Footer */}
<div className="flex justify-end p-2 sm:p-3 md:p-6">
<Button
onClick={handleSubmit}
disabled={!selectedFile || isUploading}
className="bg-blue-600 hover:bg-blue-700 text-white px-2 py-1 sm:px-3 sm:py-1.5 md:px-6 md:py-2 text-[10px] sm:text-xs md:text-base w-full sm:w-auto disabled:opacity-50 disabled:cursor-not-allowed"
>
{isUploading ? "Uploading..." : "Submit"}
</Button>
</div>
</div>
</div>
);
}
152 changes: 152 additions & 0 deletions components/features/events/upload-thumbnail-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"use client";

import { X } from "lucide-react";
import { Button } from "@/components/ui/button";
import UploadImage, { type UploadData } from "@/components/shared/upload-image";
import { useState } from "react";
import { deleteAsset } from "@/lib/api/services/assetService";

interface UploadThumbnailModalProps {
isOpen: boolean;
onClose: () => void;
onSubmit?: (uploadData: UploadData) => Promise<void>;
existingImageKey?: string; // Key of existing image to replace
}

export function UploadThumbnailModal({
isOpen,
onClose,
onSubmit,
existingImageKey,
}: UploadThumbnailModalProps) {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [uploadError, setUploadError] = useState<string | null>(null);
const [isUploading, setIsUploading] = useState(false);

if (!isOpen) return null;

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const handleFileSelect = (file: File, _preview: string) => {
console.log("Thumbnail file selected:", file.name);
setSelectedFile(file);
setUploadError(null);
};

const handleUploadError = (error: string) => {
console.error("Upload error:", error);
setUploadError(error);
};

const handleSubmit = async () => {
if (!selectedFile) return;

setIsUploading(true);
try {
// Delete old image if replacing
if (existingImageKey) {
try {
await deleteAsset(existingImageKey);
console.log("Old thumbnail deleted:", existingImageKey);
} catch (error) {
console.error("Failed to delete old thumbnail:", error);
// Continue with upload even if delete fails
}
}

// Upload new image
const { uploadAsset } = await import("@/lib/api/services/assetService");

const uploadResult = await uploadAsset(selectedFile, "event-thumbnails");

const uploadData: UploadData = {
url: uploadResult.url,
key: uploadResult.key,
bucket: uploadResult.bucket,
fileName: selectedFile.name,
fileSize: selectedFile.size,
fileType: selectedFile.type,
};

if (onSubmit) {
await onSubmit(uploadData);
}
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "Failed to upload thumbnail";
setUploadError(errorMessage);
console.error("Thumbnail upload failed:", error);
} finally {
setIsUploading(false);
}
};

const handleClose = () => {
setSelectedFile(null);
setUploadError(null);
setIsUploading(false);
onClose();
};

return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-2 sm:p-4">
<div className="bg-white rounded-lg shadow-xl w-full max-w-sm sm:max-w-lg md:max-w-2xl">
{/* Header */}
<div className="flex items-center justify-between p-2 sm:p-4 md:p-6 border-gray-200">
<h2 className="text-2xl sm:text-2xl md:text-2xl font-semibold text-gray-900">
Upload Event Thumbnail
</h2>
<button
onClick={handleClose}
className="text-gray-400 hover:text-gray-600 transition-colors flex-shrink-0"
>
<X className="h-3 w-3 sm:h-4 sm:w-4 md:h-5 md:w-5" />
</button>
</div>

{/* Content */}
<div className="p-2 sm:p-4 md:p-6 space-y-1 sm:space-y-2 md:space-y-4">
<p className="text-[10px] sm:text-xs md:text-base text-gray-700">
Select a file to upload and click the submit button
</p>

<div className="text-[9px] sm:text-[10px] md:text-sm text-gray-500 space-y-0 sm:space-y-0.5">
<p>Maximum file size: 10 MB</p>
<p>Accepted file types: png, jpg, jpeg</p>
</div>

{/* Upload Image Component */}
<div className="mt-1 sm:mt-2 md:mt-4">
<UploadImage
uploadType="asset"
folder="event-thumbnails"
immediateUpload={false}
onFileSelect={handleFileSelect}
onUploadError={handleUploadError}
acceptedTypes={["image/png", "image/jpeg", "image/jpg"]}
/>
</div>

{/* Error Message */}
{uploadError && (
<div className="flex items-start gap-1 sm:gap-1.5 md:gap-3 p-1.5 sm:p-2 md:p-4 bg-red-50 border border-red-300 rounded-md md:rounded-lg">
<p className="text-[9px] sm:text-[10px] md:text-sm text-red-800 leading-tight">
{uploadError}
</p>
</div>
)}
</div>

{/* Footer */}
<div className="flex justify-end p-2 sm:p-3 md:p-6 border-t border-gray-200">
<Button
onClick={handleSubmit}
disabled={!selectedFile || isUploading}
className="bg-blue-600 hover:bg-blue-700 text-white px-2 py-1 sm:px-3 sm:py-1.5 md:px-6 md:py-2 text-[10px] sm:text-xs md:text-base w-full sm:w-auto disabled:opacity-50 disabled:cursor-not-allowed"
>
{isUploading ? "Uploading..." : "Submit"}
</Button>
</div>
</div>
</div>
);
}
1 change: 0 additions & 1 deletion components/features/registrations/registration-columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ export const createRegistrationsColumns = ({
const registration = row.original;
return <div className="font-medium">{registration.fullName}</div>;
},
enableHiding: false,
},
{
accessorKey: "email",
Expand Down
Loading