Private
Public Access
Add an About section to the account menu (#51)
Shows the app version, links the AGPL-3.0-or-later license text, and
links the source repo -- AGPL's own suggested-usage text recommends
exactly this ("if your software can interact with users remotely...
its interface could display a 'Source' link"), not just a courtesy
credits screen.
Version comes from package.json at build time via a Vite `define`
(__APP_VERSION__), so it can't drift from what's actually released.
License text is served at /LICENSE via a frontend/public/ symlink to
the repo-root LICENSE, the same pattern already used for the user
guide. Also bumps both package manifests to 1.0.0 ahead of tagging
the first release.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "ds-chat"
|
name = "ds-chat"
|
||||||
version = "0.1.0"
|
version = "1.0.0"
|
||||||
description = "DS Chat backend service"
|
description = "DS Chat backend service"
|
||||||
license = { text = "AGPL-3.0-or-later" }
|
license = { text = "AGPL-3.0-or-later" }
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "1.0.0",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
../../LICENSE
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
.about-modal-brand {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--sp-3);
|
||||||
|
margin-bottom: var(--sp-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-modal-logo {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-modal-name {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-modal-version {
|
||||||
|
color: var(--ds-muted);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.about-modal-line {
|
||||||
|
margin: 0 0 var(--sp-2);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import logo from '../assets/logo.png'
|
||||||
|
import './Modal.css'
|
||||||
|
import './AboutModal.css'
|
||||||
|
|
||||||
|
interface AboutModalProps {
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
// AGPL-3.0 itself recommends this: "if your software can interact with
|
||||||
|
// users remotely through a computer network, you should also make sure
|
||||||
|
// that it provides a way for users to get its source... its interface
|
||||||
|
// could display a 'Source' link" -- this modal is that link, not just a
|
||||||
|
// courtesy credits screen.
|
||||||
|
const SOURCE_URL = 'https://git.darksingularity.org/DarkSingularity/ds-chat'
|
||||||
|
|
||||||
|
export function AboutModal({ onClose }: AboutModalProps) {
|
||||||
|
return (
|
||||||
|
<div className="modal-scrim" onClick={onClose}>
|
||||||
|
<div className="modal about-modal" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div className="modal-header">
|
||||||
|
<h2>About</h2>
|
||||||
|
<button type="button" className="modal-close" onClick={onClose} aria-label="Close">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="about-modal-brand">
|
||||||
|
<img src={logo} alt="" className="about-modal-logo" />
|
||||||
|
<div>
|
||||||
|
<div className="about-modal-name">DS Chat</div>
|
||||||
|
<div className="about-modal-version">Version {__APP_VERSION__}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="about-modal-line">
|
||||||
|
Licensed under the{' '}
|
||||||
|
<a href="/LICENSE" target="_blank" rel="noopener noreferrer">
|
||||||
|
GNU Affero General Public License v3.0 (or later)
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<p className="about-modal-line">
|
||||||
|
<a href={SOURCE_URL} target="_blank" rel="noopener noreferrer">
|
||||||
|
Source code
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="modal-actions" style={{ marginTop: '1rem' }}>
|
||||||
|
<button type="button" className="btn-secondary" onClick={onClose}>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
setDesktopNotificationsEnabled,
|
setDesktopNotificationsEnabled,
|
||||||
} from '../lib/desktopBridge'
|
} from '../lib/desktopBridge'
|
||||||
import { getPushSubscriptionStatus, isPushSupported, subscribeToPush, unsubscribeFromPush } from '../lib/push'
|
import { getPushSubscriptionStatus, isPushSupported, subscribeToPush, unsubscribeFromPush } from '../lib/push'
|
||||||
|
import { AboutModal } from './AboutModal'
|
||||||
import { ProfileModal } from './ProfileModal'
|
import { ProfileModal } from './ProfileModal'
|
||||||
import { UserAvatar } from './UserAvatar'
|
import { UserAvatar } from './UserAvatar'
|
||||||
import './TopBar.css'
|
import './TopBar.css'
|
||||||
@@ -27,6 +28,7 @@ export function TopBar() {
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [menuOpen, setMenuOpen] = useState(false)
|
const [menuOpen, setMenuOpen] = useState(false)
|
||||||
const [profileModalOpen, setProfileModalOpen] = useState(false)
|
const [profileModalOpen, setProfileModalOpen] = useState(false)
|
||||||
|
const [aboutModalOpen, setAboutModalOpen] = useState(false)
|
||||||
const [pushSubscribed, setPushSubscribed] = useState(false)
|
const [pushSubscribed, setPushSubscribed] = useState(false)
|
||||||
const [pushBusy, setPushBusy] = useState(false)
|
const [pushBusy, setPushBusy] = useState(false)
|
||||||
const [pushError, setPushError] = useState<string | null>(null)
|
const [pushError, setPushError] = useState<string | null>(null)
|
||||||
@@ -132,6 +134,16 @@ export function TopBar() {
|
|||||||
>
|
>
|
||||||
Help
|
Help
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="menuitem"
|
||||||
|
onClick={() => {
|
||||||
|
setMenuOpen(false)
|
||||||
|
setAboutModalOpen(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
About
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
@@ -178,6 +190,7 @@ export function TopBar() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{profileModalOpen && <ProfileModal onClose={() => setProfileModalOpen(false)} />}
|
{profileModalOpen && <ProfileModal onClose={() => setProfileModalOpen(false)} />}
|
||||||
|
{aboutModalOpen && <AboutModal onClose={() => setAboutModalOpen(false)} />}
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+3
@@ -1,2 +1,5 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
/// <reference types="vite-plugin-pwa/react" />
|
/// <reference types="vite-plugin-pwa/react" />
|
||||||
|
|
||||||
|
// Injected by vite.config.ts's `define`, from package.json's version field.
|
||||||
|
declare const __APP_VERSION__: string
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
|
import { readFileSync } from 'node:fs'
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
import { VitePWA } from 'vite-plugin-pwa'
|
import { VitePWA } from 'vite-plugin-pwa'
|
||||||
|
|
||||||
|
// Single source of truth for the version shown in the app's About dialog --
|
||||||
|
// read at build time so it can never drift from what's actually released,
|
||||||
|
// rather than a separately-maintained string in the component itself.
|
||||||
|
const { version } = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf-8'))
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
define: {
|
||||||
|
__APP_VERSION__: JSON.stringify(version),
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
VitePWA({
|
VitePWA({
|
||||||
|
|||||||
Reference in New Issue
Block a user