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 #227 from EvanTechDev/feature/fix-500-error-for-unauthenticated-access

fix: prevent unauthenticated route 500 and restyle analytics/settings panels

authored by

Evan Huang and committed by
GitHub
9f5883a3 7ac7981b

+94 -133
+5 -8
components/app/analytics/build-info-card.tsx
··· 1 1 "use client"; 2 2 3 - import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; 4 3 import { translations, type Language } from "@/lib/i18n"; 5 4 import { useEffect, useMemo, useState } from "react"; 6 5 ··· 55 54 ); 56 55 57 56 return ( 58 - <Card> 59 - <CardHeader> 60 - <CardTitle>{t.buildInfoTitle}</CardTitle> 61 - </CardHeader> 62 - <CardContent className="space-y-3 text-sm"> 57 + <div className="rounded-lg border p-4 space-y-3"> 58 + <h2 className="text-base font-semibold">{t.buildInfoTitle}</h2> 59 + <div className="space-y-3 text-sm"> 63 60 <div className="flex items-center justify-between gap-4"> 64 61 <span className="text-muted-foreground">{t.buildInfoVersion}</span> 65 62 <span className="font-mono">{APP_VERSION}</span> ··· 72 69 <span className="text-muted-foreground">{t.buildInfoDeployment}</span> 73 70 <span>{t.buildInfoDeployedAgo.replace("{time}", deployedAgo)}</span> 74 71 </div> 75 - </CardContent> 76 - </Card> 72 + </div> 73 + </div> 77 74 ); 78 75 }
+1 -4
components/app/analytics/events-calendar.tsx
··· 22 22 SelectValue, 23 23 } from "@/components/ui/select"; 24 24 import { isZhLanguage, translations, useLanguage } from "@/lib/i18n"; 25 - import { Card, CardContent } from "@/components/ui/card"; 26 25 import React, { useEffect, useState } from "react"; 27 26 28 27 interface CalendarEvent { ··· 286 285 }; 287 286 288 287 return ( 289 - <Card className="overflow-hidden"> 290 - <CardContent className="pt-4">{renderCalendarGrid()}</CardContent> 291 - </Card> 288 + <div className="rounded-lg border p-4">{renderCalendarGrid()}</div> 292 289 ); 293 290 }; 294 291
+9 -16
components/app/analytics/import-export.tsx
··· 8 8 SelectValue, 9 9 } from "@/components/ui/select"; 10 10 import { 11 - Card, 12 - CardContent, 13 - CardDescription, 14 - CardHeader, 15 - CardTitle, 16 - } from "@/components/ui/card"; 17 - import { 18 11 Dialog, 19 12 DialogContent, 20 13 DialogHeader, ··· 737 730 }; 738 731 739 732 return ( 740 - <Card className="w-full"> 741 - <CardHeader> 733 + <div className="w-full rounded-lg border p-4 space-y-6"> 734 + <div> 742 735 <div className="flex justify-between items-center"> 743 736 <div> 744 - <CardTitle>{t.importExport}</CardTitle> 745 - <CardDescription> 737 + <h2 className="text-base font-semibold">{t.importExport}</h2> 738 + <p className="text-sm text-muted-foreground"> 746 739 {t.importExportDesc || 747 740 "Exchange data with other calendar applications"} 748 - </CardDescription> 741 + </p> 749 742 </div> 750 743 <div className="flex space-x-2"> 751 744 <Button variant="outline" onClick={() => setImportDialogOpen(true)}> ··· 758 751 </Button> 759 752 </div> 760 753 </div> 761 - </CardHeader> 754 + </div> 762 755 763 - <CardContent> 756 + <div> 764 757 <div className="space-y-6"> 765 758 <Alert> 766 759 <AlertCircle className="h-4 w-4" /> ··· 778 771 </ul> 779 772 </div> 780 773 </div> 781 - </CardContent> 774 + </div> 782 775 783 776 {} 784 777 <Dialog open={importDialogOpen} onOpenChange={setImportDialogOpen}> ··· 1027 1020 </div> 1028 1021 </DialogContent> 1029 1022 </Dialog> 1030 - </Card> 1023 + </div> 1031 1024 ); 1032 1025 }
+10 -15
components/app/analytics/share-management.tsx
··· 8 8 AlertDialogCancel, 9 9 AlertDialogAction, 10 10 } from "@/components/ui/alert-dialog"; 11 - import { 12 - Card, 13 - CardHeader, 14 - CardTitle, 15 - CardDescription, 16 - CardContent, 17 - } from "@/components/ui/card"; 18 11 import { Copy, ExternalLink, Lock, Trash2 } from "lucide-react"; 19 12 import { translations, useLanguage } from "@/lib/i18n"; 20 13 import { Button } from "@/components/ui/button"; ··· 146 139 }; 147 140 148 141 return ( 149 - <Card className="w-full"> 150 - <CardHeader> 142 + <div className="w-full rounded-lg border p-4 space-y-6"> 143 + <div> 151 144 <div className="flex justify-between items-center"> 152 145 <div> 153 - <CardTitle>{t.shareManagementTitle}</CardTitle> 154 - <CardDescription>{t.shareManagementDescription}</CardDescription> 146 + <h2 className="text-base font-semibold">{t.shareManagementTitle}</h2> 147 + <p className="text-sm text-muted-foreground"> 148 + {t.shareManagementDescription} 149 + </p> 155 150 </div> 156 151 </div> 157 - </CardHeader> 152 + </div> 158 153 159 - <CardContent> 154 + <div> 160 155 {sharedEvents.length === 0 ? ( 161 156 <div className="text-center py-8 text-muted-foreground"> 162 157 {t.noShares} ··· 219 214 ))} 220 215 </div> 221 216 )} 222 - </CardContent> 217 + </div> 223 218 224 219 <AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}> 225 220 <AlertDialogContent> ··· 308 303 </AlertDialogFooter> 309 304 </AlertDialogContent> 310 305 </AlertDialog> 311 - </Card> 306 + </div> 312 307 ); 313 308 }
+68 -89
components/app/analytics/time-analytics.tsx
··· 19 19 SelectTrigger, 20 20 SelectValue, 21 21 } from "@/components/ui/select"; 22 - import { 23 - Card, 24 - CardContent, 25 - CardDescription, 26 - CardHeader, 27 - CardTitle, 28 - } from "@/components/ui/card"; 29 22 import { analyzeTimeUsage, type TimeAnalytics } from "@/lib/time-analytics"; 30 23 import type { CalendarCategory } from "../sidebar/sidebar"; 31 24 import { useLocalStorage } from "@/hooks/useLocalStorage"; ··· 168 161 })(); 169 162 170 163 return ( 171 - <Card className="w-full"> 172 - <CardHeader> 164 + <div className="w-full rounded-lg border p-4 space-y-6"> 165 + <div> 173 166 <div className="flex items-center justify-between gap-3"> 174 167 <div> 175 - <CardTitle>{t.timeAnalytics}</CardTitle> 176 - <CardDescription> 168 + <h2 className="text-base font-semibold">{t.timeAnalytics}</h2> 169 + <p className="text-sm text-muted-foreground"> 177 170 {t.timeAnalyticsDesc || "Analyze how you spend your time"} 178 - </CardDescription> 171 + </p> 179 172 </div> 180 173 <Select 181 174 value={timeRange} ··· 195 188 </SelectContent> 196 189 </Select> 197 190 </div> 198 - </CardHeader> 199 - <CardContent> 191 + </div> 192 + <div> 200 193 <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> 201 194 <div> 202 195 <h3 className="text-lg font-medium mb-2">{t.timeDistribution}</h3> ··· 252 245 </div> 253 246 </div> 254 247 <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-6"> 255 - <Card> 256 - <CardContent className="pt-6"> 257 - <div className="text-center"> 258 - <h3 className="text-lg font-medium">{t.totalEvents}</h3> 259 - <p className="text-3xl font-bold mt-2"> 260 - {analytics.totalEvents} 261 - </p> 262 - </div> 263 - </CardContent> 264 - </Card> 265 - <Card> 266 - <CardContent className="pt-6"> 267 - <div className="text-center"> 268 - <h3 className="text-lg font-medium">{t.mostProductiveDay}</h3> 269 - <p className="text-3xl font-bold mt-2"> 270 - {analytics.mostProductiveDay 271 - ? new Date(analytics.mostProductiveDay).toLocaleDateString() 272 - : t.noData} 273 - </p> 274 - </div> 275 - </CardContent> 276 - </Card> 277 - <Card> 278 - <CardContent className="pt-6"> 279 - <div className="text-center"> 280 - <h3 className="text-lg font-medium">{t.mostProductiveHour}</h3> 281 - <p className="text-3xl font-bold mt-2"> 282 - {analytics.mostProductiveHour !== undefined 283 - ? `${analytics.mostProductiveHour}:00` 284 - : t.noData} 285 - </p> 286 - </div> 287 - </CardContent> 288 - </Card> 248 + <div className="rounded-lg border p-4"> 249 + <div className="text-center"> 250 + <h3 className="text-lg font-medium">{t.totalEvents}</h3> 251 + <p className="text-3xl font-bold mt-2">{analytics.totalEvents}</p> 252 + </div> 253 + </div> 254 + <div className="rounded-lg border p-4"> 255 + <div className="text-center"> 256 + <h3 className="text-lg font-medium">{t.mostProductiveDay}</h3> 257 + <p className="text-3xl font-bold mt-2"> 258 + {analytics.mostProductiveDay 259 + ? new Date(analytics.mostProductiveDay).toLocaleDateString() 260 + : t.noData} 261 + </p> 262 + </div> 263 + </div> 264 + <div className="rounded-lg border p-4"> 265 + <div className="text-center"> 266 + <h3 className="text-lg font-medium">{t.mostProductiveHour}</h3> 267 + <p className="text-3xl font-bold mt-2"> 268 + {analytics.mostProductiveHour !== undefined 269 + ? `${analytics.mostProductiveHour}:00` 270 + : t.noData} 271 + </p> 272 + </div> 273 + </div> 289 274 </div> 290 275 291 276 <div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-4"> 292 - <Card> 293 - <CardContent className="pt-6"> 294 - <div className="text-center"> 295 - <h3 className="text-lg font-medium"> 296 - {t.totalHours || "Total Hours"} 297 - </h3> 298 - <p className="text-3xl font-bold mt-2"> 299 - {analytics.totalHours.toFixed(1)}h 300 - </p> 301 - </div> 302 - </CardContent> 303 - </Card> 304 - <Card> 305 - <CardContent className="pt-6"> 306 - <div className="text-center"> 307 - <h3 className="text-lg font-medium"> 308 - {t.averageEventDuration || "Average Event Duration"} 309 - </h3> 310 - <p className="text-3xl font-bold mt-2"> 311 - {analytics.averageEventDuration.toFixed(1)}h 312 - </p> 313 - </div> 314 - </CardContent> 315 - </Card> 316 - <Card> 317 - <CardContent className="pt-6"> 318 - <div className="text-center"> 319 - <h3 className="text-lg font-medium"> 320 - {t.busiestCategory || "Busiest Category"} 321 - </h3> 322 - <p className="text-2xl font-bold mt-2"> 323 - {busiestCategoryName || t.noData} 324 - </p> 325 - <p className="text-sm text-muted-foreground mt-1"> 326 - {t.activeDays || "Active Days"}: {analytics.activeDays} 327 - </p> 328 - </div> 329 - </CardContent> 330 - </Card> 277 + <div className="rounded-lg border p-4"> 278 + <div className="text-center"> 279 + <h3 className="text-lg font-medium"> 280 + {t.totalHours || "Total Hours"} 281 + </h3> 282 + <p className="text-3xl font-bold mt-2"> 283 + {analytics.totalHours.toFixed(1)}h 284 + </p> 285 + </div> 286 + </div> 287 + <div className="rounded-lg border p-4"> 288 + <div className="text-center"> 289 + <h3 className="text-lg font-medium"> 290 + {t.averageEventDuration || "Average Event Duration"} 291 + </h3> 292 + <p className="text-3xl font-bold mt-2"> 293 + {analytics.averageEventDuration.toFixed(1)}h 294 + </p> 295 + </div> 296 + </div> 297 + <div className="rounded-lg border p-4"> 298 + <div className="text-center"> 299 + <h3 className="text-lg font-medium"> 300 + {t.busiestCategory || "Busiest Category"} 301 + </h3> 302 + <p className="text-2xl font-bold mt-2"> 303 + {busiestCategoryName || t.noData} 304 + </p> 305 + <p className="text-sm text-muted-foreground mt-1"> 306 + {t.activeDays || "Active Days"}: {analytics.activeDays} 307 + </p> 308 + </div> 309 + </div> 331 310 </div> 332 - </CardContent> 333 - </Card> 311 + </div> 312 + </div> 334 313 ); 335 314 }
+1 -1
proxy.ts
··· 18 18 19 19 export default clerkMiddleware(async (auth, req) => { 20 20 if (!isPublicRoute(req)) { 21 - await auth.protect({ unauthenticatedUrl: "/sign-in" }) 21 + await auth.protect() 22 22 } 23 23 }) 24 24