Calendar Event Scheduler — Next.js, React, TypeScript, TailwindCSS, Framer Motion Fundamental Project 12
A modern, responsive web app that merges a monthly calendar with a timed event / to-do list. Pick future (or today’s) dates, set hours and minutes, write a short note, then add, edit, or delete events. The UI uses a dark glassmorphism style, subtle Framer Motion transitions, and Sonner toasts for feedback. All event data lives in the browser via localStorage—there is no server database and no REST API in this repo.
Live demo: https://taskmate-calendar.vercel.app/
- Project summary
- Features & functionality
- Technology stack
- Dependencies & what they do
- How the app works (architecture)
- Routes & API
- Project structure
- Environment variables (
.env) - Installation & how to run
- NPM scripts
- User walkthrough
- Components & reuse in other projects
- Data model & persistence
- Code snippets (learning examples)
- Learning keywords
- Extending the project (backend, API, auth)
- Conclusion
- License
- Happy coding! 🎉
This repository is an educational / portfolio project aimed at beginners and intermediate developers who want to see a realistic Next.js App Router setup: Server Components at the entry (app/page.tsx), Client Components for all interactivity, React Context to avoid prop drilling, custom hooks for calendar math and event CRUD, and careful handling of localStorage with useSyncExternalStore to avoid hydration mismatches between server HTML and the browser.
You can clone, run locally, deploy to Vercel, or copy individual components (e.g. RippleButton, ConfirmDialog) into other apps as long as you respect the MIT License.
| Area | What you get |
|---|---|
| Calendar | Month/year navigation, grid of days, highlights for “today”, click a valid day to open the event form. |
| Events | Create, edit, delete; each event has id, date, time (HH:mm), and text. |
| Validation | Past-only days are not used for new events (today and future allowed). |
| List | Scrollable schedule list beside (or below) the calendar; edit/delete with confirmation-style dialogs. |
| Persistence | JSON in localStorage under key calendar-todo-events; survives refresh until cleared. |
| UX | Toasts for add/edit/delete/errors; ripple buttons; animated containers; loading skeleton inside Suspense. |
| SEO | Root layout.tsx exports rich metadata (title, description, Open Graph, etc.); optional env overrides. |
- Next.js 15 — App Router,
app/directory, static generation for/. - React 19 — Client components, hooks, Context.
- TypeScript — Typed components, hooks, and shared types in
src/types/. - Tailwind CSS v4 — Utility classes + PostCSS pipeline.
- Framer Motion — Layout and entrance animations.
- Lucide React — Icons (calendar, arrows, edit, trash, etc.).
- Sonner — Toast notifications (configured in
layout.tsx). - class-variance-authority (CVA) + clsx + tailwind-merge — Composable, conflict-safe class names (see
src/lib/utils.ts).
Runtime (dependencies)
next— Framework: routing, SSR/SSG, image/font helpers,MetadataAPI.react/react-dom— UI library and DOM renderer.framer-motion—motioncomponents,AnimatePresence, transitions.lucide-react— Tree-shakeable SVG icons as React components.sonner—<Toaster />+toast()/toast.custom()for feedback.class-variance-authority— Define variant props for UI components (e.g. button styles).clsx— Conditionally join class strings.tailwind-merge— Merge Tailwind classes without duplicate conflicts (cn()helper).
Development (devDependencies)
typescript— Type-checking.tailwindcss,@tailwindcss/postcss,postcss— CSS pipeline.eslint,eslint-config-next,@eslint/eslintrc— Linting (flat config ineslint.config.mjs).@types/*— Type definitions for Node and React.
Example: merging classes safely in your own project:
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// Usage: cn("px-4", isActive && "bg-sky-500", className)src/app/page.tsx(Server Component) rendersHomePage.HomePagewraps the UI inEventProviderandSuspense(fallback:CalendarSkeleton).EventProvider(src/context/EventContext.tsx) callsuseCalendar()anduseEvents(month, year), then exposes one object via Context.CalendarAppcomposesCalendarHeader,CalendarGrid,EventList,EventPopup—all consumeuseEventContext().useEventssyncs the event list withlocalStorageusinguseSyncExternalStore, so the server and the first hydration pass agree on an empty list; the browser then shows stored events after hydration.
app/page.tsx (Server)
└── HomePage (Client)
└── EventProvider
└── CalendarApp
├── CalendarHeader / CalendarGrid / EventPopup
└── EventList| Route | File | Role |
|---|---|---|
/ |
src/app/page.tsx |
Home — calendar + events UI. |
/_not-found |
Next.js built-in | 404 page. |
API routes: None. This app does not define app/api/*. There is no backend in the repository: no database, no serverless functions for CRUD. If you need multi-device sync or accounts, you would add something like Next.js Route Handlers, tRPC, or an external REST/GraphQL API (see Extending the project).
calendar-todo/
├── LICENSE
├── README.md
├── components.json # shadcn-style component config (if used)
├── eslint.config.mjs
├── next.config.ts
├── package.json
├── postcss.config.mjs
├── tsconfig.json
├── vercel.json
├── .env.example # Optional SEO / title overrides (documented below)
├── public/
│ └── favicon.ico
└── src/
├── app/
│ ├── globals.css
│ ├── layout.tsx # Fonts, metadata, Sonner Toaster
│ └── page.tsx # Server entry → HomePage
├── components/
│ ├── calendar/ # Feature UI
│ │ ├── CalendarApp.tsx
│ │ ├── CalendarGrid.tsx
│ │ ├── CalendarHeader.tsx
│ │ ├── EventList.tsx
│ │ └── EventPopup.tsx
│ ├── pages/
│ │ └── HomePage.tsx
│ └── shared/ # Reusable pieces
│ ├── AnimatedContainer.tsx
│ ├── CalendarSkeleton.tsx
│ ├── ConfirmDialog.tsx
│ ├── CTAShineButton.tsx
│ ├── EventStatusBadge.tsx
│ └── RippleButton.tsx
├── context/
│ └── EventContext.tsx
├── hooks/
│ ├── useCalendar.ts
│ ├── useEvents.ts
│ └── useRipple.ts
├── lib/
│ ├── constants.ts
│ └── utils.ts
└── types/
├── global.d.ts
└── index.tsYou do not need any .env file to run or build this project. Everything works with sensible defaults (see src/app/layout.tsx).
Optional variables (for branding and SEO only):
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_APP_TITLE |
Browser tab title + Open Graph / Twitter title override. |
NEXT_PUBLIC_SITE_URL |
Canonical origin for metadata (metadataBase), e.g. https://taskmate-calendar.vercel.app. |
How to set them
- Copy
.env.example→.env.local(recommended for Next.js secrets;.env.localis gitignored). - Adjust values; restart
npm run dev.
Production (e.g. Vercel): add the same keys in the project Environment Variables dashboard if you want a custom title or domain for metadata.
Security note: Only NEXT_PUBLIC_* variables are exposed to the browser. This project does not use secret server-only keys.
Prerequisites: Node.js 18.18+ (or 20+ recommended) and npm.
# Clone your fork or this repository (update the URL if yours differs)
git clone https://github.com/arnobt78/Calendar-To-Do--ReactVite.git
cd <repository-folder> # e.g. calendar-todo
# Install
npm install
# Optional: copy env template
cp .env.example .env.local
# Development (Turbopack)
npm run devOpen http://localhost:3000.
# Production build + local serve
npm run build
npm start| Script | Command | Purpose |
|---|---|---|
dev |
next dev --turbopack |
Fast local development. |
build |
next build |
Optimized production build. |
start |
next start |
Run production server (after build). |
lint |
next lint && eslint . --max-warnings 0 |
Next + ESLint checks. |
lint:fix |
next lint --fix && eslint . --fix … |
Auto-fix where possible. |
- Open the app — You see the calendar card and the Schedules panel.
- Change month — Use the header arrows (
prevMonth/nextMonth). - Add an event — Click a today or future day → popup opens → set time and text → save → toast confirms; list updates.
- Edit — Pencil icon on a row → confirm if prompted → adjust in popup → save.
- Delete — Trash icon → confirm → event removed and toast shown.
- Refresh — Data remains (from
localStorage) unless you clear site data.
Many pieces are portable if you copy files and install the same dependencies (or strip Motion/Ripple if you prefer).
| Piece | Path | Reuse idea |
|---|---|---|
cn utility |
src/lib/utils.ts |
Drop into any Tailwind + React app. |
RippleButton |
src/components/shared/RippleButton.tsx |
Material-style tap feedback on buttons. |
ConfirmDialog |
src/components/shared/ConfirmDialog.tsx |
Accessible confirm/cancel for destructive actions. |
AnimatedContainer |
src/components/shared/AnimatedContainer.tsx |
Wrapper for directional framer-motion entrance. |
EventProvider + hooks |
context/, hooks/ |
Template for Context + custom hooks pattern. |
Minimal reuse pattern: wrap your tree with providers and use hooks:
import { EventProvider } from "@/context/EventContext";
import CalendarApp from "@/components/calendar/CalendarApp";
export default function Page() {
return (
<EventProvider>
<CalendarApp />
</EventProvider>
);
}To use only UI without this app’s state, copy the presentational parts and replace useEventContext() with your own props or a different store (Zustand, Redux, etc.).
Type (from src/types/index.ts):
export interface CalendarEvent {
id: number;
date: Date;
time: string; // "HH:mm"
text: string;
}Storage: localStorage key calendar-todo-events. Dates are serialized as ISO strings in JSON and rehydrated as Date in useEvents.
Limitations (learning points):
- Data is per browser, per origin (not synced across devices).
- Private browsing may clear storage when the session ends.
- No encryption—do not store sensitive secrets in event text.
Month navigation (conceptually):
const prevMonth = () => {
setCurrentMonth((prev) => {
if (prev === 0) {
setCurrentYear((y) => y - 1);
return 11;
}
return prev - 1;
});
};Functional update when saving events (avoids stale state with external store):
setEvents((prev) => {
const next = [...prev, newEvent];
next.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
return next;
});Next.js App Router · React Server vs Client Components · React Context API · Custom hooks (useCalendar, useEvents) · useSyncExternalStore · Hydration · TypeScript interfaces · Tailwind CSS · Framer Motion · localStorage persistence · Component composition · Accessibility (labels, dialogs) · ESLint flat config · Vercel deployment
Ideas for learners:
- Add
app/api/events/route.tswith GET/POST backed by a database (Prisma + PostgreSQL, Supabase, etc.). - Replace
localStoragewith SWR or TanStack Query fetching your API. - Add NextAuth.js or Clerk for user-scoped calendars.
- Generate
opengraph-image.tsxfor richer social previews.
Calendar To-Do / TaskMate Calendar is a self-contained front-end that teaches modern React patterns inside Next.js 15 without forcing a backend. Use it as a starter, a portfolio piece, or a reference for Context, hooks, and persistence-aware SSR. Extend it when you are ready for real APIs and databases.
This project is licensed under the MIT License. Feel free to use, modify, and distribute the code as per the terms of the license.
This is an open-source project — feel free to use, enhance, and extend this project further!
If you have any questions or want to share your work, reach out via GitHub or my portfolio at https://www.arnobmahmud.com.
Enjoy building and learning! 🚀
Thank you! 😊
