Optimized config management in FE and BE

Implemented standardized Avatar component
Added optional "Save message" to config pages
This commit is contained in:
grimsi
2024-09-18 11:40:03 +02:00
parent 6575e102f4
commit e4ac87f96e
17 changed files with 185 additions and 141 deletions
@@ -1,6 +1,6 @@
import Section from "Frontend/components/general/Section";
import Input from "Frontend/components/general/Input";
import {Avatar, Button, Input as NextUiInput, Tooltip} from "@nextui-org/react";
import {Button, Input as NextUiInput, Tooltip} from "@nextui-org/react";
import {Form, Formik} from "formik";
import {Check, Info, Trash} from "@phosphor-icons/react";
import React, {useEffect, useState} from "react";
@@ -11,6 +11,7 @@ import {UserEndpoint} from "Frontend/generated/endpoints";
import {SmallInfoField} from "Frontend/components/general/SmallInfoField";
import {toast} from "sonner";
import {removeAvatar, uploadAvatar} from "Frontend/endpoints/AvatarEndpoint";
import Avatar from "Frontend/components/general/Avatar";
export default function ProfileManagement() {
const [configSaved, setConfigSaved] = useState(false);
@@ -101,10 +102,7 @@ export default function ProfileManagement() {
<div className="flex flex-row flex-1 justify-between gap-16">
<div className="flex flex-col basis-1/4 mt-8 gap-4">
<div className="flex flex-row justify-center">
<Avatar showFallback
src={`/images/avatar?username=${auth.state.user?.username}`}
className="size-40 m-4 flex flex-row">
</Avatar>
<Avatar className="size-40 m-4 flex flex-row"/>
</div>
<div className="flex flex-row gap-2">
<NextUiInput type="file" accept="image/*" onChange={onFileSelected}
@@ -1,4 +1,4 @@
import React from "react";
import React, {useEffect} from "react";
import withConfigPage from "Frontend/components/administration/withConfigPage";
import * as Yup from 'yup';
import ConfigFormField from "Frontend/components/administration/ConfigFormField";
@@ -7,7 +7,15 @@ import {Button} from "@nextui-org/react";
import {MagicWand} from "@phosphor-icons/react";
import {toast} from "sonner";
function SsoMangementLayout({getConfig, formik}: any) {
function SsoManagementLayout({getConfig, formik, setSaveMessage}: any) {
useEffect(() => {
if (formik.dirty) {
setSaveMessage("Gameyfin must be restarted for the changes to take effect");
} else {
setSaveMessage(null);
}
}, [formik.dirty]);
function isAutoPopulateDisabled() {
return !formik.values.sso.oidc.enabled || !formik.values.sso.oidc["issuer-url"];
@@ -109,4 +117,4 @@ const validationSchema = Yup.object({
})
});
export const SsoManagement = withConfigPage(SsoMangementLayout, "Single Sign-On", "sso", validationSchema);
export const SsoManagement = withConfigPage(SsoManagementLayout, "Single Sign-On", "sso", validationSchema);
@@ -2,7 +2,7 @@ import React, {useEffect, useState} from "react";
import ConfigFormField from "Frontend/components/administration/ConfigFormField";
import withConfigPage from "Frontend/components/administration/withConfigPage";
import Section from "Frontend/components/general/Section";
import {ConfigController, UserEndpoint} from "Frontend/generated/endpoints";
import {ConfigEndpoint, UserEndpoint} from "Frontend/generated/endpoints";
import UserInfoDto from "Frontend/generated/de/grimsi/gameyfin/users/dto/UserInfoDto";
import {UserManagementCard} from "Frontend/components/general/UserManagementCard";
import {SmallInfoField} from "Frontend/components/general/SmallInfoField";
@@ -17,7 +17,7 @@ function UserManagementLayout({getConfig, formik}: any) {
(response) => setUsers(response as UserInfoDto[])
);
ConfigController.getConfig("sso.oidc.auto-register-new-users").then(
ConfigEndpoint.get("sso.oidc.auto-register-new-users").then(
(response) => setAutoRegisterNewUsers(response === "true")
);
}, []);
@@ -1,27 +1,25 @@
import React, {useEffect, useRef, useState} from "react";
import {ConfigController} from "Frontend/generated/endpoints";
import {ConfigEndpoint} from "Frontend/generated/endpoints";
import ConfigEntryDto from "Frontend/generated/de/grimsi/gameyfin/config/dto/ConfigEntryDto";
import {Form, Formik} from "formik";
import {Button, Skeleton} from "@nextui-org/react";
import {Check} from "@phosphor-icons/react";
import {Check, Info} from "@phosphor-icons/react";
import ConfigValuePairDto from "Frontend/generated/de/grimsi/gameyfin/config/dto/ConfigValuePairDto";
import {SmallInfoField} from "Frontend/components/general/SmallInfoField";
type NestedConfig = {
[field: string]: any;
}
type ConfigValuePair = {
key: string;
value: string | number | boolean | null | undefined;
}
export default function withConfigPage(WrappedComponent: React.ComponentType<any>, title: String, configPrefix: string, validationSchema?: any) {
return function ConfigPage(props: any) {
const isInitialized = useRef(false);
const [configSaved, setConfigSaved] = useState(false);
const [configDtos, setConfigDtos] = useState<ConfigEntryDto[]>([]);
const [saveMessage, setSaveMessage] = useState<string>();
useEffect(() => {
ConfigController.getConfigs(configPrefix).then((response: any) => {
ConfigEndpoint.getAll(configPrefix).then((response: any) => {
setConfigDtos(response as ConfigEntryDto[]);
isInitialized.current = true;
});
@@ -35,15 +33,7 @@ export default function withConfigPage(WrappedComponent: React.ComponentType<any
async function handleSubmit(values: NestedConfig) {
const configValues = toConfigValuePair(values);
await Promise.all(configValues.map(async (c: ConfigValuePair) => {
if (c.value === null || c.value === undefined) {
await ConfigController.deleteConfig(c.key);
return;
}
await ConfigController.setConfig(c.key, c.value.toString());
}));
await ConfigEndpoint.setAll(configValues);
setConfigSaved(true);
}
@@ -90,8 +80,8 @@ export default function withConfigPage(WrappedComponent: React.ComponentType<any
return nestedConfig;
}
function toConfigValuePair(obj: NestedConfig, parentKey: string = ''): ConfigValuePair[] {
let result: ConfigValuePair[] = [];
function toConfigValuePair(obj: NestedConfig, parentKey: string = ''): ConfigValuePairDto[] {
let result: ConfigValuePairDto[] = [];
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
@@ -133,17 +123,24 @@ export default function withConfigPage(WrappedComponent: React.ComponentType<any
<div className="flex flex-row flex-grow justify-between mb-8">
<h1 className="text-2xl font-bold">{title}</h1>
<Button
color="primary"
isLoading={formik.isSubmitting}
disabled={formik.isSubmitting || configSaved}
type="submit"
>
{formik.isSubmitting ? "" : configSaved ? <Check/> : "Save"}
</Button>
<div className="flex flex-row items-center gap-4">
{saveMessage && <SmallInfoField icon={Info}
message={saveMessage}
className="text-warning"/>}
<Button
color="primary"
isLoading={formik.isSubmitting}
disabled={formik.isSubmitting || configSaved}
type="submit"
>
{formik.isSubmitting ? "" : configSaved ? <Check/> : "Save"}
</Button>
</div>
</div>
<WrappedComponent {...props} getConfig={getConfig} formik={formik}/>
<WrappedComponent {...props} getConfig={getConfig} formik={formik}
setSaveMessage={setSaveMessage}/>
</Form>
)}
</Formik>