mirror of
https://github.com/iflytek/skillhub.git
synced 2026-08-28 11:25:00 +00:00
Merge pull request #19 from iflytek/feat/legal-pages-and-links
feat(web): add privacy policy and terms pages
This commit is contained in:
commit
daecc2d6cf
6 changed files with 432 additions and 7 deletions
|
|
@ -150,12 +150,12 @@ export function Layout() {
|
|||
{t('footer.copyright')}
|
||||
</p>
|
||||
<div className="flex items-center gap-4">
|
||||
<a href="#" className="text-xs text-muted-foreground hover:text-primary transition-colors">
|
||||
<Link to="/privacy" className="text-xs text-muted-foreground hover:text-primary transition-colors">
|
||||
{t('footer.privacy')}
|
||||
</a>
|
||||
<a href="#" className="text-xs text-muted-foreground hover:text-primary transition-colors">
|
||||
</Link>
|
||||
<Link to="/terms" className="text-xs text-muted-foreground hover:text-primary transition-colors">
|
||||
{t('footer.terms')}
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ function lazyRouteComponent<TModule extends Record<string, unknown>>(
|
|||
const HomePage = lazyRouteComponent(() => import('@/pages/home'), 'HomePage')
|
||||
const LoginPage = lazyRouteComponent(() => import('@/pages/login'), 'LoginPage')
|
||||
const RegisterPage = lazyRouteComponent(() => import('@/pages/register'), 'RegisterPage')
|
||||
const PrivacyPolicyPage = lazyRouteComponent(() => import('@/pages/privacy'), 'PrivacyPolicyPage')
|
||||
const SearchPage = lazyRouteComponent(() => import('@/pages/search'), 'SearchPage')
|
||||
const TermsOfServicePage = lazyRouteComponent(() => import('@/pages/terms'), 'TermsOfServicePage')
|
||||
const NamespacePage = lazyRouteComponent(() => import('@/pages/namespace'), 'NamespacePage')
|
||||
const SkillDetailPage = lazyRouteComponent(() => import('@/pages/skill-detail'), 'SkillDetailPage')
|
||||
const DashboardPage = lazyRouteComponent(() => import('@/pages/dashboard'), 'DashboardPage')
|
||||
|
|
@ -81,6 +83,12 @@ const registerRoute = createRoute({
|
|||
component: RegisterPage,
|
||||
})
|
||||
|
||||
const privacyRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: 'privacy',
|
||||
component: PrivacyPolicyPage,
|
||||
})
|
||||
|
||||
const searchRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: 'search',
|
||||
|
|
@ -94,6 +102,12 @@ const searchRoute = createRoute({
|
|||
},
|
||||
})
|
||||
|
||||
const termsRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: 'terms',
|
||||
component: TermsOfServicePage,
|
||||
})
|
||||
|
||||
const namespaceRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: '/space/$namespace',
|
||||
|
|
@ -239,7 +253,9 @@ const routeTree = rootRoute.addChildren([
|
|||
skillsRoute,
|
||||
loginRoute,
|
||||
registerRoute,
|
||||
privacyRoute,
|
||||
searchRoute,
|
||||
termsRoute,
|
||||
namespaceRoute,
|
||||
skillDetailRoute,
|
||||
dashboardRoute,
|
||||
|
|
|
|||
|
|
@ -10,13 +10,14 @@ import { Input } from '@/shared/ui/input'
|
|||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/shared/ui/tabs'
|
||||
|
||||
export function LoginPage() {
|
||||
const { t } = useTranslation()
|
||||
const { t, i18n } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const search = useSearch({ from: '/login' })
|
||||
const loginMutation = usePasswordLogin()
|
||||
const directAuthConfig = getDirectAuthRuntimeConfig()
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const isChinese = i18n.resolvedLanguage?.split('-')[0] === 'zh'
|
||||
|
||||
const returnTo = search.returnTo && search.returnTo.startsWith('/') ? search.returnTo : '/dashboard'
|
||||
|
||||
|
|
@ -115,9 +116,16 @@ export function LoginPage() {
|
|||
|
||||
<p className="text-center text-xs text-muted-foreground">
|
||||
{t('login.agreementPrefix')}
|
||||
<a href="#" className="text-primary hover:underline ml-1">{t('login.terms')}</a>
|
||||
{isChinese ? null : ' '}
|
||||
<Link to="/terms" className="text-primary hover:underline">
|
||||
{t('login.terms')}
|
||||
</Link>
|
||||
{isChinese ? null : ' '}
|
||||
{t('login.and')}
|
||||
<a href="#" className="text-primary hover:underline ml-1">{t('login.privacy')}</a>
|
||||
{isChinese ? null : ' '}
|
||||
<Link to="/privacy" className="text-primary hover:underline">
|
||||
{t('login.privacy')}
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
158
web/src/pages/privacy.tsx
Normal file
158
web/src/pages/privacy.tsx
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { LegalDocument } from '@/shared/components/legal-document'
|
||||
|
||||
const privacyDocuments = {
|
||||
zh: {
|
||||
eyebrow: '法律与隐私',
|
||||
title: '隐私政策',
|
||||
summary: '本政策说明 SkillHub 在提供技能注册、发布、审核、下载、账号登录和相关 API 服务时,如何收集、使用、共享并保护与你有关的信息。',
|
||||
lastUpdated: '最后更新:2026年3月14日',
|
||||
note: '如果你使用的是某个组织自行部署的 SkillHub 实例,该实例的运营方也可能根据其内部政策处理数据,并对其部署环境中的数据承担独立责任。',
|
||||
sections: [
|
||||
{
|
||||
title: '1. 适用范围',
|
||||
paragraphs: [
|
||||
'本政策适用于 SkillHub 网站、Web 控制台、公开技能页面、登录流程、设备授权流程以及与这些能力相关的接口和服务。',
|
||||
'当你浏览技能、发布版本、参与审核、生成令牌或下载内容时,本政策描述我们如何处理与你相关的信息。',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '2. 我们收集的信息',
|
||||
paragraphs: [],
|
||||
bullets: [
|
||||
'账户与身份信息,例如用户名、邮箱、头像、OAuth 提供方标识、平台角色和命名空间成员关系。',
|
||||
'你主动提交的内容,例如技能包、README、版本说明、命名空间资料、评分、星标和审核意见。',
|
||||
'使用与安全信息,例如 IP 地址、浏览器或设备信息、请求日志、下载记录、登录事件、API Token 元数据、错误日志和审计日志。',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '3. 我们如何使用信息',
|
||||
paragraphs: [],
|
||||
bullets: [
|
||||
'提供登录、会话管理、权限控制、设备授权、账号安全和基础客户支持。',
|
||||
'展示公开技能页面,支持搜索、下载、评分、星标、命名空间协作和后台治理流程。',
|
||||
'执行审核、限流、风控、故障排查、性能分析和产品改进。',
|
||||
'在必要时发送与安全、政策更新或服务可用性相关的重要通知。',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '4. 公开信息与共享',
|
||||
paragraphs: [
|
||||
'你发布为公开的技能、版本说明、命名空间名称、公开评分以及部分个人资料信息,可能会向其他用户或访客展示。',
|
||||
'除为提供托管、认证、监控、合规支持所必需,或为遵守法律要求、保护平台与用户安全外,我们不会出售你的个人信息。',
|
||||
'在私有部署场景中,数据也可能由部署运营方按照其内部治理和合规要求访问、处理或保留。',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '5. 数据保留',
|
||||
paragraphs: [
|
||||
'我们会在实现业务目的所需的期限内保留账户资料、技能元数据、审核记录和安全日志,并可能在法律、审计或合规要求下延长保留期限。',
|
||||
'被撤销的 API Token 将失效,但相关安全与审计记录可能继续保留。删除或下线技能后,备份、副本或日志中的相关信息可能在合理周期内继续存在。',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '6. 你的选择与权利',
|
||||
paragraphs: [],
|
||||
bullets: [
|
||||
'你可以更新账户资料、修改密码、撤销或重新创建 API Token。',
|
||||
'你可以联系实例管理员申请导出、更正或删除与你有关的信息,但某些记录可能因安全、审计或合规义务而不能立即删除。',
|
||||
'对于私有部署实例,你的隐私请求通常应优先提交给该实例的运营方或管理员。',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '7. 安全措施',
|
||||
paragraphs: [
|
||||
'我们采取合理的技术和组织措施保护数据,例如访问控制、鉴权、审计、令牌管理和传输安全。',
|
||||
'但任何互联网服务都无法保证绝对安全。你应妥善保管账号凭据,并在发现异常登录、泄露或滥用时及时通知实例运营方。',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '8. 政策更新与联系我们',
|
||||
paragraphs: [
|
||||
'我们可能随着产品能力、法律要求或运营方式变化更新本政策。新版政策发布后将在站内适当位置展示,并以页面标注的更新时间为准。',
|
||||
'如需联系,请通过当前实例提供的文档、社区、支持渠道或管理员联系方式与 SkillHub 运营方联系。',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
en: {
|
||||
eyebrow: 'Legal',
|
||||
title: 'Privacy Policy',
|
||||
summary: 'This policy explains how SkillHub collects, uses, shares, and protects information when we provide skill registry, publishing, review, download, account, and related API services.',
|
||||
lastUpdated: 'Last updated: March 14, 2026',
|
||||
note: 'If you use a privately deployed SkillHub instance, that deployment operator may also process data under its own internal policies and may act independently for data handled in that environment.',
|
||||
sections: [
|
||||
{
|
||||
title: '1. Scope',
|
||||
paragraphs: [
|
||||
'This policy applies to the SkillHub website, web console, public skill pages, login flows, device authorization flows, and related APIs and services.',
|
||||
'When you browse skills, publish versions, participate in reviews, create tokens, or download content, this policy describes how we handle information related to you.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '2. Information We Collect',
|
||||
paragraphs: [],
|
||||
bullets: [
|
||||
'Account and identity information such as username, email, avatar, OAuth provider identifiers, platform roles, and namespace membership.',
|
||||
'Content you submit, including skill packages, README files, release notes, namespace profiles, ratings, stars, and review comments.',
|
||||
'Usage and security information such as IP address, browser or device details, request logs, download activity, login events, API token metadata, error logs, and audit logs.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '3. How We Use Information',
|
||||
paragraphs: [],
|
||||
bullets: [
|
||||
'To provide login, session management, access control, device authorization, account security, and basic support.',
|
||||
'To display public skill pages and power search, downloads, ratings, stars, namespace collaboration, and governance workflows.',
|
||||
'To perform review operations, rate limiting, abuse prevention, debugging, performance analysis, and service improvement.',
|
||||
'To send important notices related to security, policy changes, or service availability when needed.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '4. Public Information and Sharing',
|
||||
paragraphs: [
|
||||
'Skills you publish publicly, release notes, namespace names, public ratings, and some profile information may be visible to other users or visitors.',
|
||||
'We do not sell your personal information. We may share information when necessary to provide hosting, authentication, monitoring, or compliance support, or to comply with law and protect the service and its users.',
|
||||
'For private deployments, the instance operator may also access, process, or retain data according to its own internal governance and compliance requirements.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '5. Data Retention',
|
||||
paragraphs: [
|
||||
'We retain account information, skill metadata, review records, and security logs for as long as needed to operate the service and may retain them longer where required for legal, audit, or compliance purposes.',
|
||||
'Revoked API tokens stop working, but related security and audit records may remain. After a skill is deleted or hidden, residual copies in backups or logs may persist for a reasonable period.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '6. Your Choices and Rights',
|
||||
paragraphs: [],
|
||||
bullets: [
|
||||
'You can update account information, change your password, and revoke or recreate API tokens.',
|
||||
'You can contact the instance administrator to request export, correction, or deletion of information related to you, although some records may need to be retained for security, audit, or compliance reasons.',
|
||||
'For privately deployed instances, privacy requests should usually be directed to that instance operator first.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '7. Security Measures',
|
||||
paragraphs: [
|
||||
'We use reasonable technical and organizational safeguards such as access controls, authentication, auditing, token management, and transport security.',
|
||||
'No internet service can guarantee absolute security. You should protect your credentials and report suspicious access, leakage, or misuse promptly to the instance operator.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '8. Policy Updates and Contact',
|
||||
paragraphs: [
|
||||
'We may update this policy as the product, legal requirements, or operating model changes. The latest version will be posted in the service and identified by its update date.',
|
||||
'If you need to contact us, use the documentation, community, support channel, or administrator contact information provided by the current SkillHub instance.',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
} as const
|
||||
|
||||
export function PrivacyPolicyPage() {
|
||||
const { i18n } = useTranslation()
|
||||
const language = i18n.resolvedLanguage?.split('-')[0] === 'zh' ? 'zh' : 'en'
|
||||
|
||||
return <LegalDocument {...privacyDocuments[language]} />
|
||||
}
|
||||
174
web/src/pages/terms.tsx
Normal file
174
web/src/pages/terms.tsx
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { LegalDocument } from '@/shared/components/legal-document'
|
||||
|
||||
const termsDocuments = {
|
||||
zh: {
|
||||
eyebrow: '法律与政策',
|
||||
title: '服务条款',
|
||||
summary: '本条款适用于你访问和使用 SkillHub 提供的技能浏览、发布、审核、下载、账号管理和 API 相关服务。使用本服务即表示你同意受本条款约束。',
|
||||
lastUpdated: '最后更新:2026年3月14日',
|
||||
note: '如果你所在组织使用的是私有部署版 SkillHub,该组织还可以在本条款之外增加自己的内部规范、信息安全要求或可接受使用政策。',
|
||||
sections: [
|
||||
{
|
||||
title: '1. 接受条款与适用范围',
|
||||
paragraphs: [
|
||||
'当你访问、注册或使用 SkillHub 时,即表示你同意本服务条款以及与之相关的隐私政策和实例运营规则。',
|
||||
'如果你代表团队、公司或其他组织使用 SkillHub,你确认自己有权代表该主体接受这些条款。',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '2. 账号与访问安全',
|
||||
paragraphs: [],
|
||||
bullets: [
|
||||
'你应提供真实、完整且最新的注册或登录信息,并妥善保护自己的账号、密码、OAuth 会话和 API Token。',
|
||||
'你需对通过自己账号发生的活动负责,包括发布、审核、下载、生成令牌和管理命名空间的操作。',
|
||||
'如果你发现未授权访问、凭据泄露或其他安全事件,应及时通知实例管理员或运营方。',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '3. 技能、命名空间与用户内容',
|
||||
paragraphs: [
|
||||
'你保留对自己上传或提交内容的权利,但你授予 SkillHub 一项非独占、全球性、可再许可的许可,以便在提供服务所必需的范围内托管、存储、复制、处理、展示和分发这些内容。',
|
||||
'你承诺对自己提交的技能包、README、描述、截图、审核意见和其他材料负责,并保证你有权上传这些内容。',
|
||||
],
|
||||
bullets: [
|
||||
'不得上传恶意代码、违法内容、侵权内容、欺诈性内容或故意误导用户的材料。',
|
||||
'不得冒用他人身份、未经授权占用命名空间,或规避平台的审核与访问控制机制。',
|
||||
'下载和再分发技能时,你还必须遵守该技能自身的许可证、第三方依赖许可证和适用法律。',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '4. 审核、治理与执行',
|
||||
paragraphs: [
|
||||
'SkillHub 可对发布内容执行审核、隐藏、下架、拒绝、撤回版本、限制访问或暂停账号等治理动作,以维护平台安全、合规和服务质量。',
|
||||
'当存在滥用、侵权、安全风险、违法行为或违反本条款的情况时,平台或实例管理员可保留相关日志并采取必要处置。',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '5. 下载、API 与可接受使用',
|
||||
paragraphs: [],
|
||||
bullets: [
|
||||
'你不得干扰服务稳定性,不得绕过鉴权、限流、安全策略或未获授权访问他人资源。',
|
||||
'你不得以破坏性方式抓取、压测、扫描或自动化调用 SkillHub,也不得借助平台传播病毒、木马或其他恶意负载。',
|
||||
'通过 SkillHub 下载的技能由提供者负责,并按其各自的许可证和风险提示使用;你应自行评估兼容性、安全性和合规性。',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '6. 开源组件、知识产权与品牌',
|
||||
paragraphs: [
|
||||
'SkillHub 可能包含受 Apache License 2.0 或其他第三方许可证约束的开源组件,这些组件仍分别受其自身许可证管理。',
|
||||
'除非明确说明,SkillHub 的商标、品牌元素、界面设计和站点内容仍归相应权利人所有,不因你使用服务而转让。',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '7. 服务可用性与免责声明',
|
||||
paragraphs: [
|
||||
'我们可以随时调整、更新、限制或中断部分功能,包括搜索、下载、审核、登录方式和 API 能力,且不保证任何功能持续可用。',
|
||||
'在适用法律允许的范围内,SkillHub 按“现状”和“可用”提供,不对适销性、特定用途适用性、不中断、无错误或内容准确性作出明示或默示保证。',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '8. 责任限制',
|
||||
paragraphs: [
|
||||
'在适用法律允许的最大范围内,SkillHub 及其运营方不对任何间接、附带、特殊、后果性损害或利润、数据、商誉损失承担责任。',
|
||||
'如果你的使用、内容或违规行为导致第三方向 SkillHub 或实例运营方提出索赔,你同意在法律允许范围内承担相应责任并配合处理。',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '9. 终止、变更与联系',
|
||||
paragraphs: [
|
||||
'你可以随时停止使用 SkillHub。我们也可以在你违反条款、造成安全风险或法律要求时暂停或终止你对服务的访问。',
|
||||
'我们可能更新本条款。更新后继续使用服务即表示你接受修订版本。如需联系,请使用当前实例提供的文档、社区或管理员渠道。',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
en: {
|
||||
eyebrow: 'Legal',
|
||||
title: 'Terms of Service',
|
||||
summary: 'These terms apply to your access to and use of SkillHub for browsing, publishing, reviewing, downloading, account management, and related API services. By using the service, you agree to these terms.',
|
||||
lastUpdated: 'Last updated: March 14, 2026',
|
||||
note: 'If your organization runs a private SkillHub deployment, it may impose additional internal rules, information security requirements, or acceptable use policies on top of these baseline terms.',
|
||||
sections: [
|
||||
{
|
||||
title: '1. Acceptance and Scope',
|
||||
paragraphs: [
|
||||
'By accessing, registering for, or using SkillHub, you agree to these Terms of Service, the related Privacy Policy, and any operating rules for the current instance.',
|
||||
'If you use SkillHub on behalf of a team, company, or other organization, you represent that you have authority to accept these terms on its behalf.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '2. Accounts and Access Security',
|
||||
paragraphs: [],
|
||||
bullets: [
|
||||
'You must provide accurate and current account information and protect your credentials, OAuth sessions, and API tokens.',
|
||||
'You are responsible for activity performed through your account, including publishing, reviewing, downloading, token generation, and namespace administration.',
|
||||
'If you become aware of unauthorized access, credential leakage, or another security incident, you must promptly notify the instance administrator or operator.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '3. Skills, Namespaces, and User Content',
|
||||
paragraphs: [
|
||||
'You retain rights in content you upload or submit, but you grant SkillHub a non-exclusive, worldwide, sublicensable license to host, store, copy, process, display, and distribute that content as needed to operate the service.',
|
||||
'You are responsible for the skill packages, README files, descriptions, screenshots, review comments, and other materials you submit, and you represent that you have the right to provide them.',
|
||||
],
|
||||
bullets: [
|
||||
'Do not upload malware, unlawful material, infringing material, deceptive material, or content intended to mislead users.',
|
||||
'Do not impersonate others, take namespaces without authorization, or attempt to bypass review, moderation, or access control mechanisms.',
|
||||
'When downloading or redistributing a skill, you must also comply with that skill’s own license terms, third-party dependency licenses, and applicable law.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '4. Review, Governance, and Enforcement',
|
||||
paragraphs: [
|
||||
'SkillHub may review content and may approve, reject, hide, remove, yank versions, restrict access, or suspend accounts to protect service security, compliance, and quality.',
|
||||
'Where abuse, infringement, security risk, unlawful conduct, or other violations are suspected, the platform or instance administrator may preserve logs and take appropriate action.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '5. Downloads, APIs, and Acceptable Use',
|
||||
paragraphs: [],
|
||||
bullets: [
|
||||
'You may not interfere with service stability or bypass authentication, rate limits, security controls, or authorization boundaries.',
|
||||
'You may not scrape, stress test, scan, or automate against SkillHub in a destructive manner, or use the service to distribute viruses, trojans, or other malicious payloads.',
|
||||
'Skills downloaded through SkillHub are provided by their publishers and are used subject to their own licenses and risk notices. You are responsible for evaluating compatibility, security, and compliance.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '6. Open Source Components, Intellectual Property, and Branding',
|
||||
paragraphs: [
|
||||
'SkillHub may include open-source components governed by Apache License 2.0 or other third-party licenses, and those components remain subject to their respective license terms.',
|
||||
'Unless explicitly stated otherwise, SkillHub trademarks, brand assets, interface design, and site content remain the property of their respective owners and are not transferred by your use of the service.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '7. Availability and Disclaimers',
|
||||
paragraphs: [
|
||||
'We may modify, update, limit, or discontinue parts of the service at any time, including search, downloads, review workflows, login methods, and API capabilities, and we do not guarantee continuous availability.',
|
||||
'To the maximum extent permitted by law, SkillHub is provided on an "as is" and "as available" basis without express or implied warranties, including warranties of merchantability, fitness for a particular purpose, non-infringement, uninterrupted availability, or accuracy.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '8. Limitation of Liability',
|
||||
paragraphs: [
|
||||
'To the maximum extent permitted by law, SkillHub and its operators will not be liable for indirect, incidental, special, consequential, or punitive damages, or for any loss of profits, data, or goodwill.',
|
||||
'If claims arise from your use of the service, your content, or your violation of these terms, you agree to bear responsibility as permitted by law and to cooperate in resolving the matter.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '9. Termination, Changes, and Contact',
|
||||
paragraphs: [
|
||||
'You may stop using SkillHub at any time. We may suspend or terminate access if you violate these terms, create security risk, or if required by law.',
|
||||
'We may update these terms from time to time. Your continued use after an update means you accept the revised version. If you need to contact us, use the documentation, community, or administrator channel provided by the current instance.',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
} as const
|
||||
|
||||
export function TermsOfServicePage() {
|
||||
const { i18n } = useTranslation()
|
||||
const language = i18n.resolvedLanguage?.split('-')[0] === 'zh' ? 'zh' : 'en'
|
||||
|
||||
return <LegalDocument {...termsDocuments[language]} />
|
||||
}
|
||||
69
web/src/shared/components/legal-document.tsx
Normal file
69
web/src/shared/components/legal-document.tsx
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import { Card, CardContent, CardHeader, CardTitle } from '@/shared/ui/card'
|
||||
|
||||
interface LegalSection {
|
||||
title: string
|
||||
paragraphs: readonly string[]
|
||||
bullets?: readonly string[]
|
||||
}
|
||||
|
||||
interface LegalDocumentProps {
|
||||
eyebrow: string
|
||||
title: string
|
||||
summary: string
|
||||
lastUpdated: string
|
||||
note?: string
|
||||
sections: readonly LegalSection[]
|
||||
}
|
||||
|
||||
export function LegalDocument({
|
||||
eyebrow,
|
||||
title,
|
||||
summary,
|
||||
lastUpdated,
|
||||
note,
|
||||
sections,
|
||||
}: LegalDocumentProps) {
|
||||
return (
|
||||
<div className="mx-auto max-w-4xl space-y-8 animate-fade-up">
|
||||
<div className="space-y-4">
|
||||
<div className="inline-flex items-center rounded-full border border-primary/20 bg-primary/10 px-3 py-1 text-xs font-semibold uppercase tracking-[0.2em] text-primary">
|
||||
{eyebrow}
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<h1 className="text-4xl font-bold font-heading text-foreground md:text-5xl">{title}</h1>
|
||||
<p className="max-w-3xl text-lg leading-relaxed text-muted-foreground">{summary}</p>
|
||||
<p className="text-sm text-muted-foreground">{lastUpdated}</p>
|
||||
</div>
|
||||
{note ? (
|
||||
<div className="rounded-2xl border border-border/60 bg-card/60 px-5 py-4 text-sm leading-6 text-muted-foreground">
|
||||
{note}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{sections.map((section) => (
|
||||
<Card key={section.title} className="glass-strong">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl font-heading">{section.title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{section.paragraphs.map((paragraph) => (
|
||||
<p key={paragraph} className="text-sm leading-7 text-muted-foreground">
|
||||
{paragraph}
|
||||
</p>
|
||||
))}
|
||||
{section.bullets ? (
|
||||
<ul className="list-disc space-y-2 pl-5 text-sm leading-7 text-muted-foreground">
|
||||
{section.bullets.map((bullet) => (
|
||||
<li key={bullet}>{bullet}</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue