feat(aether): initial commit – self-hosted Cloud, Phasen 1–7
Go-Backend (chi, pgx, golang-migrate) + React/TS-Frontend als PWA. Enthalten: Auth inkl. OIDC, Dateien mit Versionen und Papierkorb, Freigaben (Links + intern), WebDAV, Gruppenordner, Admin/Theming, Volltextsuche mit OCR, eigener PDF-Betrachter und WOPI-Office (Collabora/OnlyOffice). Nicht im Repo: deploy/.env (Sitzungsschlüssel, DB- und Admin-Passwort) sowie alle Daten- und Build-Verzeichnisse – siehe .gitignore. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
31a027c93a
225 files changed
+38042
No files matched your search
@@ -0,0 +1,63 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useAuth } from './hooks/useAuth'
|
||||
import { AppSettingsProvider, useAppSettings } from './hooks/useAppSettings'
|
||||
import { applyTheme } from './theme'
|
||||
import LoginPage from './pages/LoginPage'
|
||||
import Shell from './components/Shell'
|
||||
import SharePage from './pages/SharePage'
|
||||
|
||||
// Simple path-based routing without react-router
|
||||
function getShareToken(): string | null {
|
||||
const match = window.location.pathname.match(/^\/s\/([^/]+)/)
|
||||
return match ? match[1] : null
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const shareToken = getShareToken()
|
||||
|
||||
// Name und Farben umschliessen auch die Share-Seite: sie ist die einzige
|
||||
// Ansicht, die Fremde zu Gesicht bekommen.
|
||||
return (
|
||||
<AppSettingsProvider>
|
||||
{shareToken ? <SharePage token={shareToken} /> : <AuthedApp />}
|
||||
</AppSettingsProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function AuthedApp() {
|
||||
const { user, loading, login, logout, refreshUser } = useAuth()
|
||||
const { globalColors } = useAppSettings()
|
||||
|
||||
// Die persönliche Palette gilt nur, wenn der Nutzer sie eingeschaltet hat –
|
||||
// sonst die des Administrators. Läuft bei jeder Änderung am Nutzer erneut,
|
||||
// damit ein Umschalten im Profil sofort überall greift.
|
||||
useEffect(() => {
|
||||
if (!user) return
|
||||
if (user.use_custom_theme) {
|
||||
applyTheme(
|
||||
user.primary_color || globalColors.primary,
|
||||
user.accent_color || globalColors.accent,
|
||||
)
|
||||
} else {
|
||||
applyTheme(globalColors.primary, globalColors.accent)
|
||||
}
|
||||
}, [user, globalColors])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div style={{
|
||||
width: 32, height: 32,
|
||||
border: '2px solid var(--color-primary)',
|
||||
borderTopColor: 'transparent',
|
||||
borderRadius: '50%',
|
||||
animation: 'spin 0.7s linear infinite',
|
||||
}} />
|
||||
<style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!user) return <LoginPage onLogin={login} />
|
||||
return <Shell user={user} onLogout={logout} onUserChanged={refreshUser} />
|
||||
}
|
||||
Reference in new issue
Block a user