event images added through drive link

This commit is contained in:
Vidhya 2026-07-16 23:37:44 +05:30
parent 897dbfe20d
commit 7daefa51be
8 changed files with 336 additions and 67 deletions

2
.env
View File

@ -1 +1 @@
NEXT_PUBLIC_API_BASE_URL=http://localhost:3001/api/
NEXT_PUBLIC_API_BASE_URL=http://localhost:3006/api/

View File

@ -0,0 +1,53 @@
import SignupForm from '@/components/auth/SignupForm';
import IconFacebookCircle from '@/components/icon/icon-facebook-circle';
import IconGoogle from '@/components/icon/icon-google';
import IconInstagram from '@/components/icon/icon-instagram';
import IconTwitter from '@/components/icon/icon-twitter';
import { Metadata } from 'next';
import Link from 'next/link';
import React from 'react';
export const metadata: Metadata = {
title: 'Sign Up',
};
const CoverRegister = () => {
return (
<div>
<div className="absolute inset-0">
<img src="/assets/images/auth/bg-gradient.png" alt="image" className="h-full w-full object-cover" />
</div>
<div className="relative flex min-h-screen items-center justify-center bg-[url(/assets/images/auth/map.png)] bg-cover bg-center bg-no-repeat px-6 py-10 dark:bg-[#060818] sm:px-16">
<img src="/assets/images/auth/coming-soon-object1.png" alt="image" className="absolute left-0 top-1/2 h-full max-h-[893px] -translate-y-1/2" />
<img src="/assets/images/auth/coming-soon-object2.png" alt="image" className="absolute left-24 top-0 h-40 md:left-[30%]" />
<img src="/assets/images/auth/coming-soon-object3.png" alt="image" className="absolute right-0 top-0 h-[300px]" />
<img src="/assets/images/auth/polygon-object.svg" alt="image" className="absolute bottom-0 end-[28%]" />
<div className="relative flex w-full max-w-[1502px] flex-col justify-between overflow-hidden rounded-md bg-white/60 backdrop-blur-lg dark:bg-black/50 lg:min-h-[758px] lg:flex-row lg:gap-10 xl:gap-0">
<div className="relative hidden w-full items-center justify-center bg-[linear-gradient(225deg,rgba(239,18,98,1)_0%,rgba(67,97,238,1)_100%)] p-5 lg:inline-flex lg:max-w-[835px] xl:-ms-28 ltr:xl:skew-x-[14deg] rtl:xl:skew-x-[-14deg]">
<div className="absolute inset-y-0 w-8 from-primary/10 via-transparent to-transparent ltr:-right-10 ltr:bg-gradient-to-r rtl:-left-10 rtl:bg-gradient-to-l xl:w-16 ltr:xl:-right-20 rtl:xl:-left-20"></div>
<div className="ltr:xl:-skew-x-[14deg] rtl:xl:skew-x-[14deg]">
<Link href="/" className="ms-10 block w-48 lg:w-72">
<img src="/assets/images/auth/logo-white.svg" alt="Logo" className="w-full" />
</Link>
<div className="mt-24 hidden w-full max-w-[430px] lg:block">
<img src="/assets/images/auth/login.svg" alt="Cover Image" className="w-full" />
</div>
</div>
</div>
<div className="relative flex w-full flex-col items-center justify-center gap-6 px-4 pb-16 pt-6 sm:px-6 lg:max-w-[667px]">
<div className="w-full max-w-[440px] lg:mt-16">
<div className="mb-10">
<h1 className="text-3xl font-extrabold uppercase !leading-snug text-primary md:text-4xl">Sign up</h1>
<p className="text-base font-bold leading-normal text-white-dark">Create an account to get started</p>
</div>
<SignupForm />
</div>
<p className="absolute bottom-6 w-full text-center dark:text-white">© {new Date().getFullYear()}.VRISTO All Rights Reserved.</p>
</div>
</div>
</div>
</div>
);
};
export default CoverRegister;

View File

@ -0,0 +1,17 @@
'use client'
import React from 'react';
import FullEventGallery from '@/components/gallery/FullEventGallery';
import { useSearchParams } from 'next/navigation';
const EventGalleryFullPage = () => {
const searchParams = useSearchParams();
const eventId = searchParams.get('eventid');
return (
<div className="py-8">
<FullEventGallery eventId={eventId} />
</div>
);
};
export default EventGalleryFullPage;

View File

@ -67,6 +67,7 @@ const LoginForm = () => {
if (response.data.success && response.data.data?.token) {
cookies.set('token', response.data.data.token, { path: '/' });
cookies.set('user', JSON.stringify(response.data.data.user), { path: '/' });
localStorage.setItem('token', response.data.data.token);
router.push('/');
}
} catch (error: any) {

View File

@ -0,0 +1,173 @@
'use client';
import IconLockDots from '@/components/icon/icon-lock-dots';
import IconMail from '@/components/icon/icon-mail';
import IconEye from '@/components/icon/icon-eye';
import IconUser from '@/components/icon/icon-user';
import { useRouter } from 'next/navigation';
import React, { useState } from 'react';
import axios from 'axios';
import Cookies from 'universal-cookie';
import { buildApiUrl } from '@/utils/BaseUrl.utils';
const cookies = new Cookies();
const SignupForm = () => {
const router = useRouter();
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
confirmPassword: '',
});
const [loading, setLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [errors, setErrors] = useState<{ name?: string; email?: string; password?: string; confirmPassword?: string }>({});
const [generalError, setGeneralError] = useState('');
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
setErrors((prev) => ({ ...prev, [name]: '' }));
setGeneralError('');
};
const validate = () => {
const newErrors: { name?: string; email?: string; password?: string; confirmPassword?: string } = {};
if (!formData.name.trim()) newErrors.name = 'Name is required';
if (!formData.email.trim()) newErrors.email = 'Email is required';
if (!formData.password.trim()) newErrors.password = 'Password is required';
if (formData.password !== formData.confirmPassword) newErrors.confirmPassword = 'Passwords do not match';
return newErrors;
};
const submitForm = async (e: React.FormEvent) => {
e.preventDefault();
const validation = validate();
if (Object.keys(validation).length > 0) {
setErrors(validation);
return;
}
setLoading(true);
setGeneralError('');
try {
const payload = { name: formData.name, email: formData.email, password: formData.password };
const response = await axios.post(buildApiUrl('auth/register'), payload);
if (response.data.success && response.data.data?.token) {
cookies.set('token', response.data.data.token, { path: '/' });
cookies.set('user', JSON.stringify(response.data.data.user), { path: '/' });
router.push('/');
return;
}
setGeneralError(response.data.message || 'Unable to register');
} catch (error: any) {
console.error('Signup error', error);
const serverMessage = error.response?.data?.message || error.response?.data?.error || error.message;
setGeneralError(serverMessage || 'Unable to register');
} finally {
setLoading(false);
}
};
return (
<form className="space-y-5 dark:text-white" onSubmit={submitForm}>
{generalError && <p className="text-red-500 text-sm mt-1">{generalError}</p>}
<div>
<label htmlFor="Name">Name</label>
<div className="relative text-white-dark">
<input
id="Name"
name="name"
type="text"
placeholder="Enter Name"
className="form-input ps-10 placeholder:text-white-dark"
value={formData.name}
onChange={handleChange}
/>
<span className="absolute start-4 top-1/2 -translate-y-1/2">
<IconUser fill={true} />
</span>
</div>
{errors.name && <p className="text-red-500 text-sm mt-1">{errors.name}</p>}
</div>
<div>
<label htmlFor="Email">Email</label>
<div className="relative text-white-dark">
<input
id="Email"
name="email"
type="email"
placeholder="Enter Email"
className="form-input ps-10 placeholder:text-white-dark"
value={formData.email}
onChange={handleChange}
/>
<span className="absolute start-4 top-1/2 -translate-y-1/2">
<IconMail fill={true} />
</span>
</div>
{errors.email && <p className="text-red-500 text-sm mt-1">{errors.email}</p>}
</div>
<div>
<label htmlFor="Password">Password</label>
<div className="relative text-white-dark">
<input
id="Password"
name="password"
type={showPassword ? 'text' : 'password'}
placeholder="Enter Password"
className="form-input ps-10 pe-10 placeholder:text-white-dark"
value={formData.password}
onChange={handleChange}
/>
<span className="absolute start-4 top-1/2 -translate-y-1/2">
<IconLockDots fill={true} />
</span>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className={`absolute end-4 top-1/2 -translate-y-1/2 hover:text-black dark:hover:text-white ${showPassword ? 'text-black dark:text-white' : ''}`}
>
<IconEye />
</button>
</div>
{errors.password && <p className="text-red-500 text-sm mt-1">{errors.password}</p>}
</div>
<div>
<label htmlFor="ConfirmPassword">Confirm Password</label>
<div className="relative text-white-dark">
<input
id="ConfirmPassword"
name="confirmPassword"
type={showPassword ? 'text' : 'password'}
placeholder="Confirm Password"
className="form-input ps-10 placeholder:text-white-dark"
value={formData.confirmPassword}
onChange={handleChange}
/>
<span className="absolute start-4 top-1/2 -translate-y-1/2">
<IconLockDots />
</span>
</div>
{errors.confirmPassword && <p className="text-red-500 text-sm mt-1">{errors.confirmPassword}</p>}
</div>
<button
type="submit"
className="btn btn-gradient !mt-6 w-full border-0 uppercase shadow-[0_10px_20px_-10px_rgba(67,97,238,0.44)]"
disabled={loading}
>
{loading ? 'Signing up...' : 'Sign up'}
</button>
</form>
);
};
export default SignupForm;

View File

@ -15,47 +15,44 @@ interface EditEventFormProps {
const CreateEventGalleryForm: React.FC<EditEventFormProps> = ({ eventId }) => {
const router = useRouter();
const [selectedImages, setSelectedImages] = useState<File[]>([]);
const [previewUrls, setPreviewUrls] = useState<string[]>([]);
const [linkInput, setLinkInput] = useState('');
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState<FormErrors>({});
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
const files = Array.from(e.target.files || []);
if (selectedImages.length + files.length > 50) {
setErrors({ images: 'You can only upload up to 50 images at a time' });
alert('You can only upload up to 50 images at a time');
return;
}
const validImages: File[] = [];
const previews: string[] = [];
files.forEach((file) => {
if (file.type.startsWith('image/')) {
validImages.push(file);
previews.push(URL.createObjectURL(file));
const parsedLinks = React.useMemo(() => {
return linkInput.split(/[\n,]+/).map(l => l.trim()).filter(l => l !== '').map(link => {
if (link.includes("drive.google.com")) {
const match = link.match(/\/d\/([a-zA-Z0-9_-]+)/) || link.match(/id=([a-zA-Z0-9_-]+)/);
if (match && match[1]) {
return `https://drive.google.com/thumbnail?id=${match[1]}&sz=w1000-h1000`;
}
}
return link;
});
}, [linkInput]);
setSelectedImages(prev => [...prev, ...validImages]);
setPreviewUrls(prev => [...prev, ...previews]);
setErrors(prev => ({ ...prev, images: '' })); // clear error if valid
const handleLinkInputChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
setLinkInput(e.target.value);
if (parsedLinks.length > 20) {
setErrors({ images: 'You can only add up to 20 images at a time' });
} else {
setErrors(prev => ({ ...prev, images: '' }));
}
};
const handleImageDelete = (index: number) => {
setSelectedImages(prev => prev.filter((_, i) => i !== index));
setPreviewUrls(prev => prev.filter((_, i) => i !== index));
const lines = linkInput.split(/[\n,]+/).map(l => l.trim()).filter(l => l !== '');
lines.splice(index, 1);
setLinkInput(lines.join('\n'));
};
const validateForm = (): boolean => {
const newErrors: FormErrors = {};
if (selectedImages.length === 0) {
newErrors.images = 'Please upload at least one image';
} else if (selectedImages.length > 50) {
newErrors.images = 'Only 50 images can be uploaded at a time';
if (parsedLinks.length === 0) {
newErrors.images = 'Please provide at least one image link';
} else if (parsedLinks.length > 20) {
newErrors.images = 'Only 20 images can be added at a time';
}
setErrors(newErrors);
@ -68,28 +65,24 @@ const CreateEventGalleryForm: React.FC<EditEventFormProps> = ({ eventId }) => {
setLoading(true);
try {
const formData = new FormData();
selectedImages.forEach((file, index) => {
formData.append(`files`, file);
});
const uploadRes = await axios.post(buildApiUrl('upload/multiple'), formData)
console.log("uploadres", uploadRes)
const uploadedUrls = uploadRes?.data?.data?.map((image: any) => image?.fullUrl) || [];
// Step 2: Prepare body for bulk save
// Step 1: Prepare body for bulk save
const body = {
eventid: Number(eventId),
imageurl: uploadedUrls
imageurl: parsedLinks
};
console.log("Sending body:", body);
// Step 3: Call bulk API
const token = localStorage.getItem('token');
await axios.post(
buildApiUrl('event-images/bulk'),
body
body,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
router.push(`/event-gallery?eventid=${eventId}`);
@ -107,29 +100,33 @@ const CreateEventGalleryForm: React.FC<EditEventFormProps> = ({ eventId }) => {
<form onSubmit={handleSubmit} className="max-w-4xl mx-auto p-6 bg-white rounded shadow-md">
<h2 className="text-xl font-bold mb-4">Upload Gallery Images</h2>
{/* File Input */}
{/* Link Input */}
<div className="mb-4">
<label htmlFor="gallery" className="block font-medium">Select Images</label>
<input
type="file"
<label htmlFor="gallery" className="block font-medium mb-1">Image Links (Google Drive etc.)</label>
<textarea
id="gallery"
accept="image/*"
multiple
onChange={handleFileChange}
rows={5}
placeholder="Paste your Google Drive links here, one per line..."
value={linkInput}
onChange={handleLinkInputChange}
className="w-full border rounded px-3 py-2"
/>
{errors.images && <p className="text-red-500 text-sm">{errors.images}</p>}
<p className="text-gray-500 text-xs mt-1">You can add up to 20 images.</p>
</div>
{/* Previews */}
{previewUrls.length > 0 && (
{parsedLinks.length > 0 && (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
{previewUrls.map((url, index) => (
{parsedLinks.map((url, index) => (
<div key={index} className="relative border rounded shadow overflow-hidden">
<img
src={url}
alt={`Preview ${index + 1}`}
className="w-full h-40 object-cover"
onError={(e) => {
(e.target as HTMLImageElement).src = "https://placehold.co/150x150?text=Invalid+Image+Link";
}}
/>
<button
type="button"

View File

@ -49,13 +49,23 @@ const ListOfEventsGallery: React.FC<EditEventFormProps> = ({ eventId }) => {
const getEventGallery = async () => {
try {
const res = await axios.get(buildApiUrl(`event-images/event/${eventId}`));
const formatted: GalleryImage[] = res.data?.data?.map((img: any) => ({
id: img.id,
src: img.imageurl,
title: img?.title || '',
description: img?.description || '',
})) || [];
const token = localStorage.getItem("token");
const res = await axios.get(buildApiUrl(`event-images/event/${eventId}`),
{
headers: {
Authorization: `Bearer ${token}`,
},
});
const formatted: GalleryImage[] = res.data?.data?.map((img: any) => {
console.log("Image URL:", img.imageurl);
return {
id: img.id,
src: img.imageurl,
title: img?.title || '',
description: img?.description || '',
};
}) || [];
setEventImages(formatted);
} catch (error) {
console.log('error', error);
@ -72,7 +82,14 @@ const ListOfEventsGallery: React.FC<EditEventFormProps> = ({ eventId }) => {
id: item.id,
sort_order: index,
}));
await axios.put(buildApiUrl('event-images/reorder'), { images });
const token = localStorage.getItem("token");
await axios.put(buildApiUrl('event-images/reorder'),
{ images },
{
headers: {
Authorization: `Bearer ${token}`,
},
});
Swal.fire({
title: 'Saved!',
text: 'Image order saved successfully.',
@ -113,7 +130,13 @@ const ListOfEventsGallery: React.FC<EditEventFormProps> = ({ eventId }) => {
}).then(async (result) => {
if (result.isConfirmed) {
try {
await axios.delete(buildApiUrl(`event-images/${item.id}`));
const token = localStorage.getItem("token");
await axios.delete(buildApiUrl(`event-images/${item.id}`),
{
headers: {
Authorization: `Bearer ${token}`,
},
});
Swal.fire({
title: 'Deleted!',
text: 'Your file has been deleted.',
@ -193,12 +216,16 @@ const ListOfEventsGallery: React.FC<EditEventFormProps> = ({ eventId }) => {
</button>
</div>
<img
src={item.src}
alt={`gallery-${index}`}
onDragStart={(e) => e.preventDefault()}
className="w-full h-auto"
/>
<img
src={item.src}
alt={`gallery-${index}`}
onDragStart={(e) => e.preventDefault()}
className="w-full h-64 object-cover rounded-md"
onError={(e) => {
(e.currentTarget as HTMLImageElement).src =
"https://placehold.co/400x300?text=Image+Not+Found";
}}
/>
</div>
))}
</ReactSortable>

View File

@ -7,14 +7,15 @@ export function middleware(req: NextRequest) {
// Allow public and framework paths without auth
const allowlist = [
'/login', // login page
'/signup', // signup page
'/auth/cover-register',
'/auth/cover-login',
];
if (
pathname.startsWith('/_next') ||
pathname.startsWith('/static') ||
pathname.startsWith('/assets') ||
allowlist.some((p) => pathname === p || pathname.startsWith(p + '/')) ||
// allow public files (images, css, etc.)
/\.(jpg|jpeg|png|svg|ico|css|js|map)$/.test(pathname)