DEV Community

Cover image for ✨ Meet EP - A Bespoke Platform For Uploading Event Photos, Powered By Netlify
Omezibe Obioha
Omezibe Obioha

Posted on

✨ Meet EP - A Bespoke Platform For Uploading Event Photos, Powered By Netlify

This is a submission for the Netlify Dynamic Site Challenge: Visual Feast.


In a hurry? Check out the repo on GitHub or visit the app here. Thanks!


🛠 What I Built

👋🏽 Hi, everyone! Over the past 3 days, I worked on EP - a platform that lets you upload photos from events. I got the inspiration to build this from Vercel's Next.js Conf 2022 site & Canva's dashboard card.


😍 Demo

Demo


🏚 Platform Primitives

I used Netlify Image CDN to ensure seamless resizing, format conversion, and quality optimization, all tailored to fit specific dimensions. I created helper functions to help me generate src sets for images.

const generateNetlifySrcSet = (url: string, width: number, height: number, isAbsolute: boolean = true) => {
  const breakpoints = [2400, 1600, 1080, 720, 480, 320];
  const baseURL = process.env.NEXT_PUBLIC_NETLIFY_URL || process.env.URL;

  return breakpoints.map(breakpoint => {
    const scaledWidth = Math.min(breakpoint, width);
    const scaledHeight = Math.round((height / width) * scaledWidth);
    const path = isAbsolute ? url : `/${url}`;
    return `${baseURL}/.netlify/images?url=${path}&fit=cover&w=${scaledWidth}&h=${scaledHeight} ${scaledWidth}w`;
  }).join(', ');
};
Enter fullscreen mode Exit fullscreen mode

I also created another function to help me generate thumbnails in the blurhash format.

export const generateBlurhashThumbnailUrl = (photo: Photo, isAbsolute: boolean = true) => {
  const MAX_WIDTH = 150;
  const baseURL = process.env.NEXT_PUBLIC_NETLIFY_URL || process.env.URL;
  const path = isAbsolute ? photo.url : `/${photo.url}`;
  const scaledWidth = Math.min(photo.width, MAX_WIDTH);
  const scaledHeight = Math.round((scaledWidth * MAX_WIDTH) / MAX_WIDTH);

  const blurhashThumbnailUrl = `${baseURL}/.netlify/images?url=${path}&fit=cover&w=${scaledWidth}&h=${scaledHeight}&fm=blurhash`;
  return blurhashThumbnailUrl;
};
Enter fullscreen mode Exit fullscreen mode

Unfortunately, I didn't end up using it because it didn't work as I expected. I expected a blurred out version of an image with the blurhash format but I got a visible version. This made me opt to use a Netlify function to generate the blurhashes myself. Learn more about blurhash.

const sharp = require('sharp');
const { encode } = require('blurhash');
const { PrismaClient } = require('@prisma/client');

const prisma = new PrismaClient();

exports.handler = async (event, context) => {
    try {
        const requestBody = JSON.parse(event.body);
        const { url } = requestBody;

        // Convert image to buffer...
        const loadedImage = await fetch(url).then(response => response.blob()).then(blob => blob.arrayBuffer());

        const { data, info } = await sharp(Buffer.from(loadedImage))
            .ensureAlpha()
            .raw()
            .toBuffer({ resolveWithObject: true });

        const blurhash = encode(new Uint8ClampedArray(data), info.width, info.height, 4, 4);

        await prisma.photo.update({
            where: { url: url },
            data: { blurhash: blurhash }
        });

        return { statusCode: 200, body: JSON.stringify({ message: 'Blurhash updated successfully' } )};
    } catch (error) {
        console.log('Netlify Function Error :>>', error);
        return { statusCode: 500, body: JSON.stringify({ message: 'Internal Server Error', error }) };
    } finally {
        await prisma.$disconnect();
    }
};
Enter fullscreen mode Exit fullscreen mode

This worked pretty well until I another issue came up - timeout 😪. The function took about 30 seconds every time it ran, depending on the size of the image. As you may well know, functions on Netlify's free tier times out after 10 seconds. I then came up with a client-side alternative using Web Workers.

import { generateImageMetadata } from '#/lib/utils';
import { ImageMetadata, UploadResult, WorkerAction, WorkerResult, workersList } from '#/lib/types';

addEventListener('message', async (e: MessageEvent<string>) => {
  const { action, data } = e.data as unknown as WorkerAction<UploadResult>;
  if (action !== workersList.generateImageMetadata) {
    return;
  }

  const imageMetadata = await generateImageMetadata(`${data.preview}`);
  const result = {
    ...imageMetadata,
    url: data.url,
    thumbnailUrl: data.thumbnailUrl
  };

  const response = { data: result } as WorkerResult<ImageMetadata>;

  postMessage(response);
});

export {};
Enter fullscreen mode Exit fullscreen mode

With this, it took about 15-20 seconds. I had to settle for this as it was the best option I had. Still haven't figured out why it took so long on a lambda function ;(.

The site was hosted on Netlify & I added a custom subdomain (https://ep.omzi.dev).


💻 Code Repository

Link: https://github.com/omzi/event-photos

GitHub logo omzi / event-photos

👥 EP (Event Photos) is a customizable platform for uploading photos from your events. ⚡ Powered by Next.js, Edgestore & Netlify.

Event Photos Logo

👥 EP (Event Photos) is a customizable platform for uploading photos from your events.
⚡ Powered by Next.js, Edgestore & Netlify.

contributions welcome License: MIT


📜 About

EP (Event Photos) is a customizable platform for uploading photos from your events.

⚙️ Features

  • Image Upload to Edgestore
  • Bare-Bones Authentication
  • Blurhash Generation
  • Image Rendering using Netlify CDN
  • Adding & Editing Event Details
  • CRUD Access to Uploaded Files
  • Gallery Lightbox
  • User Feedback Mechanisms
  • Enhanced User Interface

🛠 Tech Stack

🚩 Prerequisites

Ensure that your system meets the following requirements:

Installation

Before proceeding, make sure your system satisfies the prerequisites mentioned above.

Firstly, clone the EP repository into your desired folder and navigate into it:

$ git
Enter fullscreen mode Exit fullscreen mode

🔗 Project Link

Link: https://ep.omzi.dev


⚗️ Test Credentials

Here's a test admin credential for testing things out:

Email Address: admin@event.com
Password: NsIG]s{]QokX

Visit here to login. Please be considerate of others & be careful what you upload there 🙏🏽. Daalu!


✨ Conclusion

Whew! This was shorter than I expected 👀. Thanks for making it this far! Feel free to give EP a try & let me know in the comment section below 😎.

Itadori Yuuji

I'm grateful to Netlify for organizing this hackathon. I learnt a couple of new stuff while working on EP 😁.

Have any constructive feedback for me? I’d love to know in the comments section below or via a Twitter DM (I’d prefer this). Connect with me on Twitter (@0xOmzi).

Mata ne ✌🏽

Top comments (2)

Collapse
 
philiphow profile image
Philip How

Nice work!

Collapse
 
omzi profile image
Omezibe Obioha

Thanks Phil!