Przeglądaj źródła

admin page files

Marina Yakovenko 5 lat temu
rodzic
commit
9d820ffde1

+ 11 - 0
src/actionTypes/actionTypes.js

@@ -0,0 +1,11 @@
+export const POST_NEW_EVENT_REQUEST = "POST_NEW_EVENT_REQUEST";
+export const POST_NEW_EVENT_REQUEST_SUCCESS = "POST_NEW_EVENT_REQUEST_SUCCESS";
+export const POST_NEW_EVENT_REQUEST_FAIL = "POST_NEW_EVENT_REQUEST_FAIL";
+
+export const GET_EVENTS_REQUEST = "GET_EVENTS_REQUEST";
+export const GET_EVENTS_REQUEST_SUCCESS = "GET_EVENTS_REQUEST_SUCCESS";
+export const GET_EVENTS_REQUEST_FAIL = "GET_EVENTS_REQUEST_FAIL";
+
+export const REMOVE_EVENT_REQUEST = "REMOVE_EVENT_REQUEST";
+export const REMOVE_EVENT_REQUEST_SUCCESS = "REMOVE_EVENT_REQUEST_SUCCESS";
+export const REMOVE_EVENT_REQUEST_FAIL = "REMOVE_EVENT_REQUEST_FAIL";

+ 77 - 0
src/actions/adminMainPageActions.js

@@ -0,0 +1,77 @@
+import axios from "axios";
+import * as types from "../actionTypes/actionTypes"
+
+//POST
+const postNewEventRequest = payload => ({
+	type: types.POST_NEW_EVENT_REQUEST,
+	payload
+});
+const postNewEventRequestSuccess = payload => ({
+	type: types.POST_NEW_EVENT_REQUEST_SUCCESS,
+	payload
+});
+const postNewEventRequestFail = payload => ({
+	type: types.POST_NEW_EVENT_REQUEST_FAIL,
+	payload
+});
+
+export const postNewEvent = payload => {
+	return async dispatch => {
+		dispatch(postNewEventRequest());
+		try {
+			const { data } = await axios.post("https://api-marathon.herokuapp.com/api/v1/event", payload);
+			dispatch(postNewEventRequestSuccess(data));
+			console.log("New Event Posted", payload)
+		} catch (error) {
+			dispatch(postNewEventRequestFail(error));
+		}
+	};
+};
+
+//GET
+const getEventsRequest = payload => ({
+	type: types.GET_EVENTS_REQUEST,
+	payload
+});
+
+const getEventsRequestSuccess = payload => ({
+	type: types.GET_EVENTS_REQUEST_SUCCESS,
+	payload
+});
+
+const getEventsRequestFail = payload => ({
+	type: types.GET_EVENTS_REQUEST_FAIL,
+	payload
+});
+
+export const getEvents = () => dispatch => {
+	dispatch(getEventsRequest());
+	return axios
+		.get("https://api-marathon.herokuapp.com/api/v1/event")
+		.then(res => dispatch(getEventsRequestSuccess(res)))
+		.catch(err => dispatch(getEventsRequestFail(err)));
+};
+
+// REMOVE
+const removeEventRequest = payload => ({
+	type: types.REMOVE_EVENT_REQUEST,
+	payload
+});
+
+const removeEventSuccess = payload => ({
+	type: types.REMOVE_EVENT_REQUEST_SUCCESS,
+	payload
+});
+
+const removeEventFail = payload => ({
+	type: types.REMOVE_EVENT_REQUEST_FAIL,
+	payload
+});
+
+export const removePost = id => dispatch => {
+	dispatch(removeEventRequest());
+	return axios
+		.delete(`https://api-marathon.herokuapp.com/api/v1/event/:${id}`)
+		.then(res => dispatch(removeEventSuccess({ res, id })))
+		.catch(err => dispatch(removeEventFail(err)));
+};

+ 32 - 0
src/components/adminHeader/adminHeader.js

@@ -0,0 +1,32 @@
+import React from "react";
+import { Link, withRouter } from "react-router-dom";
+import { connect } from "react-redux";
+import "./adminHeader.scss";
+import { adminMenu } from "../../state/adminMenu";
+
+const adminHeader = props => {
+
+	return (
+		<header className="header" id="header">
+			<div className="header__left-wrapper">
+				<div className="header__logo-box">logo</div>
+				<nav className="header__nav">
+					<ul className="header__list">
+						{adminMenu.map(el =>
+							el.hideWhenAuth && props.user ? null : (
+								<li className="header__item" key={el.id}>
+									<Link to={el.path}>{el.text}</Link>
+								</li>
+							)
+						)}
+					</ul>
+				</nav>
+			</div>
+		</header>
+	);
+};
+
+const mapStateToProps = state => ({
+});
+
+export default connect(mapStateToProps)(withRouter(adminHeader));

+ 74 - 0
src/components/adminHeader/adminHeader.scss

