update settings
parent
e6b9a6d0e8
commit
8544f21b94
13
src/App.tsx
13
src/App.tsx
|
@ -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 />
|
||||
) : (
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue