Added custom theme creator
This commit is contained in:
parent
378dd8e36d
commit
9ab6c65d85
10 changed files with 246 additions and 41 deletions
|
@ -1,21 +1,69 @@
|
|||
import { useState } from 'react';
|
||||
|
||||
// Redux
|
||||
import { useSelector } from 'react-redux';
|
||||
import { State } from '../../../../store/reducers';
|
||||
|
||||
// Other
|
||||
import { Theme } from '../../../../interfaces';
|
||||
import { Button } from '../../../UI';
|
||||
|
||||
// UI
|
||||
import { Button, Modal } from '../../../UI';
|
||||
import { ThemeGrid } from '../ThemeGrid/ThemeGrid';
|
||||
import classes from './ThemeBuilder.module.css';
|
||||
import { ThemeCreator } from './ThemeCreator';
|
||||
import { ThemeEditor } from './ThemeEditor';
|
||||
|
||||
interface Props {
|
||||
themes: Theme[];
|
||||
}
|
||||
|
||||
export const ThemeBuilder = ({ themes }: Props): JSX.Element => {
|
||||
const {
|
||||
auth: { isAuthenticated },
|
||||
} = useSelector((state: State) => state);
|
||||
|
||||
const [showModal, toggleShowModal] = useState(false);
|
||||
const [isInEdit, toggleIsInEdit] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={classes.ThemeBuilder}>
|
||||
{/* MODALS */}
|
||||
<Modal isOpen={showModal} setIsOpen={() => toggleShowModal(!showModal)}>
|
||||
{isInEdit ? (
|
||||
<ThemeEditor modalHandler={() => toggleShowModal(!showModal)} />
|
||||
) : (
|
||||
<ThemeCreator modalHandler={() => toggleShowModal(!showModal)} />
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
{/* USER THEMES */}
|
||||
<ThemeGrid themes={themes} />
|
||||
|
||||
<div className={classes.Buttons}>
|
||||
<Button>Create new theme</Button>
|
||||
{themes.length && <Button>Edit user themes</Button>}
|
||||
</div>
|
||||
{/* BUTTONS */}
|
||||
{isAuthenticated && (
|
||||
<div className={classes.Buttons}>
|
||||
<Button
|
||||
click={() => {
|
||||
toggleIsInEdit(false);
|
||||
toggleShowModal(!showModal);
|
||||
}}
|
||||
>
|
||||
Create new theme
|
||||
</Button>
|
||||
|
||||
{themes.length && (
|
||||
<Button
|
||||
click={() => {
|
||||
toggleIsInEdit(true);
|
||||
toggleShowModal(!showModal);
|
||||
}}
|
||||
>
|
||||
Edit user themes
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
.ColorsContainer {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
import { ChangeEvent, FormEvent, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../../../store';
|
||||
import { Theme } from '../../../../interfaces';
|
||||
import { Button, InputGroup, ModalForm } from '../../../UI';
|
||||
|
||||
import classes from './ThemeCreator.module.css';
|
||||
|
||||
interface Props {
|
||||
modalHandler: () => void;
|
||||
}
|
||||
|
||||
export const ThemeCreator = ({ modalHandler }: Props): JSX.Element => {
|
||||
const { addTheme } = bindActionCreators(actionCreators, useDispatch());
|
||||
|
||||
const [formData, setFormData] = useState<Theme>({
|
||||
name: '',
|
||||
isCustom: true,
|
||||
colors: {
|
||||
primary: '#ffffff',
|
||||
accent: '#ffffff',
|
||||
background: '#ffffff',
|
||||
},
|
||||
});
|
||||
|
||||
const inputChangeHandler = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.target;
|
||||
|
||||
setFormData({
|
||||
...formData,
|
||||
[name]: value,
|
||||
});
|
||||
};
|
||||
|
||||
const setColor = ({
|
||||
target: { value, name },
|
||||
}: ChangeEvent<HTMLInputElement>) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
colors: {
|
||||
...formData.colors,
|
||||
[name]: value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const formHandler = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
// add new theme
|
||||
addTheme(formData);
|
||||
|
||||
// close modal
|
||||
modalHandler();
|
||||
|
||||
// clear theme name
|
||||
setFormData({ ...formData, name: '' });
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalForm formHandler={formHandler} modalHandler={modalHandler}>
|
||||
<InputGroup>
|
||||
<label htmlFor="name">Theme name</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
id="name"
|
||||
placeholder="my_theme"
|
||||
required
|
||||
value={formData.name}
|
||||
onChange={(e) => inputChangeHandler(e)}
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
<div className={classes.ColorsContainer}>
|
||||
<InputGroup>
|
||||
<label htmlFor="primary">Primary color</label>
|
||||
<input
|
||||
type="color"
|
||||
name="primary"
|
||||
id="primary"
|
||||
required
|
||||
value={formData.colors.primary}
|
||||
onChange={(e) => setColor(e)}
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup>
|
||||
<label htmlFor="accent">Accent color</label>
|
||||
<input
|
||||
type="color"
|
||||
name="accent"
|
||||
id="accent"
|
||||
required
|
||||
value={formData.colors.accent}
|
||||
onChange={(e) => setColor(e)}
|
||||
/>
|
||||
</InputGroup>
|
||||
|
||||
<InputGroup>
|
||||
<label htmlFor="background">Background color</label>
|
||||
<input
|
||||
type="color"
|
||||
name="background"
|
||||
id="background"
|
||||
required
|
||||
value={formData.colors.background}
|
||||
onChange={(e) => setColor(e)}
|
||||
/>
|
||||
</InputGroup>
|
||||
</div>
|
||||
|
||||
<Button>Add theme</Button>
|
||||
</ModalForm>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
import { ModalForm } from '../../../UI';
|
||||
|
||||
interface Props {
|
||||
modalHandler: () => void;
|
||||
}
|
||||
|
||||
export const ThemeEditor = (props: Props): JSX.Element => {
|
||||
return (
|
||||
<ModalForm formHandler={() => {}} modalHandler={props.modalHandler}>
|
||||
<h1>edit</h1>
|
||||
</ModalForm>
|
||||
);
|
||||
};
|
|
@ -4,6 +4,7 @@ import { ChangeEvent, FormEvent, Fragment, useEffect, useState } from 'react';
|
|||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { actionCreators } from '../../../store';
|
||||
import { State } from '../../../store/reducers';
|
||||
|
||||
// Typescript
|
||||
import { Theme, ThemeSettingsForm } from '../../../interfaces';
|
||||
|
@ -14,7 +15,6 @@ import { ThemeBuilder } from './ThemeBuilder/ThemeBuilder';
|
|||
import { ThemeGrid } from './ThemeGrid/ThemeGrid';
|
||||
|
||||
// Other
|
||||
import { State } from '../../../store/reducers';
|
||||
import {
|
||||
inputHandler,
|
||||
parseThemeToPAB,
|
||||
|
@ -82,7 +82,7 @@ export const Themer = (): JSX.Element => {
|
|||
<form onSubmit={formSubmitHandler}>
|
||||
<SettingsHeadline text="Other settings" />
|
||||
<InputGroup>
|
||||
<label htmlFor="defaultTheme">Default theme (for new users)</label>
|
||||
<label htmlFor="defaultTheme">Default theme for new users</label>
|
||||
<select
|
||||
id="defaultTheme"
|
||||
name="defaultTheme"
|
||||
|
|
|
@ -38,3 +38,7 @@
|
|||
resize: none;
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.InputGroup input[type='color'] {
|
||||
all: unset;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import { Dispatch } from 'redux';
|
||||
import { FetchThemesAction, SetThemeAction } from '../actions/theme';
|
||||
import {
|
||||
AddThemeAction,
|
||||
FetchThemesAction,
|
||||
SetThemeAction,
|
||||
} from '../actions/theme';
|
||||
import { ActionType } from '../action-types';
|
||||
import { Theme, ApiResponse, ThemeColors } from '../../interfaces';
|
||||
import { parseThemeToPAB } from '../../utility';
|
||||
import axios from 'axios';
|
||||
import { applyAuth, parseThemeToPAB } from '../../utility';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
|
||||
export const setTheme =
|
||||
(colors: ThemeColors, remeberTheme: boolean = true) =>
|
||||
|
@ -36,32 +40,34 @@ export const fetchThemes =
|
|||
}
|
||||
};
|
||||
|
||||
// export const addTheme = () => async (dispatch: Dispatch<>) => {
|
||||
// try {
|
||||
// // const res = await axios.post<>('/api/themes')
|
||||
// } catch (err) {}
|
||||
// };
|
||||
export const addTheme =
|
||||
(theme: Theme) => async (dispatch: Dispatch<AddThemeAction>) => {
|
||||
try {
|
||||
const res = await axios.post<ApiResponse<Theme>>('/api/themes', theme, {
|
||||
headers: applyAuth(),
|
||||
});
|
||||
|
||||
// export const addQuery =
|
||||
// (query: Query) => async (dispatch: Dispatch<AddQueryAction>) => {
|
||||
// try {
|
||||
// const res = await axios.post<ApiResponse<Query>>('/api/queries', query, {
|
||||
// headers: applyAuth(),
|
||||
// });
|
||||
dispatch({
|
||||
type: ActionType.addTheme,
|
||||
payload: res.data.data,
|
||||
});
|
||||
|
||||
// dispatch({
|
||||
// type: ActionType.addQuery,
|
||||
// payload: res.data.data,
|
||||
// });
|
||||
// } catch (err) {
|
||||
// const error = err as AxiosError<{ error: string }>;
|
||||
dispatch<any>({
|
||||
type: ActionType.createNotification,
|
||||
payload: {
|
||||
title: 'Success',
|
||||
message: 'Theme added',
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
const error = err as AxiosError<{ error: string }>;
|
||||
|
||||
// dispatch<any>({
|
||||
// type: ActionType.createNotification,
|
||||
// payload: {
|
||||
// title: 'Error',
|
||||
// message: error.response?.data.error,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
dispatch<any>({
|
||||
type: ActionType.createNotification,
|
||||
payload: {
|
||||
title: 'Error',
|
||||
message: error.response?.data.error,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@ export enum ActionType {
|
|||
// THEME
|
||||
setTheme = 'SET_THEME',
|
||||
fetchThemes = 'FETCH_THEMES',
|
||||
addTheme = 'ADD_THEME',
|
||||
// CONFIG
|
||||
getConfig = 'GET_CONFIG',
|
||||
updateConfig = 'UPDATE_CONFIG',
|
||||
|
|
|
@ -10,3 +10,8 @@ export interface FetchThemesAction {
|
|||
type: ActionType.fetchThemes;
|
||||
payload: Theme[];
|
||||
}
|
||||
|
||||
export interface AddThemeAction {
|
||||
type: ActionType.addTheme;
|
||||
payload: Theme;
|
||||
}
|
||||
|
|
|
@ -9,11 +9,9 @@ interface ThemeState {
|
|||
userThemes: Theme[];
|
||||
}
|
||||
|
||||
const savedTheme: ThemeColors = parsePABToTheme(localStorage.theme) || {
|
||||
primary: '#effbff',
|
||||
accent: '#6ee2ff',
|
||||
background: '#242b33',
|
||||
};
|
||||
const savedTheme = localStorage.theme
|
||||
? parsePABToTheme(localStorage.theme)
|
||||
: parsePABToTheme('#effbff;#6ee2ff;#242b33');
|
||||
|
||||
const initialState: ThemeState = {
|
||||
activeTheme: {
|
||||
|
@ -55,6 +53,13 @@ export const themeReducer = (
|
|||
};
|
||||
}
|
||||
|
||||
case ActionType.addTheme: {
|
||||
return {
|
||||
...state,
|
||||
userThemes: [...state.userThemes, action.payload],
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue