mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-20 23:32:19 +00:00
docs: relicense Sencho to AGPLv3 and reframe Community positioning (#1623)
* docs: relicense Sencho to AGPLv3 and reframe Community positioning Replace BSL with AGPLv3 for the public Community product, update contributor and licensing copy for Community-focused contributions, and surface source and license links in Settings About. * docs: clarify CLA scope and Community contribution framing * fix(ui): split About link constants and harden AboutSection test selectors
This commit is contained in:
@@ -174,7 +174,7 @@ export function Setup({ onComplete, className, ...props }: SetupProps & React.Co
|
||||
<AuthStepHeader
|
||||
kicker="SENCHO · INITIALIZE"
|
||||
hero="Cold start"
|
||||
caption="Create the Admiral account to unlock the console."
|
||||
caption="Create the initial administrator account for this control plane."
|
||||
/>
|
||||
|
||||
<form onSubmit={handleSubmit} className="flex flex-col gap-5">
|
||||
|
||||
@@ -2,6 +2,15 @@ import { useLicense } from '@/context/LicenseContext';
|
||||
import { TierBadge } from '@/components/TierBadge';
|
||||
import { SettingsSection } from './SettingsSection';
|
||||
import { SettingsField } from './SettingsField';
|
||||
import {
|
||||
SOURCE_URL,
|
||||
LICENSE_URL,
|
||||
LICENSING_DOCS_URL,
|
||||
CHANGELOG_URL,
|
||||
} from './aboutLinks';
|
||||
|
||||
const linkClassName =
|
||||
'font-mono text-[10px] leading-3 uppercase tracking-[0.18em] text-brand hover:text-brand/80 transition-colors';
|
||||
|
||||
export function AboutSection() {
|
||||
const { license } = useLicense();
|
||||
@@ -15,7 +24,7 @@ export function AboutSection() {
|
||||
<SettingsField label="Tier">
|
||||
<TierBadge />
|
||||
</SettingsField>
|
||||
<SettingsField label="License status">
|
||||
<SettingsField label="Plan status">
|
||||
<span className="font-mono text-[10px] leading-3 uppercase tracking-[0.18em] text-stat-value">
|
||||
{license?.status ?? 'community'}
|
||||
</span>
|
||||
@@ -33,17 +42,56 @@ export function AboutSection() {
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection title="Links">
|
||||
<SettingsField
|
||||
label="Source code"
|
||||
helper="Browse the AGPLv3 repository on GitHub."
|
||||
>
|
||||
<a
|
||||
href={SOURCE_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={linkClassName}
|
||||
>
|
||||
github.com/studio-saelix/sencho →
|
||||
</a>
|
||||
</SettingsField>
|
||||
<SettingsField
|
||||
label="AGPLv3 License"
|
||||
helper="Copyright (c) 2026 Studio Saelix. Full license text."
|
||||
>
|
||||
<a
|
||||
href={LICENSE_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={linkClassName}
|
||||
>
|
||||
LICENSE →
|
||||
</a>
|
||||
</SettingsField>
|
||||
<SettingsField
|
||||
label="Licensing documentation"
|
||||
helper="Community and Admiral plans."
|
||||
>
|
||||
<a
|
||||
href={LICENSING_DOCS_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={linkClassName}
|
||||
>
|
||||
docs.sencho.io/features/licensing →
|
||||
</a>
|
||||
</SettingsField>
|
||||
<SettingsField
|
||||
label="Changelog"
|
||||
helper="See what shipped, when, and why."
|
||||
>
|
||||
<a
|
||||
href="https://github.com/studio-saelix/sencho/blob/main/CHANGELOG.md"
|
||||
href={CHANGELOG_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-mono text-[10px] leading-3 uppercase tracking-[0.18em] text-brand hover:text-brand/80 transition-colors"
|
||||
className={linkClassName}
|
||||
>
|
||||
github.com/studio-saelix/sencho →
|
||||
CHANGELOG.md →
|
||||
</a>
|
||||
</SettingsField>
|
||||
</SettingsSection>
|
||||
|
||||
@@ -93,14 +93,14 @@ export function LicenseSection() {
|
||||
label={getTierDisplayName(license?.tier, license?.status)}
|
||||
helper={
|
||||
license?.status === 'expired'
|
||||
? 'Your license has expired. Renew to restore paid features.'
|
||||
? 'Your Admiral license has expired. Renew to restore Admiral plan benefits.'
|
||||
: license?.status === 'disabled'
|
||||
? 'Your license has been disabled. Contact support for assistance.'
|
||||
: license?.status === 'trial' && license.trialDaysRemaining !== null
|
||||
? `Trial: ${license.trialDaysRemaining} day${license.trialDaysRemaining !== 1 ? 's' : ''} remaining.`
|
||||
: isPaid
|
||||
? 'Active license on this control plane.'
|
||||
: 'Free tier with the core experience.'
|
||||
: 'Community plan. Full AGPLv3 self-hosted control plane.'
|
||||
}
|
||||
tone={
|
||||
license?.status === 'expired' || license?.status === 'disabled'
|
||||
@@ -137,7 +137,7 @@ export function LicenseSection() {
|
||||
{license?.status === 'expired' ? (
|
||||
<SettingsField
|
||||
label="Status"
|
||||
helper="Renew to restore paid features."
|
||||
helper="Renew to restore Admiral plan benefits."
|
||||
tone="error"
|
||||
>
|
||||
<div className="flex items-center gap-2 text-destructive">
|
||||
@@ -150,7 +150,7 @@ export function LicenseSection() {
|
||||
{license?.status === 'trial' && license.trialDaysRemaining !== null ? (
|
||||
<SettingsField
|
||||
label="Trial countdown"
|
||||
helper="Activate before the trial ends to keep paid features."
|
||||
helper="Activate before the trial ends to keep Admiral plan benefits."
|
||||
tone="warn"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { describe, it, expect, vi, beforeAll } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { AboutSection } from '../AboutSection';
|
||||
import { ABOUT_LINK_URLS } from '../aboutLinks';
|
||||
|
||||
beforeAll(() => {
|
||||
// Vite injects this at build time; tests need a stand-in.
|
||||
(globalThis as unknown as { __APP_VERSION__: string }).__APP_VERSION__ = '0.0.0-test';
|
||||
});
|
||||
|
||||
vi.mock('@/context/LicenseContext', () => ({
|
||||
useLicense: () => ({
|
||||
license: {
|
||||
tier: 'community',
|
||||
status: 'community',
|
||||
customerName: null,
|
||||
productName: null,
|
||||
maskedKey: null,
|
||||
validUntil: null,
|
||||
trialDaysRemaining: null,
|
||||
instanceId: 'abcdef0123456789',
|
||||
portalUrl: null,
|
||||
isLifetime: false,
|
||||
},
|
||||
isPaid: false,
|
||||
loading: false,
|
||||
licenseStatus: 'ready',
|
||||
licenseReady: true,
|
||||
refresh: vi.fn(),
|
||||
activate: vi.fn(),
|
||||
deactivate: vi.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@/components/TierBadge', () => ({
|
||||
TierBadge: () => <span>Community</span>,
|
||||
}));
|
||||
|
||||
describe('AboutSection', () => {
|
||||
it('renders Plan status and Source, License, and Licensing docs links with exact URLs', () => {
|
||||
render(<AboutSection />);
|
||||
|
||||
expect(screen.getByText('Plan status')).toBeTruthy();
|
||||
expect(screen.queryByText('License status')).toBeNull();
|
||||
|
||||
const source = screen.getByRole('link', { name: 'github.com/studio-saelix/sencho →' });
|
||||
expect(source.getAttribute('href')).toBe(ABOUT_LINK_URLS.source);
|
||||
expect(source.getAttribute('target')).toBe('_blank');
|
||||
expect(source.getAttribute('rel')).toBe('noopener noreferrer');
|
||||
|
||||
const license = screen.getByRole('link', { name: 'LICENSE →' });
|
||||
expect(license.getAttribute('href')).toBe(ABOUT_LINK_URLS.license);
|
||||
expect(license.getAttribute('target')).toBe('_blank');
|
||||
expect(license.getAttribute('rel')).toBe('noopener noreferrer');
|
||||
|
||||
const licensingDocs = screen.getByRole('link', {
|
||||
name: 'docs.sencho.io/features/licensing →',
|
||||
});
|
||||
expect(licensingDocs.getAttribute('href')).toBe(ABOUT_LINK_URLS.licensingDocs);
|
||||
expect(licensingDocs.getAttribute('target')).toBe('_blank');
|
||||
expect(licensingDocs.getAttribute('rel')).toBe('noopener noreferrer');
|
||||
|
||||
expect(screen.getByText('Source code')).toBeTruthy();
|
||||
expect(screen.getByText('AGPLv3 License')).toBeTruthy();
|
||||
expect(screen.getByText('Licensing documentation')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
export const SOURCE_URL = 'https://github.com/studio-saelix/sencho';
|
||||
export const LICENSE_URL = 'https://github.com/studio-saelix/sencho/blob/main/LICENSE';
|
||||
export const LICENSING_DOCS_URL = 'https://docs.sencho.io/features/licensing';
|
||||
export const CHANGELOG_URL = 'https://github.com/studio-saelix/sencho/blob/main/CHANGELOG.md';
|
||||
|
||||
export const ABOUT_LINK_URLS = {
|
||||
source: SOURCE_URL,
|
||||
license: LICENSE_URL,
|
||||
licensingDocs: LICENSING_DOCS_URL,
|
||||
changelog: CHANGELOG_URL,
|
||||
} as const;
|
||||
Reference in New Issue
Block a user