Skip to content

Commit

Permalink
Limit File Size Uploaded (#151)
Browse files Browse the repository at this point in the history
* first iteration of adding group image uploads

* first

* rm comments

* Update app/routes/groups_.new.tsx

Co-authored-by: Andre <andre.timo.landgraf@gmail.com>

* Update app/routes/groups_.new.tsx

Co-authored-by: Tony Dang <tony@tonydang.blog>

* changed to comments

* added try catch

* Co-authored-by: Jacob Baqleh <JacobBaqleh1@users.noreply.github.com>
Co-authored-by: Kwesi Batchelor <kwesibatchelor@users.noreply.github.com>

* rm console.log

* change

* edited to comments

* edited

---------

Co-authored-by: Andre Landgraf <andre.timo.landgraf@gmail.com>
Co-authored-by: Tony Dang <tony@tonydang.blog>
  • Loading branch information
3 people committed Jun 18, 2024
1 parent d53c454 commit 6262418
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 24 deletions.
51 changes: 50 additions & 1 deletion app/components/ui/forms.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,57 @@
import type { ButtonHTMLAttributes, InputHTMLAttributes, ReactNode, TextareaHTMLAttributes } from 'react';
import {
useState,
type ButtonHTMLAttributes,
type InputHTMLAttributes,
type ReactNode,
type TextareaHTMLAttributes,
} from 'react';
import { Link } from '@remix-run/react';

import clsx from 'clsx';

export const MAX_FILE_SIZE_MB = 8;
export const ACCEPTED_IMAGE_TYPES = 'image/jpeg, image/jpg, image/png, image/webp';

type ImageUploadProps = InputHTMLAttributes<HTMLInputElement> & {
label: ReactNode;
centerText?: boolean;
showLabel?: boolean;
maxFileSizeMB?: number;
fileAccept?: string;
};
export function ImageUpload({
label,
fileAccept = ACCEPTED_IMAGE_TYPES,
centerText = false,
showLabel = true,
maxFileSizeMB = MAX_FILE_SIZE_MB,
...props
}: ImageUploadProps) {
const [errorMessage, setErrorMessage] = useState<string>('');

function checkFileSize(event: React.ChangeEvent<HTMLInputElement>) {
const file = event.target.files?.[0];
if (!file) return;

// default 8MB
if (file.size > maxFileSizeMB * 1024 * 1024) {
setErrorMessage('File size is too large');
event.target.value = '';
return;
}

setErrorMessage('');
}

return (
<label className="flex flex-col w-full">
<span className={clsx({ 'bold pb-1': showLabel, 'sr-only': !showLabel })}>{label}</span>
<input {...props} type="file" accept={fileAccept} onChange={checkFileSize} />
<p className="text-sm text-red-500">{errorMessage}</p>
</label>
);
}

type InputProps = InputHTMLAttributes<HTMLInputElement> & {
label: ReactNode;
centerText?: boolean;
Expand Down
1 change: 0 additions & 1 deletion app/routes/groups_.$groupId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export async function loader({ params }: LoaderFunctionArgs) {
},
},
});
console.log('group', group);
return json({ group });
}

Expand Down
61 changes: 42 additions & 19 deletions app/routes/groups_.new.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import type { ActionFunctionArgs, LoaderFunctionArgs } from '@remix-run/node';
import { redirect } from '@remix-run/node';
import { Form, useActionData } from '@remix-run/react';
import { createClient } from '@supabase/supabase-js';
import { z } from 'zod';

import { db } from '~/modules/database/db.server';

import { Card } from '~/components/ui/containers';
import { Input, TextArea } from '~/components/ui/forms';
import { ACCEPTED_IMAGE_TYPES, ImageUpload, Input, MAX_FILE_SIZE_MB, TextArea } from '~/components/ui/forms';
import { Button } from '~/components/ui/button';

import { H1, H2 } from '~/components/ui/headers';
import { requireUserSession } from '~/modules/session/session.server';
import { badRequest } from '~/modules/response/response.server';
import { invokeBackgroundTask } from '~/modules/background-tasks/invoke';