@@ -0,0 +1,74 @@
+a {
+	color: #0a0a0a;
+	text-decoration: none;
+	transition: opacity .3s ease-in-out;
+	font-weight: 500;
+
+	&:hover {
+		color: #eee;
+	}
+}
+// a:hover: {
+// 	font-weight: 600;
+// }
+
+.header {
+	// position: fixed;
+	// top:0;
+	// left: 0;
+	position: relative;
+	width: 100%;
+	background-color: #5acec2;
+	padding: 3px 5px 0px 0px;
+	z-index: 10;
+	font-size: 1.5rem;
+	// width: 100%;
+	// padding: 5px;
+	// display: flex;
+	// justify-content: flex-start;
+	// align-items: center;
+	// background-color: #5acec2;
+
+	&__left-wrapper {
+		width: 100%;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+	}
+
+	&__logo-box {
+		margin-right: 40px;
+	}
+
+	&__nav {
+		width: 100%;
+		display: flex;
+		justify-content: flex-end;;
+		align-items: center;
+		padding: 10px;
+	}
+
+	&__list {
+		list-style: none;
+		padding: 0;
+		margin: 0;
+
+		display: flex;
+		justify-content: space-between;
+	}
+
+	&__item {
+		text-decoration: none;
+		padding: 0;
+		&:not(:last-child) {
+			margin-right: 40px;
+		}
+	}
+
+	&__log-in {
+		padding: 5px;
+		font-size: 16px;
+		margin-right: 40px;
+	}
+}
+

+ 34 - 0
src/components/adminMenu/adminMenu.js

@@ -0,0 +1,34 @@
+import React, { Component } from "react";
+import { Link, withRouter } from "react-router-dom";
+import "./adminMenu.scss";
+import { adminMenu } from "../../state/adminMenu";
+
+class AdminMenu extends Component {
+    constructor(props) {
+        super(props);
+
+        this.state = {adminMenu};
+    }
+
+    clickEventHandler = (e) => {
+        const elem = adminMenu.find(el => el.id === +e.target.id)
+        this.props.history.push(elem.path)
+    }
+
+    render(){
+        return(
+            <div className = "menu-block">
+                {adminMenu.map(el =>
+                                el.text !== "Log out" && el.text !== "Main Page" &&
+                                <div className="menu-skew" path = {el.path}  key={el.id} id = {el.id} onClick = {this.clickEventHandler}>
+                                    <h2 className = "menu-text">{el.text.toUpperCase()}</h2>
+                                    {/* <Link to={el.path} className = "menu-text">{el.text.toUpperCase()}</Link> */}
+                                </div>
+							)
+						}
+            </div>
+        )
+    }
+}
+
+export default withRouter(AdminMenu)

+ 64 - 0
src/components/adminMenu/adminMenu.scss

