DEV Community

Cover image for Picshaw - An image sharing app
Lena Jeremiah
Lena Jeremiah

Posted on

Picshaw - An image sharing app

This is a submission for the The Pinata Challenge

What I Built

Imagine attending an exciting event — like a developer conference—where you meet new people, take tons of photos and vidoes with them, and make lasting memories. But once the event wraps up, you realize you have no easy way to get all those great pictures.

Or consider a wedding ceremony: You know your guests captured beautiful moments throughout the day, but gathering all those photos means individually reaching out to each person. It is definitely not the way to go.

Picshaw offers a simple solution. As an event organizer, you can create a dedicated event folder on the platform, generate a shareable link, and invite your guests to upload the pictures they’ve taken. This way, everyone gets to relive the event from the unique perspectives of all attendees. No more missing moments, just memories shared effortlessly.

Picshaw Features

  • Creating Public and Private events. Public events are showcased on the “Discover Events” page, allowing anyone to browse and explore them. Private events, on the other hand, remain accessible only via a shareable link provided by the event organizer.

Image description

  • Upload Files Effortlessly Guests can easily upload their event photos to the designated event folder, making sure all memorable moments are gathered in one place.

Image description

  • Browse Uploaded Photos with Two Viewing Modes.
    Users can explore photos either in list mode, which snaps images into a feed similar to Instagram, or in grid mode, offering a gallery-style layout.
    Image description
    Image description

  • Easily Share Event Links
    Organizers can generate and share a unique link to invite guests to upload their pictures, streamlining the process of gathering photos.
    Image description

  • Discover Public Events
    Explore and browse all public events from the “Discover Events” page, opening up new ways to experience moments shared by others.

Image description

  • Seamless Authentication Users can sign in quickly and securely using Google sign-in or magic links, making the experience smooth and hassle-free.

Image description

  • Support for dark mode and light mode Image description

Demo

Try out Picshaw live: https://picshaw.vercel.app

My Code

The full code is available on GitHub. Feel free to star the repo and follow me! 😊

This is a Next.js project bootstrapped with create-next-app.

Getting Started

First, run the development server:

npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:3000 with your browser to see the result.

You can start editing the page by modifying app/page.tsx. The page auto-updates as you edit the file.

This project uses next/font to automatically optimize and load Geist, a new font family for Vercel.

Learn More

To learn more about Next.js, take a look at the following resources:

You can check out the Next.js GitHub repository - your feedback and contributions are welcome!

Deploy on Vercel

The easiest way to deploy your Next.js app is to use the Vercel Platform from the creators of Next.js.

Check out our Next.js deployment documentation for more…


Using Pinata

Integrating with Pinata was one of the smoothest parts of the project. Here’s a breakdown of how I implemented it:

1. Initialize the Pinata SDK

I set up a dedicated file, @/lib/pinata.ts, to manage the Pinata configuration:

import { PinataSDK } from "pinata"

export const pinata = new PinataSDK({
    pinataJwt: `${process.env.PINATA_JWT}`,
    pinataGateway: `${process.env.NEXT_PUBLIC_GATEWAY_URL}`
})
Enter fullscreen mode Exit fullscreen mode

2. In my API Route:

  • Ensured my user was logged in: I ensured the user was logged in before uploading.
const session = await auth()

if (!session || !session.user) {
   return respondError(new Error("User not authenticated"), "Failed to authenticate user", 401)
    }
Enter fullscreen mode Exit fullscreen mode
  • Validate the Request: I checked if the event exists and ensured the file upload is within limits.
const form = await request.formData()
const files = Array.from(form.values())
const eventSlug = params["event-slug"]

try {
    const event = await prisma.event.findUnique({ where: { slug: eventSlug } })

    if (!event) {
        return respondError(new Error("Event not found"), undefined, 404)
    }

    if (files.length > 50) {
        return respondError(new Error("Limit of 50 files per request"), "Too many files", 400)
    } else if (files.length === 0) {
        return respondError(new Error("At least one file is required"), "No files", 400)
    }
Enter fullscreen mode Exit fullscreen mode
  • Upload files to Pinata The next step is uploading the files.
const res = await Promise.all(files.filter(f => typeof f !== 'string').map(f => pinata.upload.file(f)))
        const successfulUploads = res.filter(r => r.status === "fulfilled")

        const uploadsWithPublicURL = await Promise.all(successfulUploads.map(async r => {
            const publicURL = await pinata.gateways.createSignedURL({ cid: r.value.cid, expires: 60 * 60 * 24 * 365 })
            return { ...r.value, publicURL }
        }))
Enter fullscreen mode Exit fullscreen mode

Note: A better implementation would include checks for failed uploads. This way, users are notified about failed files and can retry uploading them.

  • Save Upload Data to the Database Finally, I saved the upload information into the database using Prisma.
const dbUpload = await prisma.upload.create({
            data: {
                text: `${session.user.name} uploaded ${files.length} images for ${event?.name}`,
                ownerId: session.user?.id ?? "",
                eventId: event?.id ?? "",
                files: {
                    createMany: {
                        data: uploadsWithPublicURL.map(file => {
                            return {
                                ownerId: session.user?.id ?? "",
                                eventId: event?.id ?? "",
                                cid: file.id,
                                publicURL: file.publicURL,
                            }
                        })
                    }
                }
            }
        })

        return respondSuccess(dbUpload, "Uploaded successfully", 201)
Enter fullscreen mode Exit fullscreen mode

This flow ensures the event photos are uploaded securely and efficiently to Pinata, with successful uploads tracked and stored in the database for easy access later.

Rendering the uploaded images in the browser

1. Updating next.config.js

I first had to update the images field in the next.config.js file to allow NextJS optimize images from remote URLs.

/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    remotePatterns: [
      {
        hostname: "sapphire-obliged-canidae-626.mypinata.cloud",
        protocol: "https",
      },
    ],
  },
};

export default nextConfig;
Enter fullscreen mode Exit fullscreen mode

2. Fetching Event Data

In my client, I use a custom hook useFetch to fetch the details about the selected event

 const params = useParams();
  const eventSlug = params["event-slug"];
  const [viewMode, setViewMode] = React.useState<"grid" | "list">("grid");
  const router = useRouter();

  const {
    loading,
    data,
    trigger: getEventDetails,
  } = useFetch<void, GetUploadsResponse>(`/api/e/${eventSlug}`, undefined, {
    fetchOnRender: true,
  });
Enter fullscreen mode Exit fullscreen mode

3. Rendering the Images:

I render the retrieved images inside a responsive grrrrrrrrrrid

  <div className="grid grid-cols-3 md:grid-cols-4 gap-1 my-6">
        {photos.map((file) => (
          <Image
            key={file.id}
            src={file.publicURL}
            width={400}
            height={400}
            alt=""
            className="aspect-square object-cover"
          />
      ))}
 </div>
Enter fullscreen mode Exit fullscreen mode

Ideas for improvement

To further improve the app, it would be necessary to add some content moderation features. This would ensure that users don't post NSFW content on public groups. I started integrating with Google Cloud Vision but I didn't have enough time to complete it before the deadline.

Conclusion

Integrating Pinata’s Files API into Picshaw has greatly enhanced how images are uploaded and managed. Pinata provided seamless performance, and its intuitive API made implementation straightforward, allowing me to focus on building the core features of the app while trusting Pinata to handle file storage and delivery efficiently.

Overall, Pinata has been an essential tool in building a reliable and smooth image management system for Picshaw.

Also, I really enjoyed building Picshaw.

Follow me on X @jeremiahlena13

Top comments (0)