From sheets to iCal
From Spreadsheet to Calendar: Building a Google Sheets to iCal App with Next.js
Hey there, fellow Next.js enthusiasts! ๐ Today, I'm excited to walk you through a fun little project I've been working on. We're going to build a simple app that reads events from a Google Sheet and turns them into an iCal feed, complete with a snazzy landing page. This is perfect for beginners looking to dip their toes into Next.js and API integration. Let's dive in!
The Big Idea ๐ก
Have you ever found yourself maintaining a list of events in a Google Sheet and wished you could easily sync it with your calendar? Well, that's exactly what we're going to solve! Our app will:
- Read event data from a Google Sheet
- Generate an iCal feed from this data
- Display the events on a simple, responsive landing page
Sounds cool, right? Let's break it down step by step.
Setting Up Our Next.js Project ๐
First things first, let's set up our Next.js project. If you haven't already, install Node.js and create a new Next.js app:
npx create-next-app@latest google-sheets-ical
cd google-sheets-ical
Choose the following options:
- TypeScript: Yes
- ESLint: Yes
- Tailwind CSS: Yes
- src/ directory: Yes
- App Router: Yes
The Layout: Our App's Foundation ๐๏ธ
Let's start with our layout.tsx
file. This sets up the basic structure for our app:
import "@/styles/globals.css";
import { GeistSans } from "geist/font/sans";
import { type Metadata } from "next";
export const metadata: Metadata = {
title: "Google Sheet to iCal",
description: "Take a sheet with date/title and make all day event ical link",
icons: [{ rel: "icon", url: "/favicon.ico" }],
};
export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en" className={`${GeistSans.variable}`}>
<body>{children}</body>
</html>
);
}
This layout uses the Geist Sans font and sets up some basic metadata for our app. Nothing too fancy, but it's a solid foundation!
The Home Page: Where the Magic Happens โจ
Now, let's create our main page in page.tsx
:
import { getEvents } from "@/lib/google-sheets";
import EventListCard from "@/components/EventList";
export default async function Home() {
const events = await getEvents();
return (
<main className="container mx-auto p-4">
<h1 className="mb-4 text-2xl font-bold">Google Sheets Calendar</h1>
<EventListCard events={events} />
<div className="mt-4">
<p>Subscribe to the iCal feed:</p>
<code className="rounded bg-gray-100 p-2">
{`${process.env.NEXT_PUBLIC_BASE_URL}/api/ical`}
</code>
</div>
</main>
);
}
This page does a few key things:
- It fetches events using our
getEvents
function (we'll create this soon!) - Displays the events using an
EventListCard
component - Shows a subscription link for the iCal feed
[Screenshot: Home page layout]
Displaying Events: The EventList Component ๐
Let's create a component to display our events. Create a new file called EventList.tsx
:
import type { Event } from "@/types";
import { Card } from "@/components/ui/card";
import { cn, isLessThanWeekAway, isPast } from "@/lib/utils";
interface EventListProps {
events: Event[];
}
export default function EventListCards({ events }: EventListProps) {
return (
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
{events.map((event, index) => (
<Card
key={index}
className={cn(
isLessThanWeekAway(event.date) && "bg-green-300",
isPast(event.date) && "hidden",
"border-muted rounded-md border p-4",
)}
>
<div className="flex flex-col gap-2">
<h3 className="text-lg font-semibold">{event.title}</h3>
<p className="text-muted-foreground text-sm">
{event.date.toLocaleDateString()}
</p>
</div>
</Card>
))}
</div>
);
}
This component creates a responsive grid of event cards. It also applies some conditional styling:
- Events less than a week away get a green background
- Past events are hidden
Pretty neat, huh?
[Screenshot: EventList component showing event cards]
The iCal Feed: Sharing is Caring ๐ค
Now for the piรจce de rรฉsistance - our iCal feed! Create a new file called route.ts
in the app/api/ical
directory:
import { NextResponse } from "next/server";
import { getEvents } from "@/lib/google-sheets";
import { generateICalFeed } from "@/lib/ical-generator";
export async function GET() {
try {
const events = await getEvents();
const icalFeed = generateICalFeed(events);
return new NextResponse(icalFeed, {
status: 200,
headers: {
"Content-Type": "text/calendar",
"Content-Disposition": 'attachment; filename="calendar.ics"',
},
});
} catch (error) {
console.error("Error generating iCal feed:", error);
return new NextResponse("Error generating iCal feed", { status: 500 });
}
}
This creates an API route that:
- Fetches events from our Google Sheet
- Generates an iCal feed
- Returns the feed as a downloadable file
The Missing Pieces ๐งฉ
Of course, we've glossed over a few important parts:
- The
getEvents
function that fetches data from Google Sheets - The
generateICalFeed
function that creates our iCal feed - Utility functions like
isLessThanWeekAway
andisPast
These would be great topics for follow-up posts! (Wink, wink ๐)
Wrapping Up ๐
And there you have it! We've built a simple yet powerful app that turns a Google Sheet into an iCal feed and displays events on a slick landing page. Here's what we've learned:
- Setting up a Next.js project with TypeScript and Tailwind CSS
- Creating a responsive layout with Next.js
- Fetching data from an external source (Google Sheets)
- Generating an iCal feed
- Creating API routes in Next.js
[Screenshot: Final app in action]
This project is just the tip of the iceberg. You could extend it by adding authentication, allowing users to add events through the UI, or even integrating with other calendar services.
I hope you found this tutorial helpful! Feel free to reach out if you have any questions or ideas for improvements. Happy coding! ๐๐จโ๐ป๐ฉโ๐ป