@@ -0,0 +1,64 @@
+.menu-block {
+  display:flex;
+  justify-content: flex-start;
+  flex-wrap: wrap;
+  margin-top: 5px;
+  
+  .menu-skew {
+    position: relative;
+    top: 0px;
+    left: 0;
+    height: 350px;
+    width: 40%;
+    overflow: hidden;
+    transition: 0.3s;
+    margin: 5px 0;
+    background-position: center;
+    background-repeat: no-repeat;
+    background-size: cover;
+    
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    
+    &:first-child {
+      clip-path: polygon(0 0,100% 0,80% 100%,0% 100%);
+       background-image: 
+    url(https://43nnuk1fz4a72826eo14gwfb-wpengine.netdna-ssl.com/wp-content/uploads/2016/09/Jens-Group-Ride_Blog.jpg);
+    }
+
+     &:nth-child(2) {
+      right: -100px;
+      clip-path: polygon(12% 0,100% 0,100% 100%,0% 100%);
+      margin-left: -7%;
+      flex-grow: 2;
+      
+      background-image: url(https://www.getthegloss.com/media/image/marathon.jpg);
+    } 
+    
+     &:nth-child(3){
+      flex-grow: 2;
+      clip-path: polygon(0 0,100% 0,86% 100%,0% 100%);
+      background-image: url(https://images.fitnessmagazine.mdpcdn.com/sites/fitnessmagazine.com/files/styles/slide/public/800_triathletes-swimming.jpg?itok=vEUQ17sh);
+    }
+    
+     &:last-child {
+      flex-grow: 0;
+      clip-path: polygon(24% 0,100% 0,100% 100%,0% 100%);
+      margin-left: -8.5%;
+      background-image: url(https://cdn.shopify.com/s/files/1/0235/4757/articles/CMP-201903-SinchBikes_KGorge-Highres-6214_600x300_crop_center.jpg?v=1561970623);
+    }
+
+    &:hover {
+      opacity: 0.5;
+    }
+    
+    .menu-text{
+      color: #5acec2;
+      font-size: 3rem;
+      letter-spacing: 0.2;
+      font-weight: 600;
+      text-shadow: 3px 3px 3px #000;
+    }
+  }
+}

+ 172 - 0
src/components/adminPhotogalary/adminPhotogalary.js

@@ -0,0 +1,172 @@
+import React, { Component, Fragment } from "react";
+
+import "./adminPhotogalary.scss";
+import { form } from "../../state/photogalaryFormData";
+import Input from "../input/input";
+import Select from "../select/select";
+import Button from "../button/button";
+import Header from '../adminHeader/adminHeader';
+
+export default class PhotogalaryForm extends Component {
+
+    state = {
+        form,
+        validForm: false
+    }
+
+    validator = (rules, value) => {
+        const { requred, minL } = rules;
+
+        let valid = true;
+        if (requred) {
+            valid = value.trim() === "" && valid;
+        }
+
+        if (minL) {
+            valid = value.trim().length < minL && valid;
+        }
+
+        return valid;
+    };
+
+    submit = e => {
+        e.preventDefault();
+
+        const values = Object.keys(this.state.form).reduce((prev, elem) => {
+            return { ...prev, [elem]: this.state.form[elem].value };
+        }, {});
+
+        this.props.submitHandler(values);
+    };
+
+    onChangeHandler = e => {
+        const { name, value, id } = e.target;
+
+        this.setState(prevState => {
+            const values = Object.keys(prevState.form).reduce((prev, elem) => {
+                if (elem === name) return prev.concat(value);
+                return prev.concat(prevState.form[elem].value);
+            }, []);
+
+            return {
+                ...prevState,
+                form: {
+                    ...prevState.form,
+                    [name]: {
+                        ...prevState.form[name],
+                        value,
+                        touch: true,
+                        // fail: this.validator(prevState.form[name].validation, value)
+                    },
+                    pictures: prevState.form.pictures.map(el => (el.id === +id ? { ...el, value } : el))
+                },
+                validForm: values.some(value => value)
+            };
+        });
+    };
+
+    addPicture = (e) => {
+        this.setState(prevState => ({
+            ...prevState,
+            form: {
+                ...prevState.form,
+                pictures: prevState.form.pictures.concat({
+                    id: +Math.random()
+                        .toString()
+                        .substr(2, 100),
+                    type: "input",
+                    name: "pictures",
+                    label: "Picture",
+                    placeholder: 'Enter picture url',
+                    value: ""
+                })
+                // ...prevState.form.pictures,
+                // value: prevState.form.pictures.value.concat(e.value)
+                // 	id: Math.random()
+                // 	.toString()
+                // 	.substr(2, 100),
+                // 	value: prevState.form.pictures.value.concat({
+                // 		id: Math.random()
+                // 			.toString()
+                // 			.substr(2, 100),
+                // 		url: ""
+                // )
+                // }
+            }
+        }));
+    };
+
+    render() {
+        const { form, validForm } = this.state;
+        console.log(this.state)
+
+        const { error } = this.props;
+        const disPictureAdd = form.pictures.every(el => el.value)
+        const disPictureRm = form.pictures.length === 1;
+
+        return (
+            <>
+                <Header />
+                <div className="photogalary-form">
+                    <div className="photogalary-form__content">
+                        <h2 className="photogalary-form__form-title">ADD EVENT PHOTOGALARY</h2>
+                        <form onSubmit={this.submit} className="photogalary-form__photogalary-form">
+
+                            <div className="photogalary-form__photogalary-form__selects">
+                            {Object.keys(form.selects).map((input_name, index) => (
+                                <Select
+                                    key={form.selects[input_name].id}
+                                    id={form.selects[input_name].id}
+                                    name={form.selects[input_name].name}
+                                    // value={form.selects[input_name].value}
+                                    fail={form.selects[input_name].fail}
+                                    touch={form.selects[input_name].touch}
+                                    label={form.selects[input_name].label}
+                                    placeholder={form.selects[input_name].placeholder}
+                                    onChange={this.onChangeHandler}
+                                >
+                                    {form.selects[input_name].options.map(elem =>
+                                        <option key={elem} value={elem}> {elem} </option>)}
+
+                                </Select>
+                            )
+                            )}
+                            </div>
+
+                            {form.pictures.map((el, i) => (
+                                <div key={`${el.id}/${i}`}>
+
+                                    {console.log('elem', el)}
+                                    <Input
+                                        className = "photogalary-form__picture-input"
+                                        id={el.id}
+                                        name={el.name}
+                                        value={el.value}
+                                        label={el.label}
+                                        // key={form[input_name].id}
+                                        // id={form[input_name].id}
+                                        // name={form[input_name].name}
+                                        // value={el.url}
+                                        // fail={form[input_name].fail}
+                                        // touch={form[input_name].touch}
+                                        // label={form[input_name].label}
+                                        placeholder={el.placeholder}
+                                        onChange={this.onChangeHandler}
+                                    />
+
+                                    {/* {!disPictureRm && <Button text="-" onClick={rmHobby.bind(null, el.id)} />} */}
+                                </div>
+                            ))}
+                            <Button className="photogalary-form__picture-btn" text="Add Picture" onClick={this.addPicture} disabled={!disPictureAdd} />
+
+                            {error && <p className="photogalary-form__error-photogalary-text">{error}</p>}
+                            <div className="photogalary-form__control-box">
+                                <Button disabled={!validForm} className="photogalary-form__submit-btn" type="submit" text="Add Photogalary" />
+                            </div>
+                        </form>
+                    </div>
+                </div>
+            </>
+        );
+    }
+}

+ 158 - 0
src/components/adminPhotogalary/adminPhotogalary.scss

@@ -0,0 +1,158 @@
+.photogalary-form {
+	width: 100%;
+	max-width: 1200px;
+	height: 100%;
+	margin: auto;
+
+	&__content {
+        width: 90%;
+        height: 100%;
+		position: relative;
+		// top: 40%;
+		// left: 20%;
+		// transform: translate(-50%, -50%);
+		margin: auto;
+		padding: 2rem;
+		border-left: 1px solid #5acec2;
+        border-right: 1px solid #5acec2;
+        border-bottom: 1px solid #5acec2;
+		background-color: #eee;
+		padding-bottom: 3%;
+	}
+
+	&__photogalary-form__selects{
+	position: relative;
+	display: flex;
+	flex-direction: row;
+	flex-wrap: wrap;
+	}
+
+	&__text {
+		font-size: 1.6rem;
+    }
+    
+    &__form-title{
+        // font-size: 2rem;
+        // margin-bottom: 1rem;
+		// color: #5acec2;
+		color: #5acec2;
+		font-size: 3rem;
+		letter-spacing: 0.2;
+		font-weight: 600;
+		text-shadow: 3px 3px 3px #000;
+		margin-bottom: 3%;
+    }
+
+	&__toggle-span {
+		font-size: 1.6rem;
+		font-weight: bold;
+		color: rgb(102, 104, 223);
+	}
+
+	&__photogalary-box {
+		margin-bottom: 2rem;
+	}
+
+	&__photogalary-box {
+		margin-top: 2rem;
+		width: 100%;
+    }
+    
+    &__picture-input {
+        display: flex;
+        flex-direction: column;
+        width: 100%;
+    
+        font-size: 1.3rem;
+        font-weight: 600;
+    }
+
+	&__error-photogalary-text {
+		margin-top: 1rem;
+		text-align: center;
+		width: 100%;
+		color: #8f240a;
+		font-size: 2rem;
+	}
+
+	&__picture {
+		width: 100%;
+		height: 30%;
+		border: 1px solid #5acec2;
+		margin-bottom: 2rem;
+    }
+
+    &__picture-btn {
+		width: 20%;
+		border: 1px solid #5acec2;
+		border-radius: 3px;
+        display: block;
+        margin-bottom: 3%;
+
+		color: #fff;
+		background-color: #5acec2;
+		border: none;
+		cursor: pointer;
+		font-weight: 500;
+		font-size: 1.2rem;
+		transition: 0.2s;
+		padding: 0.5rem;
+
+		&:focus {
+			outline: none;
+		}
+		&:hover {
+			outline: none;
+			transform: translateY(-1px);
+			box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.2);
+			&:after {
+				transform: scaleX(1.4) scaleY(1.6);
+				opacity: 0;
+			}
+		}
+
+		&:disabled {
+			color: #5acec2;
+			border: 1px solid #5acec2;
+			background-color: #eee;
+        }
+        
+    }
+
+	&__submit-btn {
+		width: 100%;
+		border: 1px solid #5acec2;
+		border-radius: 3px;
+		display: block;
+
+		color: #fff;
+		background-color: #5acec2;
+		border: none;
+		cursor: pointer;
+		font-weight: 600;
+		font-size: 1.2rem;
+		transition: 0.2s;
+		padding: 1.3rem 1rem;
+
+		&:focus {
+			outline: none;
+		}
+		&:hover {
+			outline: none;
+			transform: translateY(-1px);
+			box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.2);
+			&:after {
+				transform: scaleX(1.4) scaleY(1.6);
+				opacity: 0;
+			}
+		}
+
+		&:disabled {
+			color: #5acec2;
+			border: 1px solid #5acec2;
+			background-color: #eee;
+        }
+        
+    }
+
+}

+ 88 - 0
src/components/adminPhotogalary/adminPhotogalaryReduxForm.js

@@ -0,0 +1,88 @@
+import React, { Component } from "react";
+import { reduxForm, Field } from "redux-form";
+
+import "./adminPhotogalary.scss";
+import { addPhotogalaryInitialValue } from "../../state/photogalaryFormData";
+import { customInput } from "../customFields/customInput/customInput";
+import { customSelect } from "../customFields/customSelect/customSelect";
+import { eventTypes } from "../../state/addEventInitialValue";
+import { eventFormValidation } from "../../utils/eventFormValidation";
+
+class PhotogalaryReduxForm extends Component {
+
+    state = {
+        addPhotogalaryInitialValue
+    }
+
+    submit = e => {
+        e.preventDefault();
+
+        const values = Object.keys(this.state.addPhotogalaryInitialValue).reduce((prev, elem) => {
+            return { ...prev, [elem]: this.state.addPhotogalaryInitialValue[elem].value };
+        }, {});
+
+        this.props.submitHandler(values);
+    };
+
+    onChangeHandler = e => {
+        const { name, value, id } = e.target;
+
+        this.setState(prevState => {
+            const values = Object.keys(prevState.addPhotogalaryInitialValue).reduce((prev, elem) => {
+                if (elem === name) return prev.concat(value);
+                return prev.concat(prevState.addPhotogalaryInitialValue[elem].value);
+            }, []);
+
+            return {
+                ...prevState,
+                addPhotogalaryInitialValue: {
+                    ...prevState.addPhotogalaryInitialValue,
+                    pictures: prevState.addPhotogalaryInitialValue.pictures.map(el => (el.id === +id ? { ...el, value } : el))
+                }
+            };
+        });
+    };
+
+    addPicture = (e) => {
+        this.setState(prevState => ({
+            ...prevState,
+            addPhotogalaryInitialValue: {
+                ...prevState.addPhotogalaryInitialValue,
+                pictures: prevState.addPhotogalaryInitialValue.pictures.concat({
+                    id: +Math.random()
+                        .toString()
+                        .substr(2, 100),
+                    value: ""
+                })
+            }
+        }));
+    };
+
+    render() {
+        const { addPhotogalaryInitialValue } = this.state;
+        console.log(this.state)
+
+        const { handleSubmit } = this.props;
+
+        return (
+            <form className="event-form__event-form__main" onSubmit={handleSubmit(this.submit)}>
+
+            <Field name="eventType" label="Event Type" required component={customSelect} >
+                {eventTypes.map(elem => <option key={elem.id} value={elem.optionName}>{elem.optionName}</option>)}
+            </Field>
+            <Field name="title" label="Event Title" required component={customInput} />
+
+            {addPhotogalaryInitialValue.pictures.map((el, i) => (
+                <Field key={`${el.id}/${i}`}  name="pictures" label="Picture" className="input-box__wide" placeholder='Enter picture url' component={customInput} />
+            ))}
+            <button type="button" className="photogalary-form__picture-btn" onClick={this.addPicture} >Add Picture</button>
+            <div className="event-form__control-box">
+                <button className="event-form__submit-btn" >Add Event</button>
+            </div>
+        </form>
+           
+        );
+    }
+}
+
+export default reduxForm({ form: "photogalaryForm", enableReinitialize: true })(PhotogalaryReduxForm);

+ 28 - 0
src/components/customFields/customInput/customInput.js

@@ -0,0 +1,28 @@
+import React from "react";
+import "./customInput.scss";
+
+export const customInput = ({
+	input: { ...inputProps },
+	meta: { error, touched, ...rest },
+	id,
+	label,
+	placeholder,
+	className = "input-box",
+	type = "text",
+	required
+}) => (
+	<label htmlFor={id} className ={className} >
+		<span>
+			{label} {required && <span className="input-box__required">*</span>}
+		</span>
+		<input
+			{...inputProps}
+			autoComplete="off"
+			className={error && touched ? "input-box__input input-box__input--fail" : "input-box__input"}
+			id={id}
+			placeholder={placeholder}
+			type={type}
+		/>
+		{error && touched && <span className="input-box__error">{error}</span>}
+	</label>
+);

+ 81 - 0
src/components/customFields/customInput/customInput.scss

@@ -0,0 +1,81 @@
+.input-box {
+	display: flex;
+    flex-direction: column;
+	width: 40%;
+	margin-right: 10%;
+
+	font-size: 1.3rem;
+	font-weight: 600;
+
+	&__input {
+		font-family: inherit;
+		color: inherit;
+		font-size: 1.5rem;
+		padding: 0.5rem 1rem;
+		border-radius: 2px;
+		background-color: rgba(255, 255, 255, 0.5);
+		border: none;
+		border-bottom: 3px solid #eee;
+		display: inline-block;
+        transition: all 0.3s;
+        margin-bottom: 1rem;
+
+		&:focus {
+			outline: none;
+			box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.1);
+			border-bottom: 3px solid #5acec2;
+		}
+
+		&--fail {
+			border-bottom: 3px solid #9e4560;
+		}
+
+		&::placeholder {
+			font-size: 1.3rem;
+			color:  #cecece;
+		}
+
+	}
+
+}
+
+.input-box__wide {
+	display: flex;
+    flex-direction: column;
+	width: 100%;
+
+	font-size: 1.3rem;
+	font-weight: 600;
+
+	&__input {
+		font-family: inherit;
+		color: inherit;
+		font-size: 1.5rem;
+		padding: 0.5rem 1rem;
+		border-radius: 2px;
+		background-color: rgba(255, 255, 255, 0.5);
+		border: none;
+		border-bottom: 3px solid #eee;
+		// width: 40%;
+		display: inline-block;
+        transition: all 0.3s;
+        margin-bottom: 1rem;
+
+		&:focus {
+			outline: none;
+			box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.1);
+			border-bottom: 3px solid #5acec2;
+		}
+
+		&--fail {
+			border-bottom: 3px solid #9e4560;
+		}
+
+		&::placeholder {
+			font-size: 1.3rem;
+			color:  #cecece;
+		}
+
+	}
+
+}

