Wedding Memories
This is a submission for the The Pinata Challenge
What I Built
Wedding Memories is an app designed to capture, share, and cherish every unforgettable moment from your special day! It allows wedding guests to easily upload photos, creating a collaborative album that captures the essence of the celebration. By using Pinata's Files API, the app provides a seamless experience for secure and efficient media uploads and storage.
Demo
Check out the live version of the app here: Wedding Memories
Screenshots:
- Upload Section - A user-friendly interface for selecting and uploading media files:
- Gallery View - Displays all uploaded memories in a responsive layout:
- Mobile-Optimized Design - Ensures smooth user experience across all devices:
- Download Options - Guests can download their favourite moments to keep memories forever.
My Code
You can explore the full code for this project on GitHub:
More Details
Wedding Memories leverages Pinata’s Files API to build secure and efficient file uploads. Below is a brief breakdown of the integration:
File Uploads: Guests can easily upload images directly from their devices. This is accomplished using the
pinata.upload.file
, which uses JWT-based authentication to ensure secure file handling. Once uploaded, the files are securely stored, and their unique identifiers (CIDs) are generated for retrieval.Local Previews Before Upload: To enhance the guest experience, wedding memories generate local previews of images before uploading them. This feature allows guests to confirm their selections and make adjustments, ensuring only their desired image is submitted.
After successful uploads, the files are rendered using the file API. This guarantees rapid access to the content, providing guests with a seamless experience when viewing shared memories.
- Download Feature: Guests can now download their favourite photos directly from the app, making it easy to save cherished memories to their devices. The app includes an API route that facilitates the secure downloading of files.
API Integration Breakdown:
The building includes several key API routes, each designed to handle specific functionalities:
- File Upload Endpoint
This accepts file uploads and stores them using Pinata’s upload function. It then generates a signed URL for easy access to the uploaded file.
import { NextResponse, NextRequest } from "next/server";
import { pinata } from "../../../../utils/config";
export async function POST(request: NextRequest) {
try {
const data = await request.formData();
const file: File | null = data.get("file") as unknown as File;
const uploadData = await pinata.upload.file(file)
const url = await pinata.gateways.createSignedURL({
cid: uploadData.cid,
expires: 3600,
});
return NextResponse.json(url, { status: 200 });
} catch (e) {
console.log(e);
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 }
);
}
}
- API Key Generation
(/api/key/route.ts)
:
Creates a temporary API key with permissions to pin files to IPFS.
/* eslint-disable @typescript-eslint/no-unused-vars */
import { NextResponse } from "next/server";
import { pinata } from "../../../../utils/config";
export const dynamic = "force-dynamic";
export async function GET() {
try {
const uuid = crypto.randomUUID();
const keyData = await pinata.keys.create({
keyName: uuid.toString(),
permissions: {
endpoints: {
pinning: {
pinFileToIPFS: true,
},
},
},
maxUses: 1,
})
return NextResponse.json(keyData, { status: 200 });
} catch (error) {
console.log(error);
return NextResponse.json({ text: "Error creating API Key:" }, { status: 500 });
}
}
- List Files
(/api/listfiles/route.ts)
:
Retrieves a list of uploaded files from Pinata, allowing users to view all shared content in the gallery
import { NextResponse } from "next/server";
import { env } from "process";
export const dynamic = "force-dynamic";
export async function GET() {
try {
const options: RequestInit = {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NEXT_PUBLIC_PINATA_JWT}`,
},
cache: 'no-cache'
};
const response = await fetch('https://api.pinata.cloud/v3/files', options);
if (!response.ok) {
return NextResponse.json({ text: "Error listing files" }, { status: response.status });
}
const { data } = await response.json();
return NextResponse.json(data, { status: 200 });
} catch (error) {
console.log(error, "Error listing files");
return NextResponse.json({ text: "Error listing files" }, { status: 500 });
}
}
- Image Proxy
(/api/proxy/route.ts)
:
Acts as a proxy for fetching images from external sources, ensuring users can easily access and download their images.
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const imageUrl = searchParams.get('url'); // Get the image URL from the query string
if (!imageUrl) {
return NextResponse.json({ error: 'Image URL is required' }, { status: 400 });
}
try {
const response = await fetch(imageUrl);
if (!response.ok) {
return NextResponse.json({ error: 'Failed to fetch image' }, { status: response.status });
}
const contentType = response.headers.get('content-type') || 'application/octet-stream';
const imageBuffer = await response.arrayBuffer();
return new NextResponse(imageBuffer, {
headers: {
'Content-Type': contentType,
'Content-Disposition': 'attachment; filename="downloaded_image"',
},
});
} catch (error) {
console.error('Error fetching image:', error);
return NextResponse.json({ error: 'Error fetching image' }, { status: 500 });
}
}
- Signed URL Creation
(/api/sign/route.ts)
:
Generates signed URLs for uploaded files.
import { type NextRequest, NextResponse } from "next/server";
import { pinata } from "../../../../utils/config";
export const dynamic = "force-dynamic";
export async function POST(req: NextRequest) {
try {
const data = await req.json();
const mimeType = data.mime_type;
let url;
if (mimeType === 'video/mp4') {
url = await pinata.gateways.createSignedURL({
cid: data.cid,
expires: 7776000,
})
} else {
url = await pinata.gateways.createSignedURL({
cid: data.cid,
expires: 7776000,
}).optimizeImage({
width: 300,
height: 300,
format: "webp",
fit: "contain",
quality: 90,
dpr: 2,
sharpen: 1,
});
}
return NextResponse.json(url, { status: 200 });
} catch (error) {
console.log(error);
return NextResponse.json({ text: "Error creating signed URL:" }, { status: 500 });
}
}
The app is built with:
Pinata: File API
Frontend: React, Next.js, Framer Motion
Styling: Tailwind CSS for responsive, beautiful layouts.
Hosting: Amplify for deployment.
Conclusion
Weddings are joyous occasions filled with love, laughter, and countless memorable moments. Capturing these precious memories is essential, and with the Wedding Memories app, it's now easier than ever!
This application allows every guest to take lovely pictures on the event day and share them seamlessly, ensuring that no moment is missed and everyone has access to the collective memories of the celebration.
The app harnesses the power of Pinata's Files API to provide a secure and efficient way for guests to upload and share their photos and videos, creating a collaborative album that beautifully encapsulates the essence of the day.
For more details on how the app integrates with Pinata and to explore its capabilities, please refer to the Pinata documentation.
Thank you.
Top comments (23)
Wow... great project, am definitely pulling this, I hope you are open for contribution?
thanks for sharing
Thank you for your kind words and interest in the project! Yes, contributions are welcome.
Great stuff
Wow! This was well explained, and the project was properly built.
Thank you, ERIN! Glad you liked it.
Awesome stuff… really thoughtful. I know a product that can leverage your app, the api precisely. Check Ahavaplan…
Great ro hear! I'm going to check them out. Thanks @westace10
How to ensure data security?
Thank you @xiaowo. So, this first phase is for the users to use their personal access token. But, the next phase will be the integration of Authentication & Authorization, Secure and Personal Dashboard Access, and Data Encryption.
This is a nice project. Well done!
Thank you Andrew
This project is impressive, great job!
Thank you, Ronke! Glad you liked it.
Weldone! This is a nice project, but how do you intend to manage NextJS Optimized Image Billing?
Thanks for liking the project! I initially deployed it on Next.js, but after exhausting the free tier for Image Optimization, I switched to Amplify. The app is now live on Amplify, but I'll revisit Next.js later to investigate the usage issue.
Great project!👍
I like what Pinata is doing
Looks great 🙌
Thanks @a11rew