likwid/frontend/src/pages/communities/[slug]/settings.astro

338 lines
9.4 KiB
Text
Raw Normal View History

---
export const prerender = false;
import Layout from '../../../layouts/Layout.astro';
import { API_BASE as apiBase } from '../../../lib/api';
const { slug } = Astro.params;
---
<Layout title="Community Settings">
<div class="settings-container">
<header class="settings-header">
<a href={`/communities/${slug}`} class="back-link">&larr; Back to Community</a>
<h1>Community Settings</h1>
<p>Configure settings for this community</p>
</header>
<div id="loading">Loading settings...</div>
<div id="error" class="error-box" style="display: none;"></div>
<div id="no-access" class="error-box" style="display: none;">
You don't have permission to manage this community's settings.
</div>
<form id="settings-form" style="display: none;">
<!-- Membership -->
<section class="settings-section">
<h2>Membership</h2>
<div class="form-group">
<label for="membership_mode">Membership Mode</label>
<select id="membership_mode" name="membership_mode">
<option value="open">Open - Anyone can join</option>
<option value="approval">Approval Required - Requests need approval</option>
<option value="invite_only">Invite Only - Members must be invited</option>
<option value="closed">Closed - No new members</option>
</select>
</div>
</section>
<!-- Moderation -->
<section class="settings-section">
<h2>Moderation</h2>
<div class="form-group">
<label for="moderation_mode">Moderation Mode</label>
<select id="moderation_mode" name="moderation_mode">
<option value="community">Community - Members can flag, admins review</option>
<option value="centralized">Centralized - Only admins moderate</option>
<option value="democratic">Democratic - Community votes on moderation</option>
<option value="automated">Automated - Plugin-based moderation</option>
</select>
</div>
</section>
<!-- Governance -->
<section class="settings-section">
<h2>Governance</h2>
<div class="form-group">
<label for="governance_model">Governance Model</label>
<select id="governance_model" name="governance_model">
<option value="simple_majority">Simple Majority - 50%+ wins</option>
<option value="supermajority">Supermajority - 66%+ required</option>
<option value="consensus">Consensus - Near-unanimous required</option>
<option value="liquid">Liquid Democracy - Delegated voting</option>
<option value="quadratic">Quadratic Voting - Weighted by stake</option>
</select>
</div>
</section>
<!-- Plugins -->
<section class="settings-section">
<h2>Plugins</h2>
<div class="form-group">
<label for="plugin_policy">Plugin Policy</label>
<select id="plugin_policy" name="plugin_policy">
<option value="inherit">Inherit from Instance</option>
<option value="permissive">Permissive - All plugins allowed</option>
<option value="curated">Curated - Only approved plugins</option>
<option value="strict">Strict - Signed plugins only</option>
<option value="disabled">Disabled - No plugins</option>
</select>
</div>
<p class="hint">
<a href={`/communities/${slug}/plugins`}>Manage installed plugins &rarr;</a>
</p>
</section>
<div class="form-actions">
<button type="submit" id="save-btn">Save Settings</button>
<span id="save-status"></span>
</div>
</form>
</div>
</Layout>
<style>
.settings-container {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
}
.settings-header {
margin-bottom: 2rem;
}
.back-link {
color: var(--color-text-muted);
text-decoration: none;
font-size: 0.9rem;
}
.back-link:hover {
color: var(--color-primary);
}
.settings-header h1 {
margin: 0.5rem 0 0.25rem 0;
color: var(--color-text);
}
.settings-header p {
margin: 0;
color: var(--color-text-muted);
}
.settings-section {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.settings-section h2 {
margin: 0 0 1rem 0;
font-size: 1.1rem;
color: var(--color-text);
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--color-border);
}
.form-group {
margin-bottom: 1rem;
}
.form-group:last-child {
margin-bottom: 0;
}
.form-group > label {
display: block;
font-weight: 500;
margin-bottom: 0.5rem;
color: var(--color-text);
}
.form-group select {
width: 100%;
max-width: 400px;
padding: 0.75rem;
border: 1px solid var(--color-border);
border-radius: 8px;
font-size: 1rem;
background: var(--color-bg);
color: var(--color-text);
}
.form-group select:focus {
outline: none;
border-color: var(--color-primary);
}
.hint {
font-size: 0.9rem;
color: var(--color-text-muted);
margin-top: 1rem;
}
.hint a {
color: var(--color-primary);
}
.form-actions {
display: flex;
align-items: center;
gap: 1rem;
margin-top: 1rem;
}
.form-actions button {
padding: 0.75rem 2rem;
background: var(--color-primary);
color: var(--color-text-inverse);
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: opacity 0.2s;
}
.form-actions button:hover {
opacity: 0.9;
}
.form-actions button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
#save-status {
color: var(--color-success);
font-weight: 500;
}
.error-box {
background: var(--color-error-muted);
color: var(--color-error);
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
}
#loading {
color: var(--color-text-muted);
padding: 2rem;
text-align: center;
}
</style>
<script define:vars={{ slug, apiBase }}>
const API_BASE = apiBase;
const form = document.getElementById('settings-form');
const loadingEl = document.getElementById('loading');
const errorEl = document.getElementById('error');
const noAccessEl = document.getElementById('no-access');
const saveBtn = document.getElementById('save-btn');
const saveStatus = document.getElementById('save-status');
let communityId = null;
async function loadSettings() {
const token = localStorage.getItem('token');
if (!token) {
window.location.href = '/login';
return;
}
try {
// First get community ID from slug
const commRes = await fetch(`${API_BASE}/api/communities`);
if (!commRes.ok) throw new Error('Failed to load communities');
const communities = await commRes.json();
const community = communities.find(c => c.slug === slug);
if (!community) {
errorEl.textContent = 'Community not found';
errorEl.style.display = 'block';
loadingEl.style.display = 'none';
return;
}
communityId = community.id;
// Load settings
const res = await fetch(`${API_BASE}/api/settings/communities/${communityId}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (res.status === 403) {
noAccessEl.style.display = 'block';
loadingEl.style.display = 'none';
return;
}
if (!res.ok) throw new Error(await res.text());
const settings = await res.json();
document.getElementById('membership_mode').value = settings.membership_mode;
document.getElementById('moderation_mode').value = settings.moderation_mode;
document.getElementById('governance_model').value = settings.governance_model;
document.getElementById('plugin_policy').value = settings.plugin_policy;
loadingEl.style.display = 'none';
form.style.display = 'block';
} catch (err) {
errorEl.textContent = 'Failed to load settings: ' + err.message;
errorEl.style.display = 'block';
loadingEl.style.display = 'none';
}
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
const token = localStorage.getItem('token');
if (!token || !communityId) return;
saveBtn.disabled = true;
saveStatus.textContent = 'Saving...';
const data = {
membership_mode: document.getElementById('membership_mode').value,
moderation_mode: document.getElementById('moderation_mode').value,
governance_model: document.getElementById('governance_model').value,
plugin_policy: document.getElementById('plugin_policy').value
};
try {
const res = await fetch(`${API_BASE}/api/settings/communities/${communityId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(data)
});
if (!res.ok) throw new Error(await res.text());
saveStatus.textContent = 'Saved!';
saveStatus.style.color = 'var(--color-success)';
setTimeout(() => { saveStatus.textContent = ''; }, 3000);
} catch (err) {
saveStatus.textContent = 'Error: ' + err.message;
saveStatus.style.color = 'var(--color-error)';
} finally {
saveBtn.disabled = false;
}
});
loadSettings();
</script>