+ 14 - 0
src/components/customFields/customSelect/customSelect.js

@@ -0,0 +1,14 @@
+import React from "react";
+import "./customSelect.scss";
+
+export const customSelect = ({ label, input, children, id, meta }) => {
+	return (
+		<div className="form-block__select" id = {'select_'+id}>
+			<label className="select-box" htmlFor={id}>{label}</label>
+			<select {...input} className = "select-box__input" >{children}</select>
+            {meta.error && meta.touched && (
+				<span className="error">{meta.error}</span>
+			)}
+		</div>
+	)
+}

+ 46 - 0
src/components/customFields/customSelect/customSelect.scss

@@ -0,0 +1,46 @@
+.form-block__select{
+    width: 40%;
+    margin-right: 10%;
+.select-box {
+	display: flex;
+	flex-direction: column;
+	
+	font-size: 1.3rem;
+	font-weight: 600;
+
+	&__input {
+		font-family: inherit;
+		color: inherit;
+		font-size: 1.5rem;
+		padding: 0.5rem 1rem;
+		border-radius: 2px;
+		background-color: rgba(255, 255, 255, 0.5);
+		border: none;
+		border: 2px solid #eee;
+		width: 100%;
+		display: block;
+        transition: all 0.3s;
+        margin-bottom: 1rem;
+
+		&:focus {
+			outline: none;
+			box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.1);
+			border: 2px solid #5acec2;
+		}
+
+		&--fail {
+			border-bottom: 3px solid #9e4560;
+		}
+
+		&::placeholder {
+			font-size: 1.3rem;
+			color:  #cecece;
+		}
+    }
+    &____option{
+        &:hover {
+            background-color: #5acec2;
+        }
+    }
+}
+}

