feat: 添加主题管理功能,更新系统设置页面,优化用户体验
This commit is contained in:
5
auto-imports.d.ts
vendored
5
auto-imports.d.ts
vendored
@@ -281,6 +281,7 @@ declare global {
|
|||||||
const useTextDirection: typeof import('@vueuse/core').useTextDirection
|
const useTextDirection: typeof import('@vueuse/core').useTextDirection
|
||||||
const useTextSelection: typeof import('@vueuse/core').useTextSelection
|
const useTextSelection: typeof import('@vueuse/core').useTextSelection
|
||||||
const useTextareaAutosize: typeof import('@vueuse/core').useTextareaAutosize
|
const useTextareaAutosize: typeof import('@vueuse/core').useTextareaAutosize
|
||||||
|
const useTheme: typeof import('./src/composables/useTheme').useTheme
|
||||||
const useThrottle: typeof import('@vueuse/core').useThrottle
|
const useThrottle: typeof import('@vueuse/core').useThrottle
|
||||||
const useThrottleFn: typeof import('@vueuse/core').useThrottleFn
|
const useThrottleFn: typeof import('@vueuse/core').useThrottleFn
|
||||||
const useThrottledRefHistory: typeof import('@vueuse/core').useThrottledRefHistory
|
const useThrottledRefHistory: typeof import('@vueuse/core').useThrottledRefHistory
|
||||||
@@ -340,6 +341,9 @@ declare global {
|
|||||||
export type { QRScanResult } from './src/composables/useQRScanner'
|
export type { QRScanResult } from './src/composables/useQRScanner'
|
||||||
import('./src/composables/useQRScanner')
|
import('./src/composables/useQRScanner')
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
export type { ThemeMode } from './src/composables/useTheme'
|
||||||
|
import('./src/composables/useTheme')
|
||||||
|
// @ts-ignore
|
||||||
export type { Series, TData, WeightChartOptions, TradingViewData, TradingViewOptions } from './src/composables/useTradingView'
|
export type { Series, TData, WeightChartOptions, TradingViewData, TradingViewOptions } from './src/composables/useTradingView'
|
||||||
import('./src/composables/useTradingView')
|
import('./src/composables/useTradingView')
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -628,6 +632,7 @@ declare module 'vue' {
|
|||||||
readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
|
readonly useTextDirection: UnwrapRef<typeof import('@vueuse/core')['useTextDirection']>
|
||||||
readonly useTextSelection: UnwrapRef<typeof import('@vueuse/core')['useTextSelection']>
|
readonly useTextSelection: UnwrapRef<typeof import('@vueuse/core')['useTextSelection']>
|
||||||
readonly useTextareaAutosize: UnwrapRef<typeof import('@vueuse/core')['useTextareaAutosize']>
|
readonly useTextareaAutosize: UnwrapRef<typeof import('@vueuse/core')['useTextareaAutosize']>
|
||||||
|
readonly useTheme: UnwrapRef<typeof import('./src/composables/useTheme')['useTheme']>
|
||||||
readonly useThrottle: UnwrapRef<typeof import('@vueuse/core')['useThrottle']>
|
readonly useThrottle: UnwrapRef<typeof import('@vueuse/core')['useThrottle']>
|
||||||
readonly useThrottleFn: UnwrapRef<typeof import('@vueuse/core')['useThrottleFn']>
|
readonly useThrottleFn: UnwrapRef<typeof import('@vueuse/core')['useThrottleFn']>
|
||||||
readonly useThrottledRefHistory: UnwrapRef<typeof import('@vueuse/core')['useThrottledRefHistory']>
|
readonly useThrottledRefHistory: UnwrapRef<typeof import('@vueuse/core')['useThrottledRefHistory']>
|
||||||
|
|||||||
@@ -592,7 +592,7 @@ const stickyStyle = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Dark mode 支持 */
|
/* Dark mode 支持 */
|
||||||
@media (prefers-color-scheme: dark) {
|
.ion-palette-dark {
|
||||||
.ui-tabs {
|
.ui-tabs {
|
||||||
--ui-tabs-primary: var(--ion-color-primary, #ffffff);
|
--ui-tabs-primary: var(--ion-color-primary, #ffffff);
|
||||||
--ui-tabs-primary-rgb: var(--ion-color-primary-rgb, 255, 255, 255);
|
--ui-tabs-primary-rgb: var(--ion-color-primary-rgb, 255, 255, 255);
|
||||||
|
|||||||
26
src/composables/useTheme.ts
Normal file
26
src/composables/useTheme.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { usePreferredDark } from "@vueuse/core";
|
||||||
|
|
||||||
|
export type ThemeMode = "light" | "dark" | "auto";
|
||||||
|
|
||||||
|
const STORAGE_KEY = "app-theme-mode";
|
||||||
|
|
||||||
|
export function useTheme() {
|
||||||
|
const prefersDark = usePreferredDark();
|
||||||
|
const theme = useStorage<ThemeMode>(STORAGE_KEY, "auto");
|
||||||
|
|
||||||
|
const isDark = computed(() => {
|
||||||
|
if (theme.value === "auto") {
|
||||||
|
return prefersDark.value;
|
||||||
|
}
|
||||||
|
return theme.value === "dark";
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(isDark, (dark) => {
|
||||||
|
document.documentElement.classList.toggle("ion-palette-dark", dark);
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
|
return {
|
||||||
|
theme,
|
||||||
|
isDark,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -315,6 +315,11 @@
|
|||||||
"updateNow": "Update Now",
|
"updateNow": "Update Now",
|
||||||
"alreadyLatest": "Already up to date",
|
"alreadyLatest": "Already up to date",
|
||||||
"checkUpdateFailed": "Failed to check for updates",
|
"checkUpdateFailed": "Failed to check for updates",
|
||||||
"languageTitle": "Language / 语言"
|
"languageTitle": "Language / 语言",
|
||||||
|
"theme": "Theme",
|
||||||
|
"themeTitle": "Appearance",
|
||||||
|
"themeLight": "Light",
|
||||||
|
"themeDark": "Dark",
|
||||||
|
"themeAuto": "Auto"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -315,6 +315,11 @@
|
|||||||
"updateNow": "立即更新",
|
"updateNow": "立即更新",
|
||||||
"alreadyLatest": "已是最新版本",
|
"alreadyLatest": "已是最新版本",
|
||||||
"checkUpdateFailed": "检查更新失败",
|
"checkUpdateFailed": "检查更新失败",
|
||||||
"languageTitle": "语言 / Language"
|
"languageTitle": "语言 / Language",
|
||||||
|
"theme": "主题",
|
||||||
|
"themeTitle": "外观主题",
|
||||||
|
"themeLight": "浅色",
|
||||||
|
"themeDark": "深色",
|
||||||
|
"themeAuto": "跟随系统"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ import "@ionic/vue/css/display.css";
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// import "@ionic/vue/css/palettes/dark.always.css";
|
// import "@ionic/vue/css/palettes/dark.always.css";
|
||||||
// import "@ionic/vue/css/palettes/dark.class.css";
|
import "@ionic/vue/css/palettes/dark.class.css";
|
||||||
import "@ionic/vue/css/palettes/dark.system.css";
|
// import "@ionic/vue/css/palettes/dark.system.css";
|
||||||
|
|
||||||
/* Theme variables */
|
/* Theme variables */
|
||||||
import "./theme/index.css";
|
import "./theme/index.css";
|
||||||
|
|||||||
@@ -65,6 +65,10 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
path: "language",
|
path: "language",
|
||||||
component: () => import("@/views/system-settings/language.vue"),
|
component: () => import("@/views/system-settings/language.vue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "theme",
|
||||||
|
component: () => import("@/views/system-settings/theme.vue"),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ ion-datetime.ui-datetime {
|
|||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
.ion-palette-dark {
|
||||||
ion-datetime.ui-datetime {
|
ion-datetime.ui-datetime {
|
||||||
--background: rgb(15, 15, 15);
|
--background: rgb(15, 15, 15);
|
||||||
--background-rgb: 15, 15, 15;
|
--background-rgb: 15, 15, 15;
|
||||||
@@ -33,3 +33,8 @@ ion-datetime.ui-datetime {
|
|||||||
--wheel-fade-background-rgb: 15, 15, 15;
|
--wheel-fade-background-rgb: 15, 15, 15;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root.ios {
|
||||||
|
--ion-padding: 20px;
|
||||||
|
--ion-margin: 20px;
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/* For information on how to create your own theme, please refer to:
|
/* For information on how to create your own theme, please refer to:
|
||||||
http://ionicframework.com/docs/theming/ */
|
http://ionicframework.com/docs/theming/ */
|
||||||
:root {
|
html:root {
|
||||||
--ion-color-primary: #0d0d0d;
|
--ion-color-primary: #0d0d0d;
|
||||||
--ion-color-primary-rgb: 13, 13, 13;
|
--ion-color-primary-rgb: 13, 13, 13;
|
||||||
--ion-color-primary-contrast: #ffffff;
|
--ion-color-primary-contrast: #ffffff;
|
||||||
@@ -120,72 +120,70 @@ http://ionicframework.com/docs/theming/ */
|
|||||||
--ui-background-primary: linear-gradient(180deg, #615fff 0%, #9810fa 100%);
|
--ui-background-primary: linear-gradient(180deg, #615fff 0%, #9810fa 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
html.ion-palette-dark {
|
||||||
:root {
|
--ion-color-primary: #ffffff;
|
||||||
--ion-color-primary: #ffffff;
|
--ion-color-primary-rgb: 255, 255, 255;
|
||||||
--ion-color-primary-rgb: 255, 255, 255;
|
--ion-color-primary-contrast: #000000;
|
||||||
--ion-color-primary-contrast: #000000;
|
--ion-color-primary-contrast-rgb: 0, 0, 0;
|
||||||
--ion-color-primary-contrast-rgb: 0, 0, 0;
|
--ion-color-primary-shade: #e0e0e0;
|
||||||
--ion-color-primary-shade: #e0e0e0;
|
--ion-color-primary-tint: #ffffff;
|
||||||
--ion-color-primary-tint: #ffffff;
|
|
||||||
|
|
||||||
--ion-color-secondary: #ededed;
|
--ion-color-secondary: #ededed;
|
||||||
--ion-color-secondary-rgb: 237, 237, 237;
|
--ion-color-secondary-rgb: 237, 237, 237;
|
||||||
--ion-color-secondary-contrast: #000000;
|
--ion-color-secondary-contrast: #000000;
|
||||||
--ion-color-secondary-contrast-rgb: 0, 0, 0;
|
--ion-color-secondary-contrast-rgb: 0, 0, 0;
|
||||||
--ion-color-secondary-shade: #d1d1d1;
|
--ion-color-secondary-shade: #d1d1d1;
|
||||||
--ion-color-secondary-tint: #efefef;
|
--ion-color-secondary-tint: #efefef;
|
||||||
|
|
||||||
--ion-color-tertiary: #d6d6d6;
|
--ion-color-tertiary: #d6d6d6;
|
||||||
--ion-color-tertiary-rgb: 214, 214, 214;
|
--ion-color-tertiary-rgb: 214, 214, 214;
|
||||||
--ion-color-tertiary-contrast: #000000;
|
--ion-color-tertiary-contrast: #000000;
|
||||||
--ion-color-tertiary-contrast-rgb: 0, 0, 0;
|
--ion-color-tertiary-contrast-rgb: 0, 0, 0;
|
||||||
--ion-color-tertiary-shade: #bcbcbc;
|
--ion-color-tertiary-shade: #bcbcbc;
|
||||||
--ion-color-tertiary-tint: #dadada;
|
--ion-color-tertiary-tint: #dadada;
|
||||||
|
|
||||||
--ion-color-success: #2dd55b;
|
--ion-color-success: #2dd55b;
|
||||||
--ion-color-success-rgb: 45, 213, 91;
|
--ion-color-success-rgb: 45, 213, 91;
|
||||||
--ion-color-success-contrast: #000000;
|
--ion-color-success-contrast: #000000;
|
||||||
--ion-color-success-contrast-rgb: 0, 0, 0;
|
--ion-color-success-contrast-rgb: 0, 0, 0;
|
||||||
--ion-color-success-shade: #28bb50;
|
--ion-color-success-shade: #28bb50;
|
||||||
--ion-color-success-tint: #42d96b;
|
--ion-color-success-tint: #42d96b;
|
||||||
|
|
||||||
--ion-color-warning: #ffc409;
|
--ion-color-warning: #ffc409;
|
||||||
--ion-color-warning-rgb: 255, 196, 9;
|
--ion-color-warning-rgb: 255, 196, 9;
|
||||||
--ion-color-warning-contrast: #000000;
|
--ion-color-warning-contrast: #000000;
|
||||||
--ion-color-warning-contrast-rgb: 0, 0, 0;
|
--ion-color-warning-contrast-rgb: 0, 0, 0;
|
||||||
--ion-color-warning-shade: #e0ac08;
|
--ion-color-warning-shade: #e0ac08;
|
||||||
--ion-color-warning-tint: #ffca22;
|
--ion-color-warning-tint: #ffca22;
|
||||||
|
|
||||||
--ion-color-danger: #ff3344;
|
--ion-color-danger: #ff3344;
|
||||||
--ion-color-danger-rgb: 255,51,68;
|
--ion-color-danger-rgb: 255,51,68;
|
||||||
--ion-color-danger-contrast: #000000;
|
--ion-color-danger-contrast: #000000;
|
||||||
--ion-color-danger-contrast-rgb: 0,0,0;
|
--ion-color-danger-contrast-rgb: 0,0,0;
|
||||||
--ion-color-danger-shade: #e02d3c;
|
--ion-color-danger-shade: #e02d3c;
|
||||||
--ion-color-danger-tint: #ff4757;
|
--ion-color-danger-tint: #ff4757;
|
||||||
|
|
||||||
--ion-color-light: #f6f8fc;
|
--ion-color-light: #f6f8fc;
|
||||||
--ion-color-light-rgb: 246, 248, 252;
|
--ion-color-light-rgb: 246, 248, 252;
|
||||||
--ion-color-light-contrast: #000000;
|
--ion-color-light-contrast: #000000;
|
||||||
--ion-color-light-contrast-rgb: 0, 0, 0;
|
--ion-color-light-contrast-rgb: 0, 0, 0;
|
||||||
--ion-color-light-shade: #d8dade;
|
--ion-color-light-shade: #d8dade;
|
||||||
--ion-color-light-tint: #f7f9fc;
|
--ion-color-light-tint: #f7f9fc;
|
||||||
|
|
||||||
--ion-color-medium: #5f5f5f;
|
--ion-color-medium: #5f5f5f;
|
||||||
--ion-color-medium-rgb: 95, 95, 95;
|
--ion-color-medium-rgb: 95, 95, 95;
|
||||||
--ion-color-medium-contrast: #ffffff;
|
--ion-color-medium-contrast: #ffffff;
|
||||||
--ion-color-medium-contrast-rgb: 255, 255, 255;
|
--ion-color-medium-contrast-rgb: 255, 255, 255;
|
||||||
--ion-color-medium-shade: #545454;
|
--ion-color-medium-shade: #545454;
|
||||||
--ion-color-medium-tint: #6f6f6f;
|
--ion-color-medium-tint: #6f6f6f;
|
||||||
|
|
||||||
--ion-color-dark: #2f2f2f;
|
--ion-color-dark: #2f2f2f;
|
||||||
--ion-color-dark-rgb: 47, 47, 47;
|
--ion-color-dark-rgb: 47, 47, 47;
|
||||||
--ion-color-dark-contrast: #ffffff;
|
--ion-color-dark-contrast: #ffffff;
|
||||||
--ion-color-dark-contrast-rgb: 255, 255, 255;
|
--ion-color-dark-contrast-rgb: 255, 255, 255;
|
||||||
--ion-color-dark-shade: #292929;
|
--ion-color-dark-shade: #292929;
|
||||||
--ion-color-dark-tint: #444444;
|
--ion-color-dark-tint: #444444;
|
||||||
|
|
||||||
--ui-input-background: #1e1e1e;
|
--ui-input-background: #1e1e1e;
|
||||||
--ui-input-color: #ffffff;
|
--ui-input-color: #ffffff;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ ion-content::part(scroll) {
|
|||||||
.item {
|
.item {
|
||||||
@apply px-3 py-1 rounded-full text-xs transition-all;
|
@apply px-3 py-1 rounded-full text-xs transition-all;
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
.ion-palette-dark {
|
||||||
.item {
|
.item {
|
||||||
@apply bg-(--ion-color-step-800);
|
@apply bg-(--ion-color-step-800);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ const { user } = useAuth();
|
|||||||
--background: #f4f6f8;
|
--background: #f4f6f8;
|
||||||
--border-color: #c6c5c5;
|
--border-color: #c6c5c5;
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
.ion-palette-dark {
|
||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
--background: #232324;
|
--background: #232324;
|
||||||
--border-color: #3a3a3b;
|
--border-color: #3a3a3b;
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { alertController, toastController, useIonRouter } from "@ionic/vue";
|
import { alertController, toastController, useIonRouter } from "@ionic/vue";
|
||||||
import { checkbox, close, information, languageOutline, refresh } from "ionicons/icons";
|
import { checkbox, close, contrastOutline, information, languageOutline, refresh } from "ionicons/icons";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const router = useIonRouter();
|
const router = useIonRouter();
|
||||||
const { cacheSize, calculateCacheSize, clearCache } = useCacheSize();
|
const { cacheSize, calculateCacheSize, clearCache } = useCacheSize();
|
||||||
const { isChecking, checkForUpdate } = useAppUpdate();
|
const { isChecking, checkForUpdate } = useAppUpdate();
|
||||||
const { currentLanguage } = useLanguage();
|
const { currentLanguage } = useLanguage();
|
||||||
|
const { theme } = useTheme();
|
||||||
|
const themeNames = {
|
||||||
|
light: t("settings.themeLight"),
|
||||||
|
dark: t("settings.themeDark"),
|
||||||
|
auto: t("settings.themeAuto"),
|
||||||
|
};
|
||||||
|
|
||||||
function handleClearCache() {
|
function handleClearCache() {
|
||||||
clearCache();
|
clearCache();
|
||||||
@@ -98,6 +104,21 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
<ion-item button @click="router.push('/system-settings/theme')">
|
||||||
|
<div class="flex justify-between w-full items-center">
|
||||||
|
<div class="flex-center space-x-2">
|
||||||
|
<div class="icon">
|
||||||
|
<ion-icon :icon="contrastOutline" class="text-lg" />
|
||||||
|
</div>
|
||||||
|
<div class="text-sm font-semibold">
|
||||||
|
{{ t("settings.theme") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="end">
|
||||||
|
{{ themeNames[theme] }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ion-item>
|
||||||
<ion-item button>
|
<ion-item button>
|
||||||
<div class="flex justify-between w-full items-center">
|
<div class="flex justify-between w-full items-center">
|
||||||
<div class="flex-center space-x-2">
|
<div class="flex-center space-x-2">
|
||||||
|
|||||||
57
src/views/system-settings/theme.vue
Normal file
57
src/views/system-settings/theme.vue
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { ThemeMode } from "@/composables/useTheme";
|
||||||
|
import { useIonRouter } from "@ionic/vue";
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
const themes: { value: ThemeMode; label: string }[] = [
|
||||||
|
{ value: "light", label: t("settings.themeLight") },
|
||||||
|
{ value: "dark", label: t("settings.themeDark") },
|
||||||
|
{ value: "auto", label: t("settings.themeAuto") },
|
||||||
|
];
|
||||||
|
|
||||||
|
function handleThemeChange(event: CustomEvent) {
|
||||||
|
const themeMode = event.detail.value as ThemeMode;
|
||||||
|
theme.value = themeMode;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ion-page>
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar class="ui-toolbar">
|
||||||
|
<ion-back-button slot="start" />
|
||||||
|
<ion-title>{{ t("settings.theme") }}</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content :fullscreen="true" class="ion-padding">
|
||||||
|
<ion-list lines="full">
|
||||||
|
<ion-radio-group :value="theme" @ion-change="handleThemeChange">
|
||||||
|
<ion-item v-for="item in themes" :key="item.value">
|
||||||
|
<ion-radio :value="item.value">
|
||||||
|
<div class="theme-item">
|
||||||
|
<div class="font-semibold">
|
||||||
|
{{ item.label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ion-radio>
|
||||||
|
</ion-item>
|
||||||
|
</ion-radio-group>
|
||||||
|
</ion-list>
|
||||||
|
</ion-content>
|
||||||
|
</ion-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang='css' scoped>
|
||||||
|
@reference "tailwindcss";
|
||||||
|
|
||||||
|
.theme-item {
|
||||||
|
@apply py-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-radio {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -178,7 +178,7 @@ function formatCardNumber(value: string) {
|
|||||||
border-color: #ef4444;
|
border-color: #ef4444;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
.ion-palette-dark {
|
||||||
.ui-select {
|
.ui-select {
|
||||||
border-color: #4b5563;
|
border-color: #4b5563;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user