Added custom theme creator

This commit is contained in:
Paweł Malak 2022-03-24 16:07:14 +01:00
parent 378dd8e36d
commit 9ab6c65d85
10 changed files with 246 additions and 41 deletions

View file

@ -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} />
{/* BUTTONS */}
{isAuthenticated && (
<div className={classes.Buttons}>
<Button>Create new theme</Button>
{themes.length && <Button>Edit user themes</Button>}
<Button
click={() => {
toggleIsInEdit(false);
toggleShowModal(!showModal);
}}
>
Create new theme
</Button>
{themes.length && (
<Button
click={() => {
toggleIsInEdit(true);
toggleShowModal(!showModal);
}}
>
Edit user themes
</Button>
)}
</div>
)}
</div>
);
};

View file

@ -0,0 +1,6 @@
.ColorsContainer {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 10px;
margin-bottom: 20px;
}

View file

@ -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>
);
};

View file

@ -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>
);
};

View file

@ -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"

View file

@ -38,3 +38,7 @@
resize: none;
height: 50vh;
}
.InputGroup input[type='color'] {
all: unset;
}

View file

@ -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,
},
});
}
};

View file

@ -2,6 +2,7 @@ export enum ActionType {
// THEME
setTheme = 'SET_THEME',
fetchThemes = 'FETCH_THEMES',
addTheme = 'ADD_THEME',
// CONFIG
getConfig = 'GET_CONFIG',
updateConfig = 'UPDATE_CONFIG',

View file

@ -10,3 +10,8 @@ export interface FetchThemesAction {
type: ActionType.fetchThemes;
payload: Theme[];
}
export interface AddThemeAction {
type: ActionType.addTheme;
payload: Theme;
}

View file

@ -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;
}