+ 14 - 0
src/components/customFields/customTextarea/customTextarea.js

@@ -0,0 +1,14 @@
+import React from "react";
+import "./customTextarea.scss";
+
+export const customTextarea = ({ label, rows, input, placeholder, id, name, meta }) => {
+	return (
+		<div className="form-block__text-area" id = {'textarea_'+id}>
+			<label className="textarea-box">{label}</label>
+			<textarea className="textarea-box__input" {...input} rows={rows} name={name} placeholder={placeholder}/>
+            {meta.error && meta.touched && (
+				<span className="error">{meta.error}</span>
+			)} 
+		</div>
+	)
+}

+ 40 - 0
src/components/customFields/customTextarea/customTextarea.scss

@@ -0,0 +1,40 @@
+.form-block__text-area{
+	width: 100%;
+.textarea-box {
+	display: flex;
+	flex-direction: column;
+	
+	font-size: 1.3rem;
+	font-weight: 600;
+
+	&__input {
+		font-family: inherit;
+		color: inherit;
+		font-size: 1.5rem;
+		padding: 0.5rem 1rem;
+		border-radius: 2px;
+		background-color: rgba(255, 255, 255, 0.5);
+		border: none;
+		border: 2px solid #eee;
+		width: 100%;
+		display: block;
+        transition: all 0.3s;
+        margin-bottom: 1rem;
+
+		&:focus {
+			outline: none;
+			box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.1);
+			border: 2px solid #5acec2;
+		}
+
+		&--fail {
+			border-bottom: 3px solid #9e4560;
+		}
+
+		&::placeholder {
+			font-size: 1.3rem;
+			color:  #cecece;
+		}
+	}
+}
+}

