update settings

main
alex 2024-09-04 22:56:58 +02:00
parent e6b9a6d0e8
commit 8544f21b94
4 changed files with 195 additions and 150 deletions

View File

@ -2,6 +2,8 @@ import { ConfigProvider, Layout, theme } from "antd";
import DashboardLayout from "./core/components/DashboardLayout";
import {
darkMode,
primaryColor,
setPrimaryColor,
setUserAuthenticated,
userAuthenticated,
} from "./core/reducers/appSlice";
@ -19,6 +21,7 @@ function App() {
const isDarkMode = useSelector(darkMode);
const uAuthenticated = useSelector(userAuthenticated);
const primColor = useSelector(primaryColor);
console.info(
"\n %c LMS %c v1.0.0 %c \n",
@ -36,12 +39,13 @@ function App() {
(async () => {
try {
const response = await myFetch({
url: "/user",
url: "/app",
method: "GET",
});
if (response) {
dispatch(setUserAuthenticated(true))
dispatch(setUserAuthenticated(true));
dispatch(setPrimaryColor(`#${response.Organization.PrimaryColor}`));
}
} catch (error) {}
})();
@ -58,10 +62,13 @@ function App() {
<ConfigProvider
theme={{
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
token: {
colorPrimary: primColor,
},
}}
>
{uAuthenticated == null ? (
<MyCenteredSpin />
<MyCenteredSpin />
) : uAuthenticated ? (
<DashboardLayout />
) : (

View File

@ -5,6 +5,7 @@ export const appSlice = createSlice({
initialState: {
darkMode: false,
userAuthenticated: null,
primaryColor: "#1677FF",
},
reducers: {
setDarkMode: (state, action) => {
@ -12,14 +13,19 @@ export const appSlice = createSlice({
},
setUserAuthenticated: (state, action) => {
state.userAuthenticated = action.payload;
}
},
setPrimaryColor: (state, action) => {
state.primaryColor = action.payload;
},
},
selectors: {
darkMode: (state) => state.darkMode,
userAuthenticated: (state) => state.userAuthenticated,
primaryColor: (state) => state.primaryColor,
},
})
});
export const { setDarkMode, setUserAuthenticated } = appSlice.actions;
export const { setDarkMode, setUserAuthenticated, setPrimaryColor } =
appSlice.actions;
export const { darkMode, userAuthenticated } = appSlice.selectors;
export const { darkMode, userAuthenticated, primaryColor } = appSlice.selectors;

View File

@ -1,24 +1,35 @@
import { createApi } from '@reduxjs/toolkit/query/react';
import { baseQueryWithErrorHandling } from 'core/helper/api';
import { TeamMember, OrganizationSettings } from 'core/types/organization';
import { createApi } from "@reduxjs/toolkit/query/react";
import { baseQueryWithErrorHandling } from "core/helper/api";
import { TeamMember, OrganizationSettings } from "core/types/organization";
export const organizationApi = createApi({
reducerPath: 'organizationApi',
baseQuery: baseQueryWithErrorHandling,
endpoints: (builder) => ({
getTeam: builder.query<TeamMember[], undefined>({
query: () => ({
url: 'organization/team/members',
method: 'GET',
}),
}),
getOrganizationSettings: builder.query<OrganizationSettings, undefined>({
query: () => ({
url: 'organization/settings',
method: 'GET',
}),
}),
reducerPath: "organizationApi",
baseQuery: baseQueryWithErrorHandling,
endpoints: (builder) => ({
getTeam: builder.query<TeamMember[], undefined>({
query: () => ({
url: "organization/team/members",
method: "GET",
}),
}),
getOrganizationSettings: builder.query<OrganizationSettings, undefined>({
query: () => ({
url: "organization/settings",
method: "GET",
}),
}),
updateOrganizationSettings: builder.mutation({
query: ({ primaryColor, companyName }) => ({
url: "organization/settings",
method: "PATCH",
body: { PrimaryColor: primaryColor, CompanyName: companyName },
}),
}),
}),
});
export const { useGetTeamQuery, useGetOrganizationSettingsQuery } = organizationApi;
export const {
useGetTeamQuery,
useGetOrganizationSettingsQuery,
useUpdateOrganizationSettingsMutation,
} = organizationApi;

View File

@ -1,4 +1,4 @@
import { Button, Card, Divider, Flex, Form, Input, Typography } from "antd";
import { Button, Card, Flex, Form, Input } from "antd";
import HeaderBar from "../../core/components/Header";
import MyBanner from "../../shared/components/MyBanner";
import { MyContainer } from "../../shared/components/MyContainer";
@ -6,14 +6,19 @@ import { SaveOutlined } from "@ant-design/icons";
import MyUpload from "shared/components/MyUpload";
import { Constants } from "core/utils/utils";
import ColorPicker from "antd/es/color-picker";
import { useGetOrganizationSettingsQuery } from "core/services/organization";
import {
useGetOrganizationSettingsQuery,
useUpdateOrganizationSettingsMutation,
} from "core/services/organization";
import MyErrorResult from "shared/components/MyResult";
import { useForm } from "antd/es/form/Form";
import { useEffect } from "react";
import { useEffect, useRef } from "react";
import { AggregationColor } from "antd/es/color-picker/color";
import { useDispatch } from "react-redux";
import { setPrimaryColor } from "core/reducers/appSlice";
type FieldType = {
primaryColor: string;
primaryColor: string | AggregationColor;
companyName: string;
subdomain: string;
};
@ -27,9 +32,29 @@ export default function Settings() {
);
const [form] = useForm<FieldType>();
const dispatch = useDispatch();
const debounceRef = useRef<null | NodeJS.Timeout>(null);
const currentPrimaryColor = useRef<string>();
const [updateOrganizationSettings, { isLoading: isUpdateSettingsLoading }] =
useUpdateOrganizationSettingsMutation();
const handleSave = (values: FieldType) => {
console.log(values);
const hexColor =
typeof values.primaryColor === "string"
? values.primaryColor
: values.primaryColor.toHexString().split("#")[1];
try {
updateOrganizationSettings({
primaryColor: hexColor,
companyName: values.companyName,
});
currentPrimaryColor.current = hexColor;
} catch (error) {
console.error(error);
}
};
useEffect(() => {
@ -39,9 +64,21 @@ export default function Settings() {
companyName: data.CompanyName,
subdomain: data.Subdomain,
});
currentPrimaryColor.current = data.PrimaryColor;
}
}, [data]);
useEffect(() => {
return () => {
dispatch(setPrimaryColor(currentPrimaryColor.current));
if (debounceRef.current) {
clearTimeout(debounceRef.current);
}
};
}, []);
if (error) return <MyErrorResult />;
return (
@ -49,127 +86,111 @@ export default function Settings() {
<MyBanner title="Settings" subtitle="MANAGE" headerBar={<HeaderBar />} />
<MyContainer>
<Flex vertical gap={16} style={{ paddingBottom: 16 }}>
<Form form={form} onFinish={handleSave}>
<Card
loading={isLoading}
styles={{
body: {
padding: 16,
},
}}
title="Branding"
extra={
<Button
icon={<SaveOutlined />}
type="text"
shape="circle"
size="large"
htmlType="submit"
<Form form={form} onFinish={handleSave} layout="vertical">
<Card
loading={isLoading}
styles={{
body: {
padding: 16,
},
}}
title="Branding"
extra={
<Button
icon={<SaveOutlined />}
type="text"
shape="circle"
size="large"
htmlType="submit"
loading={isUpdateSettingsLoading}
/>
}
>
<Flex wrap gap={12}>
<Form.Item<FieldType> name="primaryColor" label="Primary color">
<ColorPicker
size="small"
showText
onChange={(color: AggregationColor) => {
if (debounceRef.current) {
clearTimeout(debounceRef.current);
}
debounceRef.current = setTimeout(() => {
dispatch(setPrimaryColor(color.toHexString()));
}, 600);
}}
/>
}
>
<Flex vertical gap={2}>
<Flex gap={32}>
<Flex vertical>
<Typography.Text style={{ fontSize: 16 }}>
Primary Color
</Typography.Text>
<Form.Item name="primaryColor">
<ColorPicker
defaultValue="#1677ff"
size="small"
showText
format="hex"
/>
</Form.Item>
</Flex>
<Flex vertical>
<Typography.Text style={{ fontSize: 16 }}>
Company name
</Typography.Text>
<Form.Item name="companyName">
<Input defaultValue="Jannex" />
</Form.Item>
</Flex>
<Flex vertical>
<Typography.Text style={{ fontSize: 16 }}>
Subdomain
</Typography.Text>
<Form.Item name="subdomain">
<Input
addonBefore="https://"
addonAfter=". jannex.de"
defaultValue="mysite"
/>
</Form.Item>
</Flex>
<Flex vertical>
<Typography.Text style={{ fontSize: 16 }}>
Logo
</Typography.Text>
<MyUpload
action={`/changeCompanyLogo`}
onChange={(info) => {
if (info.file.status === "done") {
//onThumbnailChanged?.();
console.log("done");
}
}}
imgCropProps={{
aspect: 1 / 1,
children: <></>,
}}
>
<img
src={`${Constants.STATIC_CONTENT_ADDRESS}/demo/lesson_thumbnail.webp`}
alt="Company Logo"
style={{
width: 128,
maxHeight: 128,
padding: 4,
borderRadius: 4,
border: "1px solid #ddd",
}}
/>
</MyUpload>
</Flex>
</Flex>
<Flex vertical>
<Typography.Text style={{ fontSize: 16 }}>
Thumbnail
</Typography.Text>
<MyUpload
action={`/changeCompanyLogo`}
onChange={(info) => {
if (info.file.status === "done") {
//onThumbnailChanged?.();
console.log("done");
}
</Form.Item>
<Form.Item<FieldType> name="companyName" label="Company name">
<Input defaultValue="Jannex" />
</Form.Item>
<Form.Item<FieldType> name="subdomain" label="Subdomain">
<Input
addonBefore="https://"
addonAfter=". jannex.de"
defaultValue="mysite"
/>
</Form.Item>
<Form.Item label="Logo">
<MyUpload
action={`/changeCompanyLogo`}
onChange={(info) => {
if (info.file.status === "done") {
//onThumbnailChanged?.();
console.log("done");
}
}}
imgCropProps={{
aspect: 1 / 1,
children: <></>,
}}
>
<img
src={`${Constants.STATIC_CONTENT_ADDRESS}/demo/lesson_thumbnail.webp`}
alt="Company Logo"
style={{
width: 128,
maxHeight: 128,
padding: 4,
borderRadius: 4,
border: "1px solid #ddd",
}}
imgCropProps={{
aspect: 22 / 9,
children: <></>,
}}
>
<img
src={`${Constants.STATIC_CONTENT_ADDRESS}/demo/lesson_thumbnail.webp`}
alt="Thumbnail"
style={{
width: "100%",
height: 228,
objectFit: "cover",
padding: 4,
borderRadius: 4,
border: "1px solid #ddd",
}}
/>
</MyUpload>
</Flex>
</Flex>
</Card>
</Form>
</Flex>
/>
</MyUpload>
</Form.Item>
</Flex>
<Form.Item label="Thumbnail">
<MyUpload
action={`/changeCompanyLogo`}
onChange={(info) => {
if (info.file.status === "done") {
//onThumbnailChanged?.();
console.log("done");
}
}}
imgCropProps={{
aspect: 22 / 9,
children: <></>,
}}
>
<img
src={`${Constants.STATIC_CONTENT_ADDRESS}/demo/lesson_thumbnail.webp`}
alt="Thumbnail"
style={{
width: "100%",
height: 228,
objectFit: "cover",
padding: 4,
borderRadius: 4,
border: "1px solid #ddd",
}}
/>
</MyUpload>
</Form.Item>
</Card>
</Form>
</MyContainer>
</>
);