export async function loader({ request }: LoaderFunctionArgs) {
Expand All @@ -20,30 +21,52 @@ export async function loader({ request }: LoaderFunctionArgs) {
export async function action({ request }: ActionFunctionArgs) {
const userSession = await requireUserSession(request);
const form = await request.formData();
const name = form.get('groupName');
const description = form.get('description');
const groupImage = form.get('groupImage');
const formObject = await z
.object({
groupName: z.string().min(1, 'Group name is required.'),
description: z.string().min(1, 'Description is required.'),
groupImage: z
.instanceof(File)
.refine(
(file) => !file || file?.size <= MAX_FILE_SIZE_MB * 1024 * 1024,
`File size can't exceed ${MAX_FILE_SIZE_MB}MB.`,
)
.refine(
(file) => ACCEPTED_IMAGE_TYPES.split(', ').includes(file.type),
`Only ${ACCEPTED_IMAGE_TYPES} are supported.`,
),
})
.safeParseAsync(Object.fromEntries(form));

if (typeof name !== 'string' || typeof description !== 'string') {
return { error: { message: `Form not submitted correctly.` } };
if (!formObject.success) {
return badRequest({
success: false,
error: { message: `the following fields contains errors: ${formObject.error}` },
});
}

let groupImageUrl = null;
const { groupName: name, description, groupImage } = formObject.data;

let groupImageUrl = '';
if (groupImage && groupImage instanceof File && groupImage.name) {
// Create a single supabase client for interacting with your database
if (!process.env.SUPABASE_SECRET) throw new Error('SUPABASE_SECRET is not defined');
const supabase = createClient('https://fzzehiiwadkmbvpouotf.supabase.co', process.env.SUPABASE_SECRET);
const uuid = crypto.randomUUID();
const { data, error } = await supabase.storage
.from('group-cover-images')
.upload(`${uuid}-${groupImage.name}`, groupImage, {
cacheControl: '3600',
upsert: false,
});
if (error) {
return { error: { message: `Error uploading image: ${error.message}` } };
try {
const { data, error } = await supabase.storage
.from('group-cover-images')
.upload(`${uuid}-${groupImage.name}`, groupImage, {
cacheControl: '3600',
upsert: false,
});
groupImageUrl = `https://fzzehiiwadkmbvpouotf.supabase.co/storage/v1/object/public/group-cover-images/${data?.path}`;
if (error) {
return { error: { message: `Error uploading image: ${error.message}` } };
}
} catch (error) {
return { error: { message: `Unexpected Error during Image Upload: ${error}` } };
}
groupImageUrl = `https://fzzehiiwadkmbvpouotf.supabase.co/storage/v1/object/public/group-cover-images/${data.path}`;
}

const new_group = await db.group.create({
Expand All @@ -57,7 +80,7 @@ export async function action({ request }: ActionFunctionArgs) {
});
}

return redirect(`/groups/`);
return redirect(`/groups/${new_group.id}`);
}

export default function GroupNew() {
Expand Down Expand Up @@ -100,7 +123,7 @@ export default function GroupNew() {
<TextArea label="Description:" name="description" rows={5} required />
</div>
<div className="flex-row pb-4">
<Input label="Attach Image" name="groupImage" type="file" />
<ImageUpload label="Attach Image" name="groupImage" />
</div>
<div className="flex-row justify-end">
<Button variant="warm" buttonStyle="fullyRounded">
Expand Down
6 changes: 3 additions & 3 deletions prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const events = [
date: new Date('2023-07-19T08:00:00'),
description:
'Join us for an evening of insightful talks on progressive web apps, serverless architecture, and the future of JavaScript frameworks.',
imgUrl: 'imgs/upc-events1.png',
imgUrl: '/imgs/upc-events1.png',
imgAlt: 'group of people in a meeting-1',
location: '123 Main St, San Francisco',
},
Expand All @@ -42,7 +42,7 @@ const events = [
date: new Date('2023-08-15T18:30:00'),
description:
'Break a sweat and achieve your fitness goals with our high-intensity workout session suitable for all fitness levels.',
imgUrl: 'imgs/upc-events2.png',
imgUrl: '/imgs/upc-events2.png',
imgAlt: 'group of people in a meeting-2',
location: '456 Elm St, Munich',
},
Expand All @@ -51,7 +51,7 @@ const events = [
date: new Date('2023-09-22T12:15:00'),
description:
'Participate in our annual tennis tournament and showcase your skills in exciting matches with players from the community.',
imgUrl: 'imgs/upc-events3.png',
imgUrl: '/imgs/upc-events3.png',
imgAlt: 'group of people in a meeting-3',
location: '789 Oak St, Istanbul',
},
Expand Down

0 comments on commit 6262418

Please sign in to comment.