+ 109 - 0
src/components/eventForm/eventForm.scss

@@ -0,0 +1,109 @@
+.event-form {
+	width: 100%;
+	height: 100%;
+	margin: auto;
+	background: -webkit-linear-gradient(top, #fff, #5acec2); /* Safari 5.1, iOS 5.0-6.1, Chrome 10-25, Android 4.0-4.3 */
+	background: -moz-linear-gradient(top, #fff, #5acec2); /* Firefox 3.6-15 */
+	background: -o-linear-gradient(top,  #fff, #5acec2); /* Opera 11.1-12 */
+	background: linear-gradient(to bottom,  #fff, #5acec2);
+
+	&__content {
+		max-width: 1000px;
+		position: relative;
+		margin: auto;
+		padding: 2rem;
+		border-left: 1px solid #5acec2;
+		border-right: 1px solid #5acec2;
+		background-color: #eee;
+		padding-bottom: 3%;
+	}
+
+	&__event-form__main{
+	position: relative;
+	display: flex;
+	flex-direction: row;
+	flex-wrap: wrap;
+	}
+
+	&__text {
+		font-size: 1.6rem;
+    }
+    
+    &__form-title{
+		color: #5acec2;
+		font-size: 3rem;
+		letter-spacing: 0.2;
+		font-weight: 600;
+		text-shadow: 3px 3px 3px #000;
+		margin-bottom: 3%;
+    }
+
+	&__toggle-span {
+		font-size: 1.6rem;
+		font-weight: bold;
+		color: rgb(102, 104, 223);
+	}
+
+	&__event-box {
+		margin-bottom: 2rem;
+	}
+
+	&__control-box {
+		margin-top: 2rem;
+		width: 100%;
+	}
+
+	&__error-event-text {
+		margin-top: 1rem;
+		text-align: center;
+		width: 100%;
+		color: #8f240a;
+		font-size: 2rem;
+	}
+
+	&__picture {
+		width: 100%;
+		height: 400px;
+
+		overflow: hidden;
+		border: 1px solid #5acec2;
+		margin-bottom: 2rem;
+	}
+
+	&__submit-btn {
+		width: 100%;
+		border: 1px solid #5acec2;
+		border-radius: 3px;
+		display: block;
+
+		color: #fff;
+		background-color: #5acec2;
+		border: none;
+		cursor: pointer;
+		font-weight: 600;
+		font-size: 1.2rem;
+		transition: 0.2s;
+		padding: 1.3rem 1rem;
+
+		&:focus {
+			outline: none;
+		}
+		&:hover {
+			outline: none;
+			transform: translateY(-1px);
+			box-shadow: 0 1rem 2rem rgba(0, 0, 0, 0.2);
+			&:after {
+				transform: scaleX(1.4) scaleY(1.6);
+				opacity: 0;
+			}
+		}
+
+		&:disabled {
+			color: #5acec2;
+			border: 1px solid #5acec2;
+			background-color: #eee;
+		}
+	}
+
+
+}

+ 89 - 0
src/components/eventForm/eventReduxForm.js

@@ -0,0 +1,89 @@
+import React, { Component } from "react";
+import { reduxForm, Field } from "redux-form";
+import "./eventForm.scss";
+import { customInput } from "../customFields/customInput/customInput";
+import { customTextarea } from "../customFields/customTextarea/customTextarea";
+import { customSelect } from "../customFields/customSelect/customSelect";
+import { eventFormInitialValue, eventTypes } from "../../state/addEventInitialValue";
+import { eventFormValidation } from "../../utils/eventFormValidation";
+
+const EventReduxForm = ({
+    handleSubmit,
+    postNewEvent
+}) => {
+
+    const submit = value => {
+        console.log('submit values', value)
+        postNewEvent(value)
+    };
+
+    // const resetForm = () => {
+    // 	reset();
+    // 	resetInitValue();
+    // }
+
+    // onChangeHandler = e => {
+    //     const { name, value, id } = e.target;
+
+    //     this.setState(prevState => {
+    //         const values = Object.keys(prevState.form).reduce((prev, elem) => {
+    //             if (elem === name) return prev.concat(value);
+    //             return prev.concat(prevState.form[elem].value);
+    //         }, []);
+
+    //         return {
+    //             ...prevState,
+    //             form: {
+    //                 ...prevState.form,
+    //                 [name]: {
+    //                     ...prevState.form[name],
+    //                     value,
+    //                     // touch: true,
+    //                     // fail: this.validator(prevState.form[name].validation, value)
+    //                 },
+    //                 // pictures: prevState.form.pictures.map(el => (el.id === +id ? { ...el, value } : el))
+    //             },
+    //             validForm: values.some(value => value)
+    //         };
+    //     });
+    // };
+
+    // render() {
+    //     const { form, validForm } = this.state;
+    //     const { error } = this.props;
+
+    return (
+        <form className="event-form__event-form__main" onSubmit={handleSubmit(submit)}>
+            {console.log('eventFormInitialValue', eventFormInitialValue)}
+
+            <img className="event-form__picture" src={eventFormInitialValue.mainBannerPicture} />
+            <Field name="mainBannerPicture" className="input-box__wide" label="Main Banner Picture" component={customInput} />
+            <Field name="title" label="Event Title" className="input-box__wide" required component={customInput} />
+            <Field name="eventType" label="Event Type" required component={customSelect} >
+                {eventTypes.map(elem => <option key={elem.id} value={elem.optionName}>{elem.optionName}</option>)}
+            </Field>
+            <Field name="eventDate" type="date" label="Event Date" required component={customInput} />
+            <Field name="country" label="Country" required component={customInput} />
+            <Field name="city" label="City" required component={customInput} />
+            <Field name="overview" label="Overview" rows="10" placeholder='Enter event overview' component={customTextarea} />
+            <Field name="contentPicture" label="Content Picture" className="input-box__wide" placeholder='Enter picture url' component={customInput} />
+            <Field name="contentVideo" label="Content Video" className="input-box__wide" placeholder='Enter video url' component={customInput} />
+            <Field name="marathoneDistancePrice" label="Marathone Distance Price" component={customInput} />
+            <Field name="halfmarathoneDistancePrice" label="Halfmarathone Distance Price" component={customInput} />
+            <Field name="ageLimit" label="Age Limit" component={customTextarea} />
+            <Field name="awardMedals" label="Award Medals" component={customTextarea} />
+            <Field name="maximumTime" label="Maximum Time" component={customTextarea} />
+            <Field name="aidStations" label="Aid Stations" component={customTextarea} />
+            <Field name="equipmentStorage" label="Equipment Storage" component={customTextarea} />
+            <Field name="parking" label="Parking" component={customTextarea} />
+            <Field name="refreshments" label="Refreshments" component={customTextarea} />
+            <Field name="map" label="Map" className="input-box__wide" placeholder='Enter picture url' component={customInput} />
+
+            <div className="event-form__control-box">
+                <button className="event-form__submit-btn" >Add Event</button>
+            </div>
+        </form>
+    );
+}
+
+export default reduxForm({ form: "eventForm", validate: eventFormValidation, enableReinitialize: true })(EventReduxForm);

+ 45 - 0
src/conteiners/adminAddEventPage/adminAddEventPage.js

@@ -0,0 +1,45 @@
+import React, { Component } from "react";
+import { connect } from "react-redux";
+import * as actions from "../../actions/adminMainPageActions";
+import AdminHeader from "../../components/adminHeader/adminHeader";
+import Form from "../../components/eventForm/eventReduxForm";
+import {eventFormInitialValue} from "../../state/addEventInitialValue"
+
+class AdminAddEventPage extends Component {
+    state = { 
+        eventFormInitialValue
+     };
+
+    componentDidMount() {
+	    this.props.getEvents();
+	}
+
+    render() {
+        const {
+            postNewEvent,
+            eventList
+        } = this.props
+
+        console.log('eventList', eventList)
+
+		console.log("initialValues", this.state.eventFormInitialValue);
+		return (
+            <>
+            <AdminHeader/>
+            <div className="event-form">
+                <div className="event-form__content">
+                    <h2 className="event-form__form-title">ADD NEW EVENT</h2>
+        
+                    <Form postNewEvent = {postNewEvent}/>
+                </div>
+            </div>
+            </>
+		);
+	}
+}
+
+const mapStateToProps = state => ({
+	eventList: state.adminMainPageReducer.eventList,
+});
+
+export default connect(mapStateToProps, actions)(AdminAddEventPage);

+ 1 - 1
src/conteiners/adminMainPage/adminMainPage.js

@@ -2,7 +2,7 @@ import React, {Component} from 'react';
 import { connect } from "react-redux";
 // import * as actions from "../../actions/carElementActions";
 import "./adminMainPage.scss";
-import Header from '../../components/header/header';
+import Header from '../../components/adminHeader/adminHeader';
 import AdminMenu from '../../components/adminMenu/adminMenu';
 
 class AdminMainPage extends Component {

+ 44 - 0
src/conteiners/adminPhotogalaryPage/adminPhotogalaryPage.js

@@ -0,0 +1,44 @@
+import React, { Component } from "react";
+import { connect } from "react-redux";
+import * as actions from "../../actions/adminMainPageActions";
+import AdminHeader from "../../components/adminHeader/adminHeader";
+import PhotogalaryForm from "../../components/adminPhotogalary/adminPhotogalaryReduxForm";
+import {eventFormInitialValue} from "../../state/addEventInitialValue"
+
+class AdminAddPhotogalarytPage extends Component {
+    // state = { 
+    //     eventFormInitialValue
+    //  };
+
+    // componentDidMount() {
+	//     this.props.getEvents();
+	// }
+
+    render() {
+        const {
+            postNewEvent,
+            eventList
+        } = this.props
+
+        // console.log('eventList', eventList)
+
+		// console.log("initialValues", this.state.eventFormInitialValue);
+		return (
+            <>
+                <AdminHeader />
+                <div className="photogalary-form">
+                    <div className="photogalary-form__content">
+                        <h2 className="photogalary-form__form-title">ADD EVENT PHOTOGALARY</h2>
+                        <PhotogalaryForm />
+                    </div>
+                </div>
+            </>
+		);
+	}
+}
+
+const mapStateToProps = state => ({
+	eventList: state.adminMainPageReducer.eventList,
+});
+
+export default connect(mapStateToProps, actions)(AdminAddPhotogalarytPage);

+ 0 - 2
src/index.js

@@ -5,14 +5,12 @@ import { Provider } from "react-redux";
 import * as serviceWorker from './serviceWorker';
 import './index.scss';
 import Router from './router';
-// import Header from './components/header/header';
 
 import { store } from "./store.js";
 
 ReactDOM.render(
     <Provider store={store}>
         <BrowserRouter>
-            {/* <Header /> */}
             <Router />
         </BrowserRouter>
     </Provider>, 

+ 49 - 17
src/reducers/adminMainPageReducer.js

@@ -1,25 +1,57 @@
 import * as types from "../actionTypes/actionTypes"
+import {eventFormInitialValue} from "../state/addEventInitialValue"
 
-const initialValue = {
 
-};
-
-export default (state = initialValue, action) => {
+export default (state = eventFormInitialValue, action) => {
 
     switch (action.type) {
-      
-        // case types.SHOW_CAR_ELEMENT: {
-        //   const item = state.carList.find(el => el.id === action.payload);
-        //   return {
-        //     ...state,
-        //     item,
-        //     rentalData: {
-        //       ...state.rentalData,
-        //       carId: item.id
-        //     },
-        //     showCarFlag: true
-        //   }
-        // }
+
+		//POST
+        case types.POST_NEW_EVENT_REQUEST_SUCCESS: {
+			console.log('reducer add event success', action.payload)
+			return {
+				state,
+				addEventMessage: "New event has been added"
+			}
+		}
+		case types.POST_NEW_EVENT_REQUEST_FAIL: {
+			console.log('reducer add event fail', action.payload)
+			return { ...state, error: action.payload }
+		}
+
+		// GET ALL EVENTS
+		case types.GET_EVENTS_REQUEST: {
+			return state;
+		}
+		case types.GET_EVENTS_REQUEST_SUCCESS: {
+			const { data } = action.payload;
+			const eventList = Object.keys(data).reduce((prev, elem) => {
+				return prev.concat({
+					...data[elem],
+					// id: elem
+				});
+			}, []);
+			return { ...state, eventList };
+		}
+		case types.GET_EVENTS_REQUEST_FAIL: {
+			return state;
+		}
+
+		// REMOVE
+		case types.REMOVE_EVENT_REQUEST: {
+			return state;
+		}
+		case types.REMOVE_EVENT_REQUEST_SUCCESS: {
+			const { id } = action.payload;
+			return {
+				...state,
+				eventList: state.eventList.filter(el => el.id !== id)
+			};
+		}
+
+		case types.REMOVE_EVENT_REQUEST_FAIL: {
+			return state;
+		}
 
         default:
 			return state; 

+ 29 - 0
src/state/addEventInitialValue.js

@@ -0,0 +1,29 @@
+export const eventFormInitialValue = {
+    mainBannerPicture: "https://tokyo2020.org/assets/img/pages/news/20180802-01-photo-main.jpg",
+    title: "",
+    eventType: "",
+    eventDate: "",
+    country: "",
+    city: "",
+    overview: "",
+    contentPicture: "",
+    contentVideo: "",
+    marathoneDistancePrice: "",
+    halfmarathoneDistancePrice: "",
+    ageLimit: "",
+    awardMedals: "",
+    maximumTime: "",
+    aidStations: "",
+    equipmentStorage: "",
+    parking: "",
+    refreshments: "",
+    map: "",
+    addEventMessage: null
+}
+
+export const eventTypes = [
+    { optionName: 'Select Event Type', id: 1 },
+    { optionName: 'Triathlon', id: 2 },
+    { optionName: 'Marathon', id: 3 },
+    { optionName: 'Cycling', id: 4 }
+]

+ 9 - 0
src/state/adminMenu.js

@@ -0,0 +1,9 @@
+export const adminMenu = [
+	{ path: "/admin", id: 0, text: "Main Page", hideWhenAuth: false },
+	{ path: "/admin/add_new_event", id: 1, text: "Add New Event", hideWhenAuth: false },
+	{ path: "/admin/my_events", id: 2, text: "My Events", hideWhenAuth: false },
+    { path: "/admin/photogalary", id: 3, text: "Photogalery", hideWhenAuth: false },
+    { path: "/admin/feedbacks", id: 4, text: "Feedbacks", hideWhenAuth: false },
+	{ path: "/main_page", id: 5, text: "Log out", hideWhenAuth: false },
+	// { path: "/auth", id: 5, text: "Auth", hideWhenAuth: true }
+];

+ 41 - 0
src/state/photogalaryFormData.js

@@ -0,0 +1,41 @@
+export const form = {
+    selects: {
+        eventType: {
+        id: 1,
+        type: "select",
+        name: "eventType",
+        label: "Choose Event Type",
+        options:['Select Event Type', 'Triathlon', 'Marathon','Cycling'],
+        value: ""
+        },
+        eventTitle: {
+        id: 2,
+        type: "select",
+        name: "eventTitle",
+        label: "Choose Event Title",
+        options:[],
+        value: ""
+        }
+    },
+    pictures: [
+        {
+            id: 3,
+            type: "input",
+            name: "pictures",
+            label: "Picture",
+            placeholder: 'Enter picture url',
+            value: ""
+        }
+    ]
+}
+
+export const addPhotogalaryInitialValue = {
+    eventType: "",
+    eventTytle: "",
+    pictures: [
+        {
+            id: 1,
+            value: ""
+        }
+    ]
+}