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 #39 from Dev-Huang1/dev

Fix: dark theme display

authored by

Evan Huang and committed by
GitHub
1d1eaebb 57ce69be

+592 -39
+14 -13
app/(app)/about/page.tsx
··· 45 45 const t = content[lang] 46 46 47 47 return ( 48 - <div className="min-h-screen flex flex-col text-black"> 48 + <div className="min-h-screen flex flex-col text-black dark:text-white"> 49 49 <main className="max-w-3xl mx-auto px-6 py-24"> 50 50 <div className="fixed -z-10 inset-0 overflow-hidden"> 51 51 <div className="absolute left-0 bottom-0 h-[300px] w-[300px] rounded-full bg-blue-400 opacity-20 blur-[80px]" /> ··· 56 56 <div className="space-y-5 text-left"> 57 57 <h2 className="text-2xl font-semibold">{t.heading}</h2> 58 58 {t.paragraphs.map((p, i) => ( 59 - <p key={i} className="text-lg text-gray-700 leading-relaxed"> 59 + <p key={i} className="text-lg text-gray-700 leading-relaxed dark:text-white"> 60 60 {p} 61 61 </p> 62 62 ))} ··· 66 66 <section className="text-center px-6 py-16"> 67 67 <div className="max-w-3xl mx-auto space-y-4"> 68 68 <h2 className="text-xl font-medium">{t.contact}</h2> 69 - <p className="text-gray-600">{t.cta}</p> 69 + <p className="text-gray-600 dark:text-white">{t.cta}</p> 70 70 <div className="flex justify-center gap-4 pt-4"> 71 71 <Link 72 72 href="https://github.com/Dev-Huang1/One-Calendar" ··· 87 87 <div className="max-w-6xl mx-auto flex flex-col md:flex-row justify-between items-center gap-4"> 88 88 <p>&copy; 2025 One Calendar. All rights reserved.</p> 89 89 <div className="flex gap-4"> 90 - <Link href="/" className="hover:underline"> 91 - {lang === "zh" ? "首页" : "Home"} 92 - </Link> 93 - <Link 94 - href="https://github.com/Dev-Huang1/One-Calendar" 95 - target="_blank" 96 - className="flex items-center gap-1 hover:underline" 97 - > 98 - <GithubIcon className="w-4 h-4" /> GitHub 99 - </Link> 90 + <a href="/about" className="hover:underline">About</a> 91 + <a href="/privacy" className="hover:underline">Privacy</a> 92 + <a href="/terms" className="hover:underline">Terms</a> 93 + <a href="https://github.com/Dev-Huang1/One-Calendar" target="_blank" rel="noopener" className="flex items-center gap-1 hover:underline"> 94 + <GithubIcon className="w-4 h-4" /> 95 + </a> 96 + <a href="https://x.com/One__Cal" target="_blank" className="flex items-center gap-1 hover:underline"> 97 + <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="16" height="16" viewBox="0 0 32 32"> 98 + <path d="M 4.0175781 4 L 13.091797 17.609375 L 4.3359375 28 L 6.9511719 28 L 14.246094 19.34375 L 20.017578 28 L 20.552734 28 L 28.015625 28 L 18.712891 14.042969 L 27.175781 4 L 24.560547 4 L 17.558594 12.310547 L 12.017578 4 L 4.0175781 4 z M 7.7558594 6 L 10.947266 6 L 24.279297 26 L 21.087891 26 L 7.7558594 6 z"></path> 99 + </svg> 100 + </a> 100 101 </div> 101 102 </div> 102 103 </footer>
+265
app/(app)/privacy/page.tsx
··· 1 + "use client" 2 + 3 + import { useEffect, useState } from "react" 4 + import Link from "next/link" 5 + import { GithubIcon } from "lucide-react" 6 + 7 + export default function PrivacyPolicy() { 8 + const [lang, setLang] = useState<"en" | "zh">("en") 9 + 10 + useEffect(() => { 11 + if (navigator.language.startsWith("zh")) { 12 + setLang("zh") 13 + } 14 + }, []) 15 + 16 + const content = { 17 + en: { 18 + title: "One Calendar Privacy Policy", 19 + lastUpdated: "Last updated: April 22, 2025", 20 + intro: "At One Calendar, we value your privacy and are committed to protecting your personal information. This Privacy Policy explains how we collect, use, store, and protect your data when you use our services, including our website and application.", 21 + sections: [ 22 + { 23 + heading: "1. Information We Collect", 24 + content: [ 25 + "Account Information: When you sign up using Clerk authentication (via GitHub, Google, or Microsoft), we collect your email address, name, and profile information provided by these services to create and manage your account.", 26 + "Calendar Data: Events, schedules, and related data you input into One Calendar are stored to provide our scheduling and collaboration features.", 27 + "Usage Data: We collect information about how you interact with our services, such as pages visited, features used, and device information (e.g., browser type, IP address).", 28 + "Files Uploaded: Any files you upload to One Calendar, stored via Vercel Blob, are used solely to enhance your experience, such as attaching documents to events.", 29 + "Location Data: We will request to collect your location data to get the weather in your area. This is optional and you can refuse it, but the weather component may not work properly." 30 + ] 31 + }, 32 + { 33 + heading: "2. How We Use Your Information", 34 + content: [ 35 + "To provide and improve our services, including personalizing your calendar experience and enabling collaboration features.", 36 + "To authenticate your identity via Clerk and ensure secure access to your account.", 37 + "To analyze usage patterns and optimize our platform's performance and user experience.", 38 + "To communicate with you, such as sending service-related notifications or responding to your inquiries." 39 + ] 40 + }, 41 + { 42 + heading: "3. Data Storage and Security", 43 + content: [ 44 + "Your data is stored securely using Vercel’s infrastructure, with encryption at rest and in transit.", 45 + "Files uploaded via Vercel Blob are stored in a manner that ensures only you (and those you explicitly share with) can access them.", 46 + "We implement industry-standard security measures to protect your data from unauthorized access, alteration, or disclosure.", 47 + "While we strive to protect your information, no system is completely secure, and we cannot guarantee absolute security." 48 + ] 49 + }, 50 + { 51 + heading: "4. Third-Party Services", 52 + content: [ 53 + "Clerk Authentication: We use Clerk to manage user authentication. When you log in via GitHub, Google, or Microsoft, Clerk processes your credentials and shares limited profile information with us. Please review Clerk’s Privacy Policy for details.", 54 + "Vercel Blob: Files you upload are stored using Vercel Blob. Vercel’s Privacy Policy governs their handling of your data.", 55 + "We do not share your data with other third parties except as necessary to provide our services or as required by law." 56 + ] 57 + }, 58 + { 59 + heading: "5. Data Sharing and Disclosure", 60 + content: [ 61 + "Your calendar data is private by default and only shared with others if you explicitly enable collaboration features.", 62 + "We may disclose your information if required by law, such as in response to a court order or subpoena.", 63 + "In the event of a merger, acquisition, or sale of assets, your data may be transferred as part of the transaction, with safeguards to maintain your privacy." 64 + ] 65 + }, 66 + { 67 + heading: "6. Your Rights and Choices", 68 + content: [ 69 + "You can access, update, or delete your account information at any time through your One Calendar account settings.", 70 + "You can opt out of non-essential communications by adjusting your notification preferences.", 71 + "If you’re in a region with specific data protection laws (e.g., GDPR or CCPA), you may have additional rights, such as requesting a copy of your data or restricting certain processing. Contact us to exercise these rights." 72 + ] 73 + }, 74 + { 75 + heading: "7. Data Retention", 76 + content: [ 77 + "We retain your data for as long as your account is active or as needed to provide our services.", 78 + "If you delete your account, we will remove your personal information and uploaded files within 30 days, except where required to retain data for legal purposes." 79 + ] 80 + }, 81 + { 82 + heading: "8. Children’s Privacy", 83 + content: [ 84 + "One Calendar is a simple and safe calendar tool designed to be suitable for users of all ages, including children. Parents or guardians can manage accounts for children to ensure appropriate use.", 85 + "We collect and process personal information from children in the same manner as adults, but we encourage parental supervision to ensure privacy and safety." 86 + ] 87 + }, 88 + { 89 + heading: "9. Changes to This Privacy Policy", 90 + content: [ 91 + "We may update this Privacy Policy from time to time. Changes will be posted on this page with an updated 'Last updated' date.", 92 + "We encourage you to review this policy periodically to stay informed about how we protect your data." 93 + ] 94 + }, 95 + { 96 + heading: "10. Contact Us", 97 + content: [ 98 + "If you have questions or concerns about this Privacy Policy, please contact us at evan.huang000@proton.me or via GitHub.", 99 + "You can also reach out to provide feedback or report privacy-related issues." 100 + ] 101 + } 102 + ], 103 + cta: "Have feedback or want to contribute to One Calendar?", 104 + github: "Visit our GitHub", 105 + home: "Back to Home" 106 + }, 107 + zh: { 108 + title: "One Calendar 隐私政策", 109 + lastUpdated: "最后更新:2025年4月22日", 110 + intro: "在 One Calendar,我们重视您的隐私,并致力于保护您的个人信息。本隐私政策说明了您使用我们的服务(包括网站和应用程序)时,我们如何收集、使用、存储和保护您的数据。", 111 + sections: [ 112 + { 113 + heading: "1. 我们收集的信息", 114 + content: [ 115 + "账户信息:当您通过 Clerk 认证(GitHub、Google 或 Microsoft)注册时,我们会收集这些服务提供的电子邮件地址、姓名和个人资料信息,以创建和管理您的账户。", 116 + "日历数据:您在 One Calendar 中输入的事件、日程和其他相关数据将被存储,以提供我们的日程安排和协作功能。", 117 + "使用数据:我们会收集您与我们服务交互的信息,例如访问的页面、使用的功能以及设备信息(例如浏览器类型、IP 地址)。", 118 + "上传的文件:您通过 Vercel Blob 上传的任何文件仅用于增强您的使用体验,例如将文档附加到事件中。", 119 + "地点数据:我们会请求收集您的地点数据来获取您地区的天气,当然这是可选的,您可以拒绝它,不过天气组件可能不会正常工作。" 120 + ] 121 + }, 122 + { 123 + heading: "2. 我们如何使用您的信息", 124 + content: [ 125 + "提供和改进我们的服务,包括个性化您的日历体验和启用协作功能。", 126 + "通过 Clerk 验证您的身份,确保账户安全访问。", 127 + "分析使用模式,优化平台性能和用户体验。", 128 + "与您沟通,例如发送服务相关通知或回复您的询问。" 129 + ] 130 + }, 131 + { 132 + heading: "3. 数据存储与安全", 133 + content: [ 134 + "您的数据通过 Vercel 的基础设施安全存储,数据在传输和静态时均进行加密。", 135 + "通过 Vercel Blob 上传的文件以确保只有您(以及您明确共享的人)可以访问的方式存储。", 136 + "我们采用行业标准的安全措施,保护您的数据免受未经授权的访问、更改或披露。", 137 + "虽然我们努力保护您的信息,但没有任何系统是完全安全的,我们无法保证绝对的安全性。" 138 + ] 139 + }, 140 + { 141 + heading: "4. 第三方服务", 142 + content: [ 143 + "Clerk 认证:我们使用 Clerk 管理用户认证。当您通过 GitHub、Google 或 Microsoft 登录时,Clerk 会处理您的凭据并与我们共享有限的个人资料信息。请参阅 Clerk 的隐私政策以了解详情。", 144 + "Vercel Blob:您上传的文件通过 Vercel Blob 存储。Vercel 的隐私政策规定了他们对您数据的处理方式。", 145 + "除非为了提供我们的服务或法律要求,我们不会与其他第三方共享您的数据。" 146 + ] 147 + }, 148 + { 149 + heading: "5. 数据共享与披露", 150 + content: [ 151 + "您的日历数据默认是私密的,只有在您明确启用协作功能时才会与他人共享。", 152 + "如果法律要求,例如响应法院命令或传票,我们可能会披露您的信息。", 153 + "如果发生合并、收购或资产出售,您的信息可能会作为交易的一部分转移,我们将采取措施保护您的隐私。" 154 + ] 155 + }, 156 + { 157 + heading: "6. 您的权利与选择", 158 + content: [ 159 + "您可以随时通过 One Calendar 账户设置访问、更新或删除您的账户信息。", 160 + "您可以通过调整通知偏好来选择退出非必要通信。", 161 + "如果您所在地区有特定的数据保护法律(例如 GDPR 或 CCPA),您可能拥有额外权利,例如请求数据副本或限制某些处理。请联系我们以行使这些权利。" 162 + ] 163 + }, 164 + { 165 + heading: "7. 数据保留", 166 + content: [ 167 + "只要您的账户处于活跃状态或我们需要提供服务,我们就会保留您的数据。", 168 + "如果您删除账户,我们将在 30 天内删除您的个人信息和上传的文件,除非法律要求保留数据。" 169 + ] 170 + }, 171 + { 172 + heading: "8. 儿童隐私", 173 + content: [ 174 + "One Calendar 是一个简单且安全的日历工具,适合包括儿童在内的所有年龄段用户。家长或监护人可以管理儿童的账户,以确保适当使用。", 175 + "我们以与成人相同的方式收集和处理儿童的个人信息,但我们鼓励家长监督以确保隐私和安全。" 176 + ] 177 + }, 178 + { 179 + heading: "9. 本隐私政策的变更", 180 + content: [ 181 + "我们可能会不时更新本隐私政策。变更将在本页面发布,并更新“最后更新”日期。", 182 + "我们鼓励您定期查看本政策,以了解我们如何保护您的数据。" 183 + ] 184 + }, 185 + { 186 + heading: "10. 联系我们", 187 + content: [ 188 + "如果您对本隐私政策有任何疑问或担忧,请通过 evan.huang000@proton.me 或 GitHub 联系我们。", 189 + "您也可以联系我们提供反馈或报告与隐私相关的问题。" 190 + ] 191 + } 192 + ], 193 + cta: "有反馈或想为 One Calendar 做贡献?", 194 + github: "访问我们的 GitHub", 195 + home: "返回首页" 196 + } 197 + } 198 + 199 + const t = content[lang] 200 + 201 + return ( 202 + <div className="min-h-screen flex flex-col text-black dark:text-white"> 203 + <main className="max-w-3xl mx-auto px-6 py-24"> 204 + <div className="fixed -z-10 inset-0 overflow-hidden"> 205 + <div className="absolute left-0 bottom-0 h-[300px] w-[300px] rounded-full bg-blue-400 opacity-20 blur-[80px]" /> 206 + <div className="absolute right-0 top-0 h-[400px] w-[400px] rounded-full bg-purple-400 opacity-25 blur-[100px]" /> 207 + <div className="absolute right-1/4 bottom-1/3 h-[250px] w-[250px] rounded-full bg-indigo-400 opacity-20 blur-[90px]" /> 208 + </div> 209 + <h1 className="text-4xl font-bold text-center mb-12">{t.title}</h1> 210 + <p className="text-sm text-gray-500 text-center mb-8 dark:text-white">{t.lastUpdated}</p> 211 + <div className="space-y-8 text-left"> 212 + <p className="text-lg text-gray-700 leading-relaxed dark:text-white">{t.intro}</p> 213 + {t.sections.map((section, i) => ( 214 + <div key={i} className="space-y-4"> 215 + <h2 className="text-2xl font-semibold">{section.heading}</h2> 216 + {section.content.map((item, j) => ( 217 + <p key={j} className="text-lg text-gray-700 leading-relaxed dark:text-white"> 218 + {item} 219 + </p> 220 + ))} 221 + </div> 222 + ))} 223 + </div> 224 + </main> 225 + 226 + <section className="text-center px-6 py-16"> 227 + <div className="max-w-3xl mx-auto space-y-4"> 228 + <h2 className="text-xl font-medium dark:text-white">{t.cta}</h2> 229 + <div className="flex justify-center gap-4 pt-4"> 230 + <Link 231 + href="https://github.com/Dev-Huang1/One-Calendar" 232 + target="_blank" 233 + className="flex items-center gap-2 text-blue-600 hover:underline" 234 + > 235 + <GithubIcon className="w-4 h-4" /> 236 + {t.github} 237 + </Link> 238 + <Link href="/" className="text-sm text-gray-500 underline hover:text-black"> 239 + {t.home} 240 + </Link> 241 + </div> 242 + </div> 243 + </section> 244 + 245 + <footer className="mt-auto py-8 border-t text-sm text-gray-500 px-6"> 246 + <div className="max-w-6xl mx-auto flex flex-col md:flex-row justify-between items-center gap-4"> 247 + <p>&copy; 2025 One Calendar. All rights reserved.</p> 248 + <div className="flex gap-4"> 249 + <a href="/about" className="hover:underline">About</a> 250 + <a href="/privacy" className="hover:underline">Privacy</a> 251 + <a href="/terms" className="hover:underline">Terms</a> 252 + <a href="https://github.com/Dev-Huang1/One-Calendar" target="_blank" rel="noopener" className="flex items-center gap-1 hover:underline"> 253 + <GithubIcon className="w-4 h-4" /> 254 + </a> 255 + <a href="https://x.com/One__Cal" target="_blank" className="flex items-center gap-1 hover:underline"> 256 + <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="16" height="16" viewBox="0 0 32 32"> 257 + <path d="M 4.0175781 4 L 13.091797 17.609375 L 4.3359375 28 L 6.9511719 28 L 14.246094 19.34375 L 20.017578 28 L 20.552734 28 L 28.015625 28 L 18.712891 14.042969 L 27.175781 4 L 24.560547 4 L 17.558594 12.310547 L 12.017578 4 L 4.0175781 4 z M 7.7558594 6 L 10.947266 6 L 24.279297 26 L 21.087891 26 L 7.7558594 6 z"></path> 258 + </svg> 259 + </a> 260 + </div> 261 + </div> 262 + </footer> 263 + </div> 264 + ) 265 + }
+281
app/(app)/terms/page.tsx
··· 1 + "use client" 2 + 3 + import { useEffect, useState } from "react" 4 + import Link from "next/link" 5 + import { GithubIcon } from "lucide-react" 6 + 7 + export default function TermsOfService() { 8 + const [lang, setLang] = useState<"en" | "zh">("en") 9 + 10 + useEffect(() => { 11 + if (navigator.language.startsWith("zh")) { 12 + setLang("zh") 13 + } 14 + }, []) 15 + 16 + const content = { 17 + en: { 18 + title: "One Calendar Terms of Service", 19 + lastUpdated: "Last updated: April 22, 2025", 20 + intro: "Welcome to One Calendar! These Terms of Service ('Terms') govern your use of our website, application, and services. By accessing or using One Calendar, you agree to be bound by these Terms. If you do not agree, please do not use our services.", 21 + sections: [ 22 + { 23 + heading: "1. Use of One Calendar", 24 + content: [ 25 + "One Calendar is a scheduling and collaboration tool designed for users of all ages. You may use our services to create, manage, and share calendars, subject to these Terms.", 26 + "You must provide accurate information when creating an account via Clerk authentication (GitHub, Google, or Microsoft).", 27 + "You are responsible for maintaining the confidentiality of your account credentials and for all activities under your account." 28 + ] 29 + }, 30 + { 31 + heading: "2. License (GNU GPLv3)", 32 + content: [ 33 + "One Calendar is open-source software licensed under the GNU General Public License v3.0 (GPLv3). You are free to use, modify, and distribute the software in accordance with the terms of the GPLv3.", 34 + "The source code is available on our GitHub repository. Any modifications or derivative works must also be licensed under GPLv3 and made publicly available.", 35 + "For details, please review the full GPLv3 license at <a href='https://www.gnu.org/licenses/gpl-3.0.en.html' target='_blank' class='text-blue-600 hover:underline'>https://www.gnu.org/licenses/gpl-3.0.en.html</a>." 36 + ] 37 + }, 38 + { 39 + heading: "3. Self-Hosting", 40 + content: [ 41 + "You may self-host One Calendar on your own servers. Instructions and source code are available on our GitHub repository.", 42 + "When self-hosting, you are responsible for ensuring compliance with these Terms, the GPLv3 license, and applicable laws, including data protection regulations.", 43 + "We provide no warranties or support for self-hosted instances unless explicitly agreed upon." 44 + ] 45 + }, 46 + { 47 + heading: "4. Third-Party Services", 48 + content: [ 49 + "One Calendar integrates with third-party services, including Clerk for authentication (via GitHub, Google, or Microsoft) and Vercel Blob for file storage.", 50 + "We are not responsible for any interruptions, downtime, or issues caused by these third-party services. Your use of these services is subject to their respective terms and privacy policies.", 51 + "We strive to maintain reliable integrations but cannot guarantee the availability or performance of third-party services." 52 + ] 53 + }, 54 + { 55 + heading: "5. User Responsibilities", 56 + content: [ 57 + "You agree to use One Calendar in compliance with applicable laws and regulations.", 58 + "You are responsible for the content you create, upload, or share, including calendar events and files stored via Vercel Blob.", 59 + "You must not use One Calendar for illegal, harmful, or abusive purposes, including but not limited to distributing malware, engaging in harassment, or violating intellectual property rights." 60 + ] 61 + }, 62 + { 63 + heading: "6. Community Guidelines", 64 + content: [ 65 + "We encourage a respectful and inclusive community. When collaborating or contributing to One Calendar (e.g., via GitHub), you agree to:", 66 + "- Be respectful and considerate in your interactions.", 67 + "- Refrain from posting offensive, discriminatory, or inappropriate content.", 68 + "- Follow our contribution guidelines outlined in the GitHub repository.", 69 + "Violations of these guidelines may result in account suspension or termination." 70 + ] 71 + }, 72 + { 73 + heading: "7. Intellectual Property", 74 + content: [ 75 + "Content you create in One Calendar (e.g., events, uploaded files) remains your property. By uploading content, you grant us a non-exclusive, worldwide, royalty-free license to store, display, and process it as needed to provide our services.", 76 + "One Calendar’s software, design, and branding are licensed under GPLv3 or owned by us, except where third-party licenses apply." 77 + ] 78 + }, 79 + { 80 + heading: "8. Limitation of Liability", 81 + content: [ 82 + "One Calendar is provided 'as is' without warranties of any kind, express or implied, including fitness for a particular purpose.", 83 + "We are not liable for any damages arising from your use of One Calendar, including data loss, service interruptions, or third-party actions.", 84 + "Our liability is limited to the maximum extent permitted by law." 85 + ] 86 + }, 87 + { 88 + heading: "9. Termination", 89 + content: [ 90 + "You may stop using One Calendar at any time. You can delete your account via the account settings.", 91 + "We reserve the right to suspend or terminate your access if you violate these Terms or engage in harmful behavior.", 92 + "Upon termination, your data will be deleted in accordance with our Privacy Policy." 93 + ] 94 + }, 95 + { 96 + heading: "10. Changes to These Terms", 97 + content: [ 98 + "We may update these Terms from time to time. Changes will be posted on this page with an updated 'Last updated' date.", 99 + "Continued use of One Calendar after changes constitutes acceptance of the updated Terms." 100 + ] 101 + }, 102 + { 103 + heading: "11. Contact Us", 104 + content: [ 105 + "If you have questions about these Terms, please contact us at evan.huang000@proton.me or via GitHub.", 106 + "We welcome feedback and contributions to improve One Calendar." 107 + ] 108 + } 109 + ], 110 + cta: "Want to contribute or learn more about One Calendar?", 111 + github: "Visit our GitHub", 112 + home: "Back to Home" 113 + }, 114 + zh: { 115 + title: "One Calendar 服务条款", 116 + lastUpdated: "最后更新:2025年4月22日", 117 + intro: "欢迎使用 One Calendar!本服务条款(“条款”)规范您对我们的网站、应用程序和服务的访问和使用。使用 One Calendar 即表示您同意受本条款约束。如果您不同意,请勿使用我们的服务。", 118 + sections: [ 119 + { 120 + heading: "1. 使用 One Calendar", 121 + content: [ 122 + "One Calendar 是一个为所有年龄段用户设计的日程安排和协作工具。您可以根据本条款使用我们的服务来创建、管理和共享日历。", 123 + "通过 Clerk 认证(GitHub、Google 或 Microsoft)创建账户时,您必须提供准确的信息。", 124 + "您有责任保护账户凭据的机密性,并对账户下的所有活动负责。" 125 + ] 126 + }, 127 + { 128 + heading: "2. 许可证 (GNU GPLv3)", 129 + content: [ 130 + "One Calendar 是根据 GNU 通用公共许可证 v3.0 (GPLv3) 许可的开源软件。您可以根据 GPLv3 的条款自由使用、修改和分发软件。", 131 + "源代码可在我们的 GitHub 仓库中获取。任何修改或衍生作品也必须根据 GPLv3 许可并公开提供。", 132 + "详情请查看完整的 GPLv3 许可证:<a href='https://www.gnu.org/licenses/gpl-3.0.en.html' target='_blank' class='text-blue-600 hover:underline'>https://www.gnu.org/licenses/gpl-3.0.en.html</a>。" 133 + ] 134 + }, 135 + { 136 + heading: "3. 自托管", 137 + content: [ 138 + "您可以在自己的服务器上自托管 One Calendar。相关说明和源代码可在我们的 GitHub 仓库中找到。", 139 + "自托管时,您有责任确保遵守本条款、GPLv3 许可证及适用的法律,包括数据保护法规。", 140 + "除非另有明确约定,我们不对自托管实例提供任何保证或支持。" 141 + ] 142 + }, 143 + { 144 + heading: "4. 第三方服务", 145 + content: [ 146 + "One Calendar 集成了第三方服务,包括用于身份验证的 Clerk(通过 GitHub、Google 或 Microsoft)和用于文件存储的 Vercel Blob。", 147 + "我们不对第三方服务的中断、停机或问题负责。您对这些服务的使用受其各自条款和隐私政策的约束。", 148 + "我们努力维护可靠的集成,但无法保证第三方服务的可用性或性能。" 149 + ] 150 + }, 151 + { 152 + heading: "5. 用户责任", 153 + content: [ 154 + "您同意遵守适用法律法规使用 One Calendar。", 155 + "您对创建、上传或共享的内容(包括日历事件和通过 Vercel Blob 存储的文件)负责。", 156 + "您不得将 One Calendar 用于非法、有害或滥用的目的,包括但不限于分发恶意软件、进行骚扰或侵犯知识产权。" 157 + ] 158 + }, 159 + { 160 + heading: "6. 社区准则", 161 + content: [ 162 + "我们鼓励建立一个尊重和包容的社区。在协作或为 One Calendar 做贡献(例如通过 GitHub)时,您同意:", 163 + "- 在互动中保持尊重和体贴。", 164 + "- 不发布冒犯性、歧视性或不适当的内容。", 165 + "- 遵循 GitHub 仓库中概述的贡献指南。", 166 + "违反这些准则可能导致账户暂停或终止。" 167 + ] 168 + }, 169 + { 170 + heading: "7. 知识产权", 171 + content: [ 172 + "您在 One Calendar 中创建的内容(例如事件、上传的文件)仍归您所有。通过上传内容,您授予我们非独占的、全球性的、免版税的许可,以存储、显示和处理内容,以提供我们的服务。", 173 + "One Calendar 的软件、设计和品牌根据 GPLv3 许可或由我们拥有,除非适用第三方许可。" 174 + ] 175 + }, 176 + { 177 + heading: "8. 责任限制", 178 + content: [ 179 + "One Calendar 按“现状”提供,不提供任何明示或暗示的保证,包括特定用途的适用性。", 180 + "我们不对您使用 One Calendar 所产生的任何损害负责,包括数据丢失、服务中断或第三方行为。", 181 + "我们的责任在法律允许的最大范围内受到限制。" 182 + ] 183 + }, 184 + { 185 + heading: "9. 终止", 186 + content: [ 187 + "您可以随时停止使用 One Calendar,并通过账户设置删除您的账户。", 188 + "如果您违反本条款或从事有害行为,我们保留暂停或终止您访问的权利。", 189 + "终止后,您的信息将根据我们的隐私政策删除。" 190 + ] 191 + }, 192 + { 193 + heading: "10. 本条款的变更", 194 + content: [ 195 + "我们可能会不时更新本条款。变更将在本页面发布,并更新“最后更新”日期。", 196 + "在变更后继续使用 One Calendar 即表示接受更新后的条款。" 197 + ] 198 + }, 199 + { 200 + heading: "11. 联系我们", 201 + content: [ 202 + "如对本条款有任何疑问,请通过 evan.huang000@proton.me 或 GitHub 联系我们。", 203 + "我们欢迎反馈和贡献,以改进 One Calendar。" 204 + ] 205 + } 206 + ], 207 + cta: "想为 One Calendar 做贡献或了解更多?", 208 + github: "访问我们的 GitHub", 209 + home: "返回首页" 210 + } 211 + } 212 + 213 + const t = content[lang] 214 + 215 + return ( 216 + <div className="min-h-screen flex flex-col text-black dark:text-white"> 217 + <main className="max-w-3xl mx-auto px-6 py-24"> 218 + <div className="fixed -z-10 inset-0 overflow-hidden"> 219 + <div className="absolute left-0 bottom-0 h-[300px] w-[300px] rounded-full bg-blue-400 opacity-20 blur-[80px]" /> 220 + <div className="absolute right-0 top-0 h-[400px] w-[400px] rounded-full bg-purple-400 opacity-25 blur-[100px]" /> 221 + <div className="absolute right-1/4 bottom-1/3 h-[250px] w-[250px] rounded-full bg-indigo-400 opacity-20 blur-[90px]" /> 222 + </div> 223 + <h1 className="text-4xl font-bold text-center mb-12">{t.title}</h1> 224 + <p className="text-sm text-gray-500 text-center mb-8 dark:text-white">{t.lastUpdated}</p> 225 + <div className="space-y-8 text-left"> 226 + <p className="text-lg text-gray-700 leading-relaxed dark:text-white">{t.intro}</p> 227 + {t.sections.map((section, i) => ( 228 + <div key={i} className="space-y-4"> 229 + <h2 className="text-2xl font-semibold">{section.heading}</h2> 230 + {section.content.map((item, j) => ( 231 + <p 232 + key={j} 233 + className="text-lg text-gray-700 leading-relaxed dark:text-white" 234 + dangerouslySetInnerHTML={{ __html: item }} 235 + /> 236 + ))} 237 + </div> 238 + ))} 239 + </div> 240 + </main> 241 + 242 + <section className="text-center px-6 py-16"> 243 + <div className="max-w-3xl mx-auto space-y-4"> 244 + <h2 className="text-xl font-medium">{t.cta}</h2> 245 + <div className="flex justify-center gap-4 pt-4"> 246 + <Link 247 + href="https://github.com/Dev-Huang1/One-Calendar" 248 + target="_blank" 249 + className="flex items-center gap-2 text-blue-600 hover:underline" 250 + > 251 + <GithubIcon className="w-4 h-4" /> 252 + {t.github} 253 + </Link> 254 + <Link href="/" className="text-sm text-gray-500 underline hover:text-black dark:text-white"> 255 + {t.home} 256 + </Link> 257 + </div> 258 + </div> 259 + </section> 260 + 261 + <footer className="mt-auto py-8 border-t text-sm text-gray-500 px-6"> 262 + <div className="max-w-6xl mx-auto flex flex-col md:flex-row justify-between items-center gap-4"> 263 + <p>&copy; 2025 One Calendar. All rights reserved.</p> 264 + <div className="flex gap-4"> 265 + <a href="/about" className="hover:underline">About</a> 266 + <a href="/privacy" className="hover:underline">Privacy</a> 267 + <a href="/terms" className="hover:underline">Terms</a> 268 + <a href="https://github.com/Dev-Huang1/One-Calendar" target="_blank" rel="noopener" className="flex items-center gap-1 hover:underline"> 269 + <GithubIcon className="w-4 h-4" /> 270 + </a> 271 + <a href="https://x.com/One__Cal" target="_blank" className="flex items-center gap-1 hover:underline"> 272 + <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="16" height="16" viewBox="0 0 32 32"> 273 + <path d="M 4.0175781 4 L 13.091797 17.609375 L 4.3359375 28 L 6.9511719 28 L 14.246094 19.34375 L 20.017578 28 L 20.552734 28 L 28.015625 28 L 18.712891 14.042969 L 27.175781 4 L 24.560547 4 L 17.558594 12.310547 L 12.017578 4 L 4.0175781 4 z M 7.7558594 6 L 10.947266 6 L 24.279297 26 L 21.087891 26 L 7.7558594 6 z"></path> 274 + </svg> 275 + </a> 276 + </div> 277 + </div> 278 + </footer> 279 + </div> 280 + ) 281 + }
+13 -1
app/sitemap.ts
··· 2 2 import fs from 'fs'; 3 3 import path from 'path'; 4 4 5 - const baseUrl = 'https://calendar.xyehr.cn'; 5 + const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'https://calendar.xyehr.cn'; 6 6 7 7 export const revalidate = 86400; // Revalidate once per day 8 8 ··· 29 29 { 30 30 url: `${baseUrl}/about`, 31 31 lastModified: getFileModDate('app/about/page.tsx'), 32 + changeFrequency: 'monthly', 33 + priority: 0.8, 34 + }, 35 + { 36 + url: `${baseUrl}/privacy`, 37 + lastModified: getFileModDate('app/privacy/page.tsx'), 38 + changeFrequency: 'monthly', 39 + priority: 0.8, 40 + }, 41 + { 42 + url: `${baseUrl}/terms`, 43 + lastModified: getFileModDate('app/terms/page.tsx'), 32 44 changeFrequency: 'monthly', 33 45 priority: 0.8, 34 46 },
+17 -10
components/home/Landing.tsx
··· 29 29 if (!shouldRender) return null 30 30 31 31 return ( 32 - <div className="flex flex-col min-h-screen text-black"> 32 + <div className="flex flex-col min-h-screen text-black dark:text-white"> 33 33 <div className="fixed -z-10 inset-0 overflow-hidden"> 34 34 <div className="absolute left-0 bottom-0 h-[300px] w-[300px] rounded-full bg-blue-400 opacity-20 blur-[80px]" /> 35 35 <div className="absolute right-0 top-0 h-[400px] w-[400px] rounded-full bg-purple-400 opacity-25 blur-[100px]" /> 36 36 <div className="absolute right-1/4 bottom-1/3 h-[250px] w-[250px] rounded-full bg-indigo-400 opacity-20 blur-[90px]" /> 37 37 </div> 38 38 {/* Hero Section */} 39 - <section className="flex flex-col items-center justify-center text-center py-24 px-4"> {/* Reduced padding */} 40 - <h1 className="text-5xl font-bold tracking-tight mb-2">One Calendar</h1> {/* Reduced bottom margin */} 41 - <p className="text-lg text-gray-600 max-w-xl mb-4"> {/* Reduced bottom margin */} 39 + <section className="flex flex-col items-center justify-center text-center py-24 px-4"> 40 + <h1 className="text-5xl font-bold tracking-tight mb-2">One Calendar</h1> 41 + <p className="text-lg text-gray-600 dark:text-gray-300 max-w-xl mb-4"> 42 42 All your events in one place, beautifully organized. 43 43 </p> 44 - <div className="flex gap-4 mb-6"> {/* Reduced bottom margin */} 44 + <div className="flex gap-4 mb-6"> 45 45 <Button onClick={handleGetStarted} className="px-6">Get Started</Button> 46 46 <Button variant="outline" onClick={() => router.push("/sign-in")} className="px-6">Login</Button> 47 47 </div> ··· 56 56 </section> 57 57 58 58 {/* Features Section */} 59 - <section className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-6xl mx-auto py-16 px-6"> {/* Reduced top padding */} 59 + <section className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-6xl mx-auto py-16 px-6"> 60 60 <Feature icon={<CloudIcon className="h-6 w-6 text-blue-500" />} title="Cloud Sync" description="Access your events from anywhere with secure cloud storage." /> 61 61 <Feature icon={<Share2Icon className="h-6 w-6 text-green-500" />} title="Easy Sharing" description="Collaborate and share your schedule with ease." /> 62 62 <Feature icon={<BarChart3Icon className="h-6 w-6 text-purple-500" />} title="Analytics" description="Gain insights with smart event tracking and summaries." /> ··· 66 66 </section> 67 67 68 68 {/* Footer */} 69 - <footer className="mt-auto py-8 border-t text-sm text-gray-500 px-6"> 69 + <footer className="mt-auto py-8 border-t border-gray-200 dark:border-gray-700 text-sm text-gray-500 dark:text-gray-400 px-6"> 70 70 <div className="max-w-6xl mx-auto flex flex-col md:flex-row justify-between items-center gap-4"> 71 71 <p>&copy; 2025 One Calendar. All rights reserved.</p> 72 72 <div className="flex gap-4"> 73 73 <a href="/about" className="hover:underline">About</a> 74 + <a href="/privacy" className="hover:underline">Privacy</a> 75 + <a href="/terms" className="hover:underline">Terms</a> 74 76 <a href="https://github.com/Dev-Huang1/One-Calendar" target="_blank" rel="noopener" className="flex items-center gap-1 hover:underline"> 75 - <GithubIcon className="w-4 h-4" /> GitHub 77 + <GithubIcon className="w-4 h-4" /> 78 + </a> 79 + <a href="https://x.com/One__Cal" target="_blank" className="flex items-center gap-1 hover:underline"> 80 + <svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="16" height="16" viewBox="0 0 32 32"> 81 + <path d="M 4.0175781 4 L 13.091797 17.609375 L 4.3359375 28 L 6.9511719 28 L 14.246094 19.34375 L 20.017578 28 L 20.552734 28 L 28.015625 28 L 18.712891 14.042969 L 27.175781 4 L 24.560547 4 L 17.558594 12.310547 L 12.017578 4 L 4.0175781 4 z M 7.7558594 6 L 10.947266 6 L 24.279297 26 L 21.087891 26 L 7.7558594 6 z"></path> 82 + </svg> 76 83 </a> 77 84 </div> 78 85 </div> ··· 83 90 84 91 function Feature({ icon, title, description }: { icon: React.ReactNode; title: string; description: string }) { 85 92 return ( 86 - <div className="flex flex-col items-start p-6 border rounded-2xl shadow-sm bg-white hover:shadow-md transition"> 93 + <div className="flex flex-col items-start p-6 border border-gray-200 dark:border-gray-700 rounded-2xl shadow-sm bg-white dark:bg-gray-900 hover:shadow-md transition"> 87 94 <div className="mb-4">{icon}</div> 88 95 <h3 className="text-xl font-semibold mb-1">{title}</h3> 89 - <p className="text-gray-600 text-sm">{description}</p> 96 + <p className="text-gray-600 dark:text-gray-300 text-sm">{description}</p> 90 97 </div> 91 98 ) 92 99 }
-13
components/home/QuickStartGuide.tsx
··· 9 9 DialogDescription, 10 10 DialogFooter, 11 11 } from "@/components/ui/dialog" 12 - import { Button } from "@/components/ui/button" 13 12 import { useLocalStorage } from "@/hooks/useLocalStorage" 14 13 import { translations } from "@/lib/i18n" 15 14 import { useLanguage } from "@/hooks/useLanguage" ··· 98 97 /> 99 98 </section> 100 99 101 - <DialogFooter className="flex justify-between"> 102 - <Button 103 - variant="outline" 104 - disabled={activeTab === "basics"} 105 - > 106 - {t.previousStep} 107 - </Button> 108 - <Button> 109 - > 110 - {activeTab === "import-export" ? t.startUsing : t.nextStep} 111 - </Button> 112 - </DialogFooter> 113 100 </DialogContent> 114 101 </Dialog> 115 102 )
+2 -2
components/home/Settings.tsx
··· 110 110 </Select> 111 111 </div> 112 112 113 - <div className="space-y-2"> 113 + {/*<div className="space-y-2"> 114 114 <Label htmlFor="language">{t.language}</Label> 115 115 <Select value={language} onValueChange={(value: Language) => handleLanguageChange(value)}> 116 116 <SelectTrigger id="language"> ··· 121 121 <SelectItem value="zh">中文</SelectItem> 122 122 </SelectContent> 123 123 </Select> 124 - </div> 124 + </div>*/} 125 125 126 126 <div className="space-y-2"> 127 127 <Label htmlFor="first-day">{t.firstDayOfWeek}</Label>