2026-01-27 16:21:58 +00:00
|
|
|
---
|
|
|
|
|
export const prerender = false;
|
|
|
|
|
import Layout from '../../../layouts/Layout.astro';
|
2026-01-28 23:47:14 +00:00
|
|
|
import { API_BASE as apiBase } from '../../../lib/api';
|
2026-01-27 16:21:58 +00:00
|
|
|
|
|
|
|
|
const { slug } = Astro.params;
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
<Layout title="Community Settings">
|
|
|
|
|
<div class="settings-container">
|
|
|
|
|
<header class="settings-header">
|
|
|
|
|
<a href={`/communities/${slug}`} class="back-link">← 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>
|
|
|
|
|
|
2026-01-30 08:50:02 +00:00
|
|
|
<form id="settings-form" class="ui-form" style="--ui-form-control-max-width: 400px; display: none;">
|
2026-01-27 16:21:58 +00:00
|
|
|
<!-- Membership -->
|
2026-01-30 08:50:02 +00:00
|
|
|
<section class="settings-section ui-card ui-card-soft ui-card-pad-md">
|
2026-01-27 16:21:58 +00:00
|
|
|
<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 -->
|
2026-01-30 08:50:02 +00:00
|
|
|
<section class="settings-section ui-card ui-card-soft ui-card-pad-md">
|
2026-01-27 16:21:58 +00:00
|
|
|
<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 -->
|
2026-01-30 08:50:02 +00:00
|
|
|
<section class="settings-section ui-card ui-card-soft ui-card-pad-md">
|
2026-01-27 16:21:58 +00:00
|
|
|
<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 -->
|
2026-01-30 08:50:02 +00:00
|
|
|
<section class="settings-section ui-card ui-card-soft ui-card-pad-md">
|
2026-01-27 16:21:58 +00:00
|
|
|
<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 →</a>
|
|
|
|
|
</p>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<div class="form-actions">
|
2026-01-30 08:50:02 +00:00
|
|
|
<button type="submit" id="save-btn" class="ui-btn ui-btn-primary">Save Settings</button>
|
2026-01-27 16:21:58 +00:00
|
|
|
<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 {
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#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>
|
|
|
|
|
|
2026-01-28 23:47:14 +00:00
|
|
|
<script define:vars={{ slug, apiBase }}>
|
|
|
|
|
const API_BASE = apiBase;
|
2026-01-27 16:21:58 +00:00
|
|
|
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>
|