One Calendar is a privacy-first calendar web app built with Next.js. It has modern security features, including e2ee, password-protected sharing, and self-destructing share links ๐Ÿ“… calendar.xyehr.cn
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #38 from Dev-Huang1/dev

refactor: optimize directory structure and update README

authored by

Evan Huang and committed by
GitHub
3a66a761 2a4d61e8

+132 -103
+94 -3
README.md
··· 1 - ![Banner](https://calendar.xyehr.cn/Banner.jpg) 1 + <div align="center"> 2 + <img src="public/icon.svg" width="72"> 3 + </div> 4 + 5 + # One Calendar 6 + 7 + [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/Dev-Huang1/One-Calendar&env=NEXT_PUBLIC_BASE_URL,NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,CLERK_SECRET_KEY,OPENWEATHER_API_KEY,BLOB_READ_WRITE_TOKEN&project-name=one-calendar&repo-name=one-calendar) 8 + 9 + A beautifully minimal open-source calendar app to plan your week and life. 10 + 11 + ## What is One Calendar? 12 + 13 + **One Calendar** is a privacy-first, weekly-focused, open-source calendar app, designed to help individuals and teams plan, focus, and stay in sync. 14 + 15 + > Without *One Calendar*, your schedule is scattered. With it, your week feels intentional. 16 + 17 + ## Why One Calendar? 18 + 19 + Most calendar tools are cluttered, over-engineered, or locked behind paywalls. One Calendar aims to be: 20 + 21 + - ๐Ÿง  **Simple and Intuitive** โ€“ Weekly view first, with minimal distractions. 22 + - ๐Ÿ•น **Interactive & Smooth** โ€“ Drag, drop, right-click, and edit with ease. 23 + - ๐Ÿ” **Private & Local** โ€“ Your data is yours. Export, backup, and control. 24 + - โ˜๏ธ **Cloud Sync** โ€“ Optional sync via Supabase and Vercel Blob. 25 + - ๐ŸŒ **Multi-Account Google Sync** โ€“ Easily sync with Google Calendar. 26 + - ๐ŸŒ **International** โ€“ Automatically adapts to your language (English / ไธญๆ–‡). 27 + - ๐Ÿงฑ **Customizable** โ€“ Tailor themes, default view, and integrations. 28 + 29 + ## Tech Stack 30 + 31 + - **Frontend**: Next.js 14, Tailwind CSS, shadcn/ui, TypeScript 32 + - **Auth**: Clerk 33 + - **Storage**: LocalStorage, Vercel Blob 34 + - **Weather**: OpenWeather API 35 + 36 + ## Preview 37 + 38 + ![Home](public/Home.jpg) 39 + ![App](/public/Banner.jpg) 40 + 41 + ## Quick Start 42 + 43 + ```bash 44 + # Clone the repo 45 + git clone https://github.com/Dev-Huang1/One-Calendar.git 46 + cd One-Calendar 47 + 48 + # Install dependencies 49 + bun install 2 50 51 + # Start the app 52 + bun run dev 53 + ``` 3 54 4 - # One Calendar 55 + Then visit `http://localhost:3000` 5 56 6 - One Calendar is a calendar web app that uses React + Vercel/blob for storage. It has rich features: address book, notes, bookmarks, to-do lists and analysis features! ๐Ÿ“…๐Ÿ—“๏ธ 57 + ## Environment Variables 58 + 59 + Copy `.env.example` to `.env.local` and fill in: 60 + 61 + ```env 62 + NEXT_PUBLIC_BASE_URL=your_url 63 + NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your-clerk-publishable-key 64 + CLERK_SECRET_KEY=your-clerk-secret 65 + OPENWEATHER_API_KEY=your-open-weather-api-key 66 + BLOB_READ_WRITE_TOKEN=your-vercel-blob-token 67 + ``` 68 + 69 + ## Star History 70 + 71 + [![Star History Chart](https://api.star-history.com/svg?repos=Dev-Huang1/One-Calendar&type=Date)](https://www.star-history.com/#Dev-Huang1/One-Calendar&Date) 72 + 73 + ## The Team 74 + 75 + Brought to you by a small team of makers who love clean tools and open-source. 76 + 77 + Check out our [contributors](https://github.com/Dev-Huang1/One-Calendar/graphs/contributors) โค๏ธ 78 + 79 + ## Acknowledgements 80 + 81 + This project wouldn't be possible without these awesome services: 82 + 83 + <div style="display: flex; justify-content: center;"> 84 + <a href="https://vercel.com" style="text-decoration: none;"> 85 + <img src="https://github.com/user-attachments/assets/5107d47f-7ce9-425a-8e24-77c322205bd4" alt="Vercel" width="96"/> 86 + </a> 87 + <a href="https://clerk.com" style="text-decoration: none;"> 88 + <img src="https://github.com/user-attachments/assets/6f9fa5d7-e0c2-4c14-aef9-e39bd0465e23" alt="Clerk" width="96"/> 89 + </a> 90 + <a href="https://openweathermap.org" style="text-decoration: none;"> 91 + <img src="https://github.com/user-attachments/assets/d07ed7a1-c374-45f5-90fd-17c3de2a9098" alt="OpenWeather API" width="96"/> 92 + </a> 93 + </div> 94 + 95 + ## License 96 + 97 + [GPL 3.0 Licensed](./LICENSE). Copyright ยฉ Tech-Art-Studio 2025.
app/about/page.tsx app/(app)/about/page.tsx
app/app/page.tsx app/(app)/app/page.tsx
+2 -2
app/layout.tsx
··· 2 2 import type { Metadata } from "next" 3 3 import "./globals.css" 4 4 import { Toaster } from "@/components/ui/toaster" 5 - import { CalendarProvider } from "@/contexts/CalendarContext" 5 + import { CalendarProvider } from "@/components/context/CalendarContext" 6 6 import { Analytics } from '@vercel/analytics/next'; 7 7 import { SpeedInsights } from "@vercel/speed-insights/next" 8 8 import { ClerkProvider } from '@clerk/nextjs' 9 9 import { enUS } from '@clerk/localizations' 10 10 import { GeistSans } from "geist/font/sans" 11 - import { ThemeProvider } from "@/components/theme-provider" 11 + import { ThemeProvider } from "@/components/context/theme-provider" 12 12 13 13 export const metadata: Metadata = { 14 14 title: "One Calendar",
+1 -1
app/page.tsx
··· 1 - import LandingPage from "@/components/Landing" 1 + import LandingPage from "@/components/home/Landing" 2 2 3 3 export default function Landing() { 4 4 return <LandingPage />
app/reset-password/page.tsx app/(auth)/reset-password/page.tsx
app/share/[id]/layout.tsx app/(app)/share/[id]/layout.tsx
+1 -1
app/share/[id]/page.tsx app/(app)/share/[id]/page.tsx
··· 10 10 import { useLanguage } from "@/lib/i18n" 11 11 import { translations } from "@/lib/i18n" 12 12 import { Button } from "@/components/ui/button" 13 - import { useCalendar } from "@/contexts/CalendarContext" 13 + import { useCalendar } from "@/components/context/CalendarContext" 14 14 import { motion } from "framer-motion" 15 15 import { Badge } from "@/components/ui/badge" 16 16
app/sign-in/page.tsx app/(auth)/sign-in/page.tsx
app/sign-in/sso-callback/page.tsx app/(auth)/sign-in/sso-callback/page.tsx
app/sign-up/page.tsx app/(auth)/sign-up/page.tsx
app/sign-up/sso-callback/page.tsx app/(auth)/sign-up/sso-callback/page.tsx
components/AnalyticsGuide.tsx components/analytics/AnalyticsGuide.tsx
+2 -2
components/AnalyticsView.tsx components/analytics/AnalyticsView.tsx
··· 4 4 import TimeAnalyticsComponent from "./TimeAnalytics" 5 5 import ImportExport from "./ImportExport" 6 6 import AnalyticsGuide from "./AnalyticsGuide" 7 - import type { CalendarEvent } from "./Calendar" 8 - import { useCalendar } from "@/contexts/CalendarContext" 7 + import type { CalendarEvent } from "../Calendar" 8 + import { useCalendar } from "@/components/context/CalendarContext" 9 9 import { useLanguage } from "@/hooks/useLanguage" 10 10 import { translations } from "@/lib/i18n" 11 11 import ShareManagement from "./ShareManagement"
components/BookmarkPanel.tsx components/sidebar/BookmarkPanel.tsx
+15 -15
components/Calendar.tsx
··· 6 6 import { Input } from "@/components/ui/input" 7 7 import { ChevronLeft, ChevronRight, Search, PanelLeft } from 'lucide-react' 8 8 import { addDays, subDays } from "date-fns" 9 - import Sidebar from "./Sidebar" 10 - import DayView from "./DayView" 11 - import WeekView from "./WeekView" 12 - import MonthView from "./MonthView" 13 - import EventDialog from "./EventDialog" 14 - import Settings from "./Settings" 9 + import Sidebar from "@/components/sidebar/Sidebar" 10 + import DayView from "@/components/view/DayView" 11 + import WeekView from "@/components/view/WeekView" 12 + import MonthView from "@/components/view/MonthView" 13 + import EventDialog from "@/components/event/EventDialog" 14 + import Settings from "@/components/home/Settings" 15 15 import { translations, useLanguage } from "@/lib/i18n" 16 16 import { checkPendingNotifications, clearAllNotificationTimers, type NOTIFICATION_SOUNDS } from "@/lib/notifications" 17 - import EventPreview from "./EventPreview" 17 + import EventPreview from "@/components/event/EventPreview" 18 18 import { useLocalStorage } from "@/hooks/useLocalStorage" 19 - import { useCalendar } from "@/contexts/CalendarContext" 20 - import EventUrlHandler from "./EventUrlHandler" 21 - import RightSidebar from "./RightSidebar" 22 - import AnalyticsView from "./AnalyticsView" 19 + import { useCalendar } from "@/components/context/CalendarContext" 20 + import EventUrlHandler from "@/components/event/EventUrlHandler" 21 + import RightSidebar from "@/components/sidebar/RightSidebar" 22 + import AnalyticsView from "@/components/analytics/AnalyticsView" 23 23 import { ScrollArea } from "@/components/ui/scroll-area" 24 - import UserProfileButton from "./UserProfileButton" 24 + import UserProfileButton from "@/components/home/UserProfileButton" 25 25 import { cn } from "@/lib/utils" 26 - import Weather from "./Weather" 27 - import DailyToast from "./DailyToast" 28 - import QuickStartGuide from "./QuickStartGuide" 26 + import Weather from "@/components/home/Weather" 27 + import DailyToast from "@/components/home/DailyToast" 28 + import QuickStartGuide from "@/components/home/QuickStartGuide" 29 29 30 30 type ViewType = "day" | "week" | "month" | "analytics" 31 31
components/DailyToast.tsx components/home/DailyToast.tsx
+1 -1
components/DayView.tsx components/view/DayView.tsx
··· 12 12 import { format, isSameDay, isWithinInterval, endOfDay, startOfDay } from "date-fns" 13 13 import { zhCN, enUS } from "date-fns/locale" 14 14 import { cn } from "@/lib/utils" 15 - import type { CalendarEvent } from "./Calendar" 15 + import type { CalendarEvent } from "../Calendar" 16 16 import type { Language } from "@/lib/i18n" 17 17 18 18 interface DayViewProps {
+2 -2
components/EventDialog.tsx components/event/EventDialog.tsx
··· 11 11 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" 12 12 import { Textarea } from "@/components/ui/textarea" 13 13 import { format } from "date-fns" 14 - import type { CalendarEvent } from "./Calendar" 14 + import type { CalendarEvent } from "../Calendar" 15 15 import { cn } from "@/lib/utils" 16 16 import { translations, type Language } from "@/lib/i18n" 17 - import { useCalendar } from "@/contexts/CalendarContext" 17 + import { useCalendar } from "@/components/context/CalendarContext" 18 18 19 19 const colorOptions = [ 20 20 { value: "bg-blue-500", label: "Blue" },
+2 -2
components/EventPreview.tsx components/event/EventPreview.tsx
··· 20 20 import { Button } from "@/components/ui/button" 21 21 import { zhCN, enUS } from "date-fns/locale" 22 22 import { format } from "date-fns" 23 - import type { CalendarEvent } from "./Calendar" 23 + import type { CalendarEvent } from "../Calendar" 24 24 import type { Language } from "@/lib/i18n" 25 25 import { translations } from "@/lib/i18n" 26 26 import { cn } from "@/lib/utils" 27 - import { useCalendar } from "@/contexts/CalendarContext" 27 + import { useCalendar } from "@/components/context/CalendarContext" 28 28 import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog" 29 29 import { Input } from "@/components/ui/input" 30 30 import { Label } from "@/components/ui/label"
+1 -1
components/EventUrlHandler.tsx components/event/EventUrlHandler.tsx
··· 13 13 AlertDialogTitle, 14 14 } from "@/components/ui/alert-dialog" 15 15 import { addDays } from "date-fns" 16 - import { useCalendar } from "@/contexts/CalendarContext" 16 + import { useCalendar } from "@/components/context/CalendarContext" 17 17 import { translations, useLanguage } from "@/lib/i18n" 18 18 import { toast } from "@/components/ui/use-toast" 19 19
-57
components/FeatureGuide.tsx
··· 1 - "use client" 2 - 3 - import { useState } from "react" 4 - import { 5 - Dialog, 6 - DialogContent, 7 - DialogHeader, 8 - DialogTitle, 9 - DialogDescription, 10 - DialogFooter, 11 - } from "@/components/ui/dialog" 12 - import { Button } from "@/components/ui/button" 13 - import { HelpCircle } from "lucide-react" 14 - 15 - interface FeatureGuideProps { 16 - title: string 17 - description: string 18 - steps: { 19 - title: string 20 - description: string 21 - }[] 22 - } 23 - 24 - export default function FeatureGuide({ title, description, steps }: FeatureGuideProps) { 25 - const [open, setOpen] = useState(false) 26 - 27 - return ( 28 - <> 29 - <Button variant="ghost" size="icon" onClick={() => setOpen(true)} className="absolute right-2 top-2 z-10"> 30 - <HelpCircle className="h-5 w-5" /> 31 - </Button> 32 - 33 - <Dialog open={open} onOpenChange={setOpen}> 34 - <DialogContent className="max-w-md"> 35 - <DialogHeader> 36 - <DialogTitle>{title}</DialogTitle> 37 - <DialogDescription>{description}</DialogDescription> 38 - </DialogHeader> 39 - <div className="space-y-4"> 40 - {steps.map((step, index) => ( 41 - <div key={index}> 42 - <h3 className="font-medium"> 43 - ๆญฅ้ชค {index + 1}: {step.title} 44 - </h3> 45 - <p className="text-sm text-muted-foreground">{step.description}</p> 46 - </div> 47 - ))} 48 - </div> 49 - <DialogFooter> 50 - <Button onClick={() => setOpen(false)}>ๆ˜Ž็™ฝไบ†</Button> 51 - </DialogFooter> 52 - </DialogContent> 53 - </Dialog> 54 - </> 55 - ) 56 - } 57 -
+1 -1
components/ImportExport.tsx components/analytics/ImportExport.tsx
··· 11 11 import { Label } from "@/components/ui/label" 12 12 import { toast } from "@/components/ui/use-toast" 13 13 import { Download, Upload, CalendarIcon, ExternalLink, AlertCircle } from "lucide-react" 14 - import type { CalendarEvent } from "./Calendar" 14 + import type { CalendarEvent } from "../Calendar" 15 15 import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" 16 16 import { translations, useLanguage } from "@/lib/i18n" // ไฝฟ็”จuseLanguage่€Œไธๆ˜ฏuseLocalStorage 17 17
components/Landing.tsx components/home/Landing.tsx
+2 -2
components/MiniCalendarSheet.tsx components/sidebar/MiniCalendarSheet.tsx
··· 9 9 import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet" 10 10 import { cn } from "@/lib/utils" 11 11 import { useLanguage } from "@/lib/i18n" 12 - import type { CalendarEvent } from "./Calendar" 13 - import { useCalendar } from "@/contexts/CalendarContext" 12 + import type { CalendarEvent } from "../Calendar" 13 + import { useCalendar } from "@/components/context/CalendarContext" 14 14 15 15 interface MiniCalendarSheetProps { 16 16 open: boolean
+1 -1
components/MonthView.tsx components/view/MonthView.tsx
··· 2 2 3 3 import { format, startOfMonth, endOfMonth, eachDayOfInterval, isSameMonth, isSameDay } from "date-fns" 4 4 import { cn } from "@/lib/utils" 5 - import type { CalendarEvent } from "./Calendar" 5 + import type { CalendarEvent } from "../Calendar" 6 6 7 7 type Language = "en" | "zh" 8 8
components/QuickStartGuide.tsx components/home/QuickStartGuide.tsx
components/RightSidebar.tsx components/sidebar/RightSidebar.tsx
components/Settings.tsx components/home/Settings.tsx
components/ShareManagement.tsx components/analytics/ShareManagement.tsx
+1 -1
components/Sidebar.tsx components/sidebar/Sidebar.tsx
··· 10 10 import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog" 11 11 import { Label } from "@/components/ui/label" 12 12 import { translations } from "@/lib/i18n" 13 - import { useCalendar } from "@/contexts/CalendarContext" 13 + import { useCalendar } from "@/components/context/CalendarContext" 14 14 import { CalendarIcon } from "lucide-react" 15 15 16 16 interface SidebarProps {
+2 -2
components/TimeAnalytics.tsx components/analytics/TimeAnalytics.tsx
··· 11 11 import { Label } from "@/components/ui/label" 12 12 import { Plus, X } from "lucide-react" 13 13 import { cn } from "@/lib/utils" 14 - import type { CalendarEvent } from "./Calendar" 15 - import type { CalendarCategory } from "./Sidebar" 14 + import type { CalendarEvent } from "../Calendar" 15 + import type { CalendarCategory } from "../Sidebar" 16 16 import { translations, useLanguage } from "@/lib/i18n" 17 17 18 18 interface TimeAnalyticsProps {
+2 -3
components/UserProfileButton.tsx components/home/UserProfileButton.tsx
··· 14 14 import { Label } from "@/components/ui/label" 15 15 import { toast } from "@/components/ui/use-toast" 16 16 import { validatePassword, generateIdFromPassword } from "@/lib/backup-utils" 17 - import { useCalendar } from "@/contexts/CalendarContext" 17 + import { useCalendar } from "@/components/context/CalendarContext" 18 18 import { translations, useLanguage } from "@/lib/i18n" 19 19 import { Checkbox } from "@/components/ui/checkbox" 20 - import { useUser, SignIn, SignUp, SignOutButton } from "@clerk/nextjs" 20 + import { useUser, SignIn, SignUp, SignOutButton, UserProfile, useClerk } from "@clerk/nextjs" 21 21 import { useRouter } from "next/navigation" 22 22 import Image from "next/image" 23 - import { UserProfile, useClerk } from "@clerk/nextjs"; 24 23 25 24 export default function UserProfileButton() { 26 25 const [language] = useLanguage()
components/Weather.tsx components/home/Weather.tsx
components/WeekView.tsx components/view/WeekView.tsx
components/theme-provider.tsx components/context/theme-provider.tsx
-1
contexts/CalendarContext.tsx components/context/CalendarContext.tsx
··· 114 114 } 115 115 116 116 export const useCalendarContext = useCalendar 117 -
+1 -5
package.json
··· 1 1 { 2 2 "name": "one-calendar", 3 - "version": "1.8.2", 3 + "version": "1.8.5", 4 4 "scripts": { 5 5 "dev": "next dev", 6 6 "build": "next build", ··· 9 9 }, 10 10 "dependencies": { 11 11 "@hookform/resolvers": "^3.9.1", 12 - "@radix-ui/react-accordion": "^1.2.2", 13 12 "@radix-ui/react-alert-dialog": "^1.1.7", 14 - "@radix-ui/react-aspect-ratio": "^1.1.1", 15 13 "@radix-ui/react-avatar": "^1.1.2", 16 14 "@radix-ui/react-checkbox": "^1.1.3", 17 15 "@radix-ui/react-collapsible": "^1.1.2", ··· 23 21 "@radix-ui/react-menubar": "^1.1.4", 24 22 "@radix-ui/react-navigation-menu": "^1.2.3", 25 23 "@radix-ui/react-popover": "latest", 26 - "@radix-ui/react-progress": "^1.1.1", 27 - "@radix-ui/react-radio-group": "^1.2.2", 28 24 "@radix-ui/react-scroll-area": "^1.2.2", 29 25 "@radix-ui/react-select": "^2.1.4", 30 26 "@radix-ui/react-separator": "^1.1.1",
public/Home.jpg

This is a binary file and will not be displayed.

+1
public/icon.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#0066ff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-calendar-icon lucide-calendar"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/></svg>