mirror of
https://github.com/chenasraf/nextcloud-forum.git
synced 2026-05-17 17:28:02 +00:00
feat: admin settings + forum title/subtitle
This commit is contained in:
@@ -7,6 +7,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace OCA\Forum\Controller;
|
||||
|
||||
use OCA\Forum\AppInfo\Application;
|
||||
use OCA\Forum\Attribute\RequirePermission;
|
||||
use OCA\Forum\Db\CategoryMapper;
|
||||
use OCA\Forum\Db\PostMapper;
|
||||
@@ -19,12 +20,14 @@ use OCP\AppFramework\Http\Attribute\ApiRoute;
|
||||
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\OCSController;
|
||||
use OCP\IConfig;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class AdminController extends OCSController {
|
||||
|
||||
public function __construct(
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
@@ -36,6 +39,7 @@ class AdminController extends OCSController {
|
||||
private UserRoleMapper $userRoleMapper,
|
||||
private IUserManager $userManager,
|
||||
private IUserSession $userSession,
|
||||
private IConfig $config,
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
@@ -154,6 +158,65 @@ class AdminController extends OCSController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get general forum settings
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, array<string, mixed>, array{}>
|
||||
*
|
||||
* 200: Settings returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools')]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/admin/settings')]
|
||||
public function getSettings(): DataResponse {
|
||||
try {
|
||||
$settings = [
|
||||
'title' => $this->config->getAppValue(Application::APP_ID, 'title', 'Forum'),
|
||||
'subtitle' => $this->config->getAppValue(Application::APP_ID, 'subtitle', 'Welcome to the forum'),
|
||||
];
|
||||
|
||||
return new DataResponse($settings);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Error fetching settings: ' . $e->getMessage());
|
||||
return new DataResponse(['error' => 'Failed to fetch settings'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update general forum settings
|
||||
*
|
||||
* @param string|null $title Forum title
|
||||
* @param string|null $subtitle Forum subtitle
|
||||
* @return DataResponse<Http::STATUS_OK, array<string, mixed>, array{}>
|
||||
*
|
||||
* 200: Settings updated
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools')]
|
||||
#[ApiRoute(verb: 'PUT', url: '/api/admin/settings')]
|
||||
public function updateSettings(?string $title = null, ?string $subtitle = null): DataResponse {
|
||||
try {
|
||||
if ($title !== null) {
|
||||
$this->config->setAppValue(Application::APP_ID, 'title', $title);
|
||||
}
|
||||
|
||||
if ($subtitle !== null) {
|
||||
$this->config->setAppValue(Application::APP_ID, 'subtitle', $subtitle);
|
||||
}
|
||||
|
||||
// Return updated settings
|
||||
$settings = [
|
||||
'title' => $this->config->getAppValue(Application::APP_ID, 'title', 'Forum'),
|
||||
'subtitle' => $this->config->getAppValue(Application::APP_ID, 'subtitle', 'Welcome to the forum'),
|
||||
];
|
||||
|
||||
return new DataResponse($settings);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Error updating settings: ' . $e->getMessage());
|
||||
return new DataResponse(['error' => 'Failed to update settings'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has admin role
|
||||
*/
|
||||
|
||||
206
openapi.json
206
openapi.json
@@ -242,6 +242,212 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/admin/settings": {
|
||||
"get": {
|
||||
"operationId": "admin-get-settings",
|
||||
"summary": "Get general forum settings",
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Settings returned",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"operationId": "admin-update-settings",
|
||||
"summary": "Update general forum settings",
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": false,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"default": null,
|
||||
"description": "Forum title"
|
||||
},
|
||||
"subtitle": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"default": null,
|
||||
"description": "Forum subtitle"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Settings updated",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/posts/{postId}/attachments": {
|
||||
"get": {
|
||||
"operationId": "attachment-by-post",
|
||||
|
||||
16
src/App.vue
16
src/App.vue
@@ -130,6 +130,16 @@
|
||||
<CodeBracketsIcon :size="20" />
|
||||
</template>
|
||||
</NcAppNavigationItem>
|
||||
|
||||
<NcAppNavigationItem
|
||||
:name="strings.navAdminSettings"
|
||||
:to="{ path: '/admin/settings' }"
|
||||
:active="isAdminSettingsActive"
|
||||
>
|
||||
<template #icon>
|
||||
<CogIcon :size="20" />
|
||||
</template>
|
||||
</NcAppNavigationItem>
|
||||
</template>
|
||||
</NcAppNavigationItem>
|
||||
</template>
|
||||
@@ -180,6 +190,7 @@ import ShieldAccountIcon from '@icons/ShieldAccount.vue'
|
||||
import ChartLineIcon from '@icons/ChartLine.vue'
|
||||
import AccountMultipleIcon from '@icons/AccountMultiple.vue'
|
||||
import CodeBracketsIcon from '@icons/CodeBrackets.vue'
|
||||
import CogIcon from '@icons/Cog.vue'
|
||||
import NcAvatar from '@nextcloud/vue/components/NcAvatar'
|
||||
import { useCategories } from '@/composables/useCategories'
|
||||
import { useCurrentUser } from '@/composables/useCurrentUser'
|
||||
@@ -211,6 +222,7 @@ export default defineComponent({
|
||||
ChartLineIcon,
|
||||
AccountMultipleIcon,
|
||||
CodeBracketsIcon,
|
||||
CogIcon,
|
||||
},
|
||||
setup() {
|
||||
const { categoryHeaders, fetchCategories } = useCategories()
|
||||
@@ -259,6 +271,7 @@ export default defineComponent({
|
||||
navAdminRoles: t('forum', 'Roles'),
|
||||
navAdminCategories: t('forum', 'Categories'),
|
||||
navAdminBBCodes: t('forum', 'BBCodes'),
|
||||
navAdminSettings: t('forum', 'Settings'),
|
||||
navExamples: t('forum', 'Examples'),
|
||||
navAbout: t('forum', 'About'),
|
||||
},
|
||||
@@ -285,6 +298,9 @@ export default defineComponent({
|
||||
isAdminBBCodesActive(): boolean {
|
||||
return this.$route.path.startsWith('/admin/bbcodes')
|
||||
},
|
||||
isAdminSettingsActive(): boolean {
|
||||
return this.$route.path === '/admin/settings'
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
// Fetch categories for sidebar
|
||||
|
||||
@@ -14,6 +14,7 @@ const routes: RouteRecordRaw[] = [
|
||||
{ path: '/u/:userId', component: () => import('@/views/ProfileView.vue') },
|
||||
{ path: '/search', component: () => import('@/views/SearchView.vue') },
|
||||
{ path: '/admin', component: () => import('@/views/admin/AdminDashboard.vue') },
|
||||
{ path: '/admin/settings', component: () => import('@/views/admin/AdminGeneralSettings.vue') },
|
||||
{ path: '/admin/users', component: () => import('@/views/admin/AdminUserList.vue') },
|
||||
{ path: '/admin/roles', component: () => import('@/views/admin/AdminRoleList.vue') },
|
||||
{ path: '/admin/roles/create', component: () => import('@/views/admin/AdminRoleEdit.vue') },
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="categories-view">
|
||||
<header class="page-header">
|
||||
<h2>{{ strings.mainTitle }}</h2>
|
||||
<p class="muted" v-html="strings.subtitle"></p>
|
||||
<h2>{{ forumTitle }}</h2>
|
||||
<p class="muted">{{ forumSubtitle }}</p>
|
||||
</header>
|
||||
|
||||
<!-- Toolbar -->
|
||||
@@ -60,6 +60,7 @@ import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
|
||||
import CategoryCard from '@/components/CategoryCard.vue'
|
||||
import { useCategories } from '@/composables/useCategories'
|
||||
import type { Category } from '@/types'
|
||||
import { ocs } from '@/axios'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
|
||||
export default defineComponent({
|
||||
@@ -81,15 +82,9 @@ export default defineComponent({
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
forumTitle: t('forum', 'Forum'),
|
||||
forumSubtitle: t('forum', 'Welcome to the forum'),
|
||||
strings: {
|
||||
mainTitle: t('forum', 'Hello World — App'),
|
||||
subtitle: t(
|
||||
'forum',
|
||||
'Use the sidebar to navigate between views. Backend calls use {cStart}axios{cEnd} and OCS responses.',
|
||||
{ cStart: '<code>', cEnd: '</code>' },
|
||||
undefined,
|
||||
{ escape: false },
|
||||
),
|
||||
title: t('forum', 'Categories'),
|
||||
refresh: t('forum', 'Refresh'),
|
||||
loading: t('forum', 'Loading…'),
|
||||
@@ -100,14 +95,25 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
// Fetch categories if not already loaded
|
||||
// Fetch forum settings and categories
|
||||
try {
|
||||
await this.fetchCategories()
|
||||
await Promise.all([this.fetchForumSettings(), this.fetchCategories()])
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch categories', e)
|
||||
console.error('Failed to fetch initial data', e)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async fetchForumSettings() {
|
||||
try {
|
||||
const response = await ocs.get<{ title: string; subtitle: string }>('/admin/settings')
|
||||
this.forumTitle = response.data.title || t('forum', 'Forum')
|
||||
this.forumSubtitle = response.data.subtitle || t('forum', 'Welcome to the forum')
|
||||
} catch (e) {
|
||||
// Silently fail and use defaults if settings can't be loaded
|
||||
console.debug('Could not load forum settings, using defaults', e)
|
||||
}
|
||||
},
|
||||
|
||||
async refresh() {
|
||||
try {
|
||||
await this.refreshCategories()
|
||||
|
||||
284
src/views/admin/AdminGeneralSettings.vue
Normal file
284
src/views/admin/AdminGeneralSettings.vue
Normal file
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<div class="admin-general-settings">
|
||||
<div class="page-header">
|
||||
<h2>{{ strings.title }}</h2>
|
||||
<p class="muted">{{ strings.subtitle }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Loading state -->
|
||||
<div v-if="loading" class="center mt-16">
|
||||
<NcLoadingIcon :size="32" />
|
||||
<span class="muted ml-8">{{ strings.loading }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Error state -->
|
||||
<NcEmptyContent v-else-if="error" :title="strings.errorTitle" :description="error" class="mt-16">
|
||||
<template #action>
|
||||
<NcButton @click="loadSettings">{{ strings.retry }}</NcButton>
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
|
||||
<!-- Settings form -->
|
||||
<div v-else class="settings-form">
|
||||
<div class="form-section">
|
||||
<h3>{{ strings.appearanceTitle }}</h3>
|
||||
<p class="muted">{{ strings.appearanceDesc }}</p>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="forum-title">{{ strings.forumTitle }}</label>
|
||||
<NcTextField
|
||||
id="forum-title"
|
||||
v-model.trim="formData.title"
|
||||
:placeholder="strings.forumTitlePlaceholder"
|
||||
:maxlength="100"
|
||||
/>
|
||||
<p class="hint">{{ strings.forumTitleHint }}</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="forum-subtitle">{{ strings.forumSubtitle }}</label>
|
||||
<NcTextArea
|
||||
id="forum-subtitle"
|
||||
v-model.trim="formData.subtitle"
|
||||
:placeholder="strings.forumSubtitlePlaceholder"
|
||||
:rows="3"
|
||||
:maxlength="500"
|
||||
/>
|
||||
<p class="hint">{{ strings.forumSubtitleHint }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="form-actions">
|
||||
<NcButton :disabled="saving || !hasChanges" @click="saveSettings">
|
||||
<template #icon>
|
||||
<NcLoadingIcon v-if="saving" :size="20" />
|
||||
<CheckIcon v-else :size="20" />
|
||||
</template>
|
||||
{{ strings.save }}
|
||||
</NcButton>
|
||||
<NcButton :disabled="saving || !hasChanges" @click="resetForm">
|
||||
{{ strings.cancel }}
|
||||
</NcButton>
|
||||
</div>
|
||||
|
||||
<!-- Success message -->
|
||||
<div v-if="saveSuccess" class="success-message">
|
||||
<CheckIcon :size="20" />
|
||||
<span>{{ strings.saveSuccess }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import NcButton from '@nextcloud/vue/components/NcButton'
|
||||
import NcEmptyContent from '@nextcloud/vue/components/NcEmptyContent'
|
||||
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
|
||||
import NcTextField from '@nextcloud/vue/components/NcTextField'
|
||||
import NcTextArea from '@nextcloud/vue/components/NcTextArea'
|
||||
import CheckIcon from '@icons/Check.vue'
|
||||
import { ocs } from '@/axios'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
|
||||
interface Settings {
|
||||
title: string
|
||||
subtitle: string
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AdminGeneralSettings',
|
||||
components: {
|
||||
NcButton,
|
||||
NcEmptyContent,
|
||||
NcLoadingIcon,
|
||||
NcTextField,
|
||||
NcTextArea,
|
||||
CheckIcon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
saving: false,
|
||||
saveSuccess: false,
|
||||
error: null as string | null,
|
||||
originalData: {
|
||||
title: '',
|
||||
subtitle: '',
|
||||
} as Settings,
|
||||
formData: {
|
||||
title: '',
|
||||
subtitle: '',
|
||||
} as Settings,
|
||||
|
||||
strings: {
|
||||
title: t('forum', 'General Settings'),
|
||||
subtitle: t('forum', 'Configure general forum settings'),
|
||||
loading: t('forum', 'Loading settings…'),
|
||||
errorTitle: t('forum', 'Error loading settings'),
|
||||
retry: t('forum', 'Retry'),
|
||||
appearanceTitle: t('forum', 'Appearance'),
|
||||
appearanceDesc: t('forum', 'Customize how your forum looks to users'),
|
||||
forumTitle: t('forum', 'Forum Title'),
|
||||
forumTitlePlaceholder: t('forum', 'Forum'),
|
||||
forumTitleHint: t('forum', 'Displayed at the top of the forum home page'),
|
||||
forumSubtitle: t('forum', 'Forum Subtitle'),
|
||||
forumSubtitlePlaceholder: t('forum', 'Welcome to the forum'),
|
||||
forumSubtitleHint: t('forum', 'A brief description shown below the title'),
|
||||
save: t('forum', 'Save'),
|
||||
cancel: t('forum', 'Cancel'),
|
||||
saveSuccess: t('forum', 'Settings saved successfully'),
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasChanges(): boolean {
|
||||
return (
|
||||
this.formData.title !== this.originalData.title ||
|
||||
this.formData.subtitle !== this.originalData.subtitle
|
||||
)
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.loadSettings()
|
||||
},
|
||||
methods: {
|
||||
async loadSettings(): Promise<void> {
|
||||
try {
|
||||
this.loading = true
|
||||
this.error = null
|
||||
|
||||
const response = await ocs.get<Settings>('/admin/settings')
|
||||
this.originalData = { ...response.data }
|
||||
this.formData = { ...response.data }
|
||||
} catch (e) {
|
||||
console.error('Failed to load settings', e)
|
||||
this.error = (e as Error).message || t('forum', 'An unexpected error occurred')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
async saveSettings(): Promise<void> {
|
||||
try {
|
||||
this.saving = true
|
||||
this.saveSuccess = false
|
||||
|
||||
await ocs.put('/admin/settings', this.formData)
|
||||
|
||||
this.originalData = { ...this.formData }
|
||||
this.saveSuccess = true
|
||||
|
||||
// Hide success message after 3 seconds
|
||||
setTimeout(() => {
|
||||
this.saveSuccess = false
|
||||
}, 3000)
|
||||
} catch (e) {
|
||||
console.error('Failed to save settings', e)
|
||||
this.error = (e as Error).message || t('forum', 'Failed to save settings')
|
||||
} finally {
|
||||
this.saving = false
|
||||
}
|
||||
},
|
||||
|
||||
resetForm(): void {
|
||||
this.formData = { ...this.originalData }
|
||||
this.saveSuccess = false
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.admin-general-settings {
|
||||
.muted {
|
||||
color: var(--color-text-maxcontrast);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.mt-16 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.ml-8 {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
|
||||
h2 {
|
||||
margin: 0 0 6px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-form {
|
||||
max-width: 800px;
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 32px;
|
||||
padding: 24px;
|
||||
background: var(--color-main-background);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 8px;
|
||||
|
||||
h3 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
> p {
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 24px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
color: var(--color-main-text);
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin: 6px 0 0 0;
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-text-maxcontrast);
|
||||
}
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
padding: 12px 16px;
|
||||
background: var(--color-success-light);
|
||||
color: var(--color-success-dark);
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user