Artem 3 роки тому
батько
коміт
f73c85796a

+ 6 - 0
jsconfig.json

@@ -0,0 +1,6 @@
+{
+  "compilerOptions": {
+    "baseUrl": "src"
+  },
+  "include": ["src"]
+}

Різницю між файлами не показано, бо вона завелика
+ 2224 - 4
package-lock.json


+ 4 - 0
package.json

@@ -6,9 +6,13 @@
     "@testing-library/jest-dom": "^5.14.1",
     "@testing-library/react": "^11.2.7",
     "@testing-library/user-event": "^12.8.3",
+    "classnames": "^2.3.1",
+    "hook-easy-form": "^2.7.2",
+    "node-sass": "^6.0.1",
     "react": "^17.0.2",
     "react-dom": "^17.0.2",
     "react-redux": "^7.2.4",
+    "react-router-dom": "^5.2.1",
     "react-scripts": "4.0.3",
     "redux": "^4.1.1",
     "redux-logger": "^3.0.6",

+ 0 - 38
src/App.css

@@ -1,38 +0,0 @@
-.App {
-  text-align: center;
-}
-
-.App-logo {
-  height: 40vmin;
-  pointer-events: none;
-}
-
-@media (prefers-reduced-motion: no-preference) {
-  .App-logo {
-    animation: App-logo-spin infinite 20s linear;
-  }
-}
-
-.App-header {
-  background-color: #282c34;
-  min-height: 100vh;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  font-size: calc(10px + 2vmin);
-  color: white;
-}
-
-.App-link {
-  color: #61dafb;
-}
-
-@keyframes App-logo-spin {
-  from {
-    transform: rotate(0deg);
-  }
-  to {
-    transform: rotate(360deg);
-  }
-}

+ 0 - 1
src/App.js

@@ -5,7 +5,6 @@ import { useDispatch, useSelector } from 'react-redux';
 import { decrease, increase } from './store/counter/actions';
 import { getFakeData, updateValue } from './store/fake/actions';
 
-import './App.css';
 
 function App() {
 

+ 0 - 8
src/App.test.js

@@ -1,8 +0,0 @@
-import { render, screen } from '@testing-library/react';
-import App from './App';
-
-test('renders learn react link', () => {
-  render(<App />);
-  const linkElement = screen.getByText(/learn react/i);
-  expect(linkElement).toBeInTheDocument();
-});

+ 22 - 0
src/components/inputs/text-input/text-input.jsx

@@ -0,0 +1,22 @@
+import classes from './text-input.module.scss';
+
+const Input = ({
+  id = Math.random().toString(),
+  label,
+  touched,
+  error,
+  value,
+  onChange,
+  name,
+  type
+}) => {
+  return (
+    <div className={classes.container}>
+      {label && <label htmlFor={id} className={classes['container__label']}>{label}</label>}
+      <input type={type} id={id} name={name} value={value} onChange={onChange} className={classes['container__input']} />
+      {touched && error && <span className={classes['container__error']}>{error}</span>}
+    </div>
+  )
+}
+
+export default Input;

+ 75 - 0
src/components/inputs/text-input/text-input.module.scss

@@ -0,0 +1,75 @@
+$input-text-color: rgb(74, 86, 96);
+
+.container {
+  height: 3.6rem;
+  width: 100%;
+  position: relative;
+  display: flex;
+  background: var(--base-white);
+  border-radius: 4px;
+  border: 1px solid var(--primary);
+  padding: 0 1.2rem;
+  box-sizing: border-box;
+  align-items: center;
+
+  &__input {
+    color: $input-text-color;
+    background: transparent;
+    font-style: normal;
+    font-weight: 500;
+    font-size: 1.4rem;
+    font-family: inherit;
+    line-height: 1.6rem;
+    border: none;
+    box-shadow: none;
+    width: 100%;
+    height: 100%;
+    padding: 0;
+  }
+
+  &__label {
+    color: var(--text-color);
+    font-family: inherit;
+    font-size: 1.4rem;
+    font-weight: 400;
+    position: absolute;
+    left: 0;
+    top: -2rem;
+
+    text-transform: none;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    max-width: 100%;
+    display: inline-block;
+  }
+
+  &__error {
+    border: 1px solid var(--button-red-hover);
+  }
+
+  &__error-span {
+    color: var(--button-red-hover);
+    font-weight: 100;
+    font-size: 1.2rem;
+    line-height: 1.2rem;
+
+    position: absolute;
+    left: 0;
+    bottom: -1.6rem;
+  }
+
+  input:disabled,
+  &__disabled {
+    cursor: not-allowed;
+    color: var(--text-color);
+  }
+}
+
+.margin-bottom {
+  margin-bottom: 2rem;
+}
+
+.margin-top {
+  margin-top: 2rem;
+}

+ 0 - 13
src/index.css

@@ -1,13 +0,0 @@
-body {
-  margin: 0;
-  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
-    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
-    sans-serif;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-}
-
-code {
-  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
-    monospace;
-}

+ 9 - 6
src/index.js

@@ -1,17 +1,20 @@
 import React from 'react';
 import ReactDOM from 'react-dom';
+import { BrowserRouter } from 'react-router-dom';
 import { Provider } from 'react-redux';
-import './index.css';
-import App from './App';
+import App from './routes/app';
 import reportWebVitals from './reportWebVitals';
 
-import store from './store'
+import store from './store';
+import './styles/index.scss';
 
 ReactDOM.render(
   <React.StrictMode>
-    <Provider store={store}>
-    <App />
-    </Provider>
+    <BrowserRouter>
+      <Provider store={store}>
+        <App />
+      </Provider>
+    </BrowserRouter>
   </React.StrictMode>,
   document.getElementById('root')
 );

Різницю між файлами не показано, бо вона завелика
+ 0 - 1
src/logo.svg


+ 24 - 0
src/routes/app/index.js

@@ -0,0 +1,24 @@
+import { Suspense } from 'react';
+import { Switch, Route } from 'react-router-dom';
+
+import { ROUTES } from '../index';
+
+const App = () => {
+  return (
+    <Switch>
+      <Suspense fallback={<div>Loading...</div>}>
+      {ROUTES.map((el) => (
+        <Route
+          key={el.id}
+          exact={el.exact}
+          path={el.path}
+          // component={el.component}
+          render={(props) => <el.component {...props} /> }
+        />
+      ))}
+      </Suspense>
+    </Switch>
+  )
+}
+
+export default App;

+ 9 - 0
src/routes/auth/auth.module.scss

@@ -0,0 +1,9 @@
+.container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.form {
+  width: 300px;
+}

+ 58 - 0
src/routes/auth/index.jsx

@@ -0,0 +1,58 @@
+import hookForm from 'hook-easy-form';
+import { useDispatch } from 'react-redux';
+
+import { runAuth } from 'store/auth/actions';
+import Input from 'components/inputs/text-input/text-input';
+
+import classes from './auth.module.scss';
+
+const form = [
+  {
+    name: 'email',
+    value: '',
+    required: true,
+    options: {
+      type: 'email',
+      label: 'Your email'
+    }
+  },
+  {
+    name: 'password',
+    value: '',
+    required: true,
+    options: {
+      type: 'password',
+      label: 'Your password'
+    },
+    validate: {
+      maxLength: (v) => (v.trim().length < 6 ? 'Invalid' : ''),
+    },
+  },
+]
+
+const Auth = () => {
+  const dispatch = useDispatch();
+
+  // const [values, setValues] = useState({ email: '', password: '' })
+  const { formArray, updateEvent, valid, disabled, submitEvent } = hookForm({ initialForm: form })
+
+  const submit = submitEvent((v) => dispatch(runAuth(v)));
+
+  // console.log('s', formArray);  
+  return (
+    <form onSubmit={submit} className={classes.container}>
+      {formArray.map(el => (
+        <div className={classes.form} key={el.name}>
+          <Input
+            name={el.name}
+            label={el.options.label}
+            type={el.options.type}
+            onChange={updateEvent} />
+        </div>
+      ))}
+      <button type="submit" disabled={disabled || !valid}>Submit</button>
+    </form>
+  )
+}
+
+export default Auth;

+ 18 - 0
src/routes/index.js

@@ -0,0 +1,18 @@
+import { lazy } from 'react';
+
+export const ROUTES = [
+  {
+    id: 0,
+    exact: true,
+    private: true,
+    path: '/',
+    component: lazy(() => import('./main')),
+  },
+  {
+    id: 1,
+    exact: true,
+    private: false,
+    path: '/auth',
+    component: lazy(() => import('./auth')),
+  },
+]

+ 5 - 0
src/routes/main/index.js

@@ -0,0 +1,5 @@
+const Main = () => {
+  return (<div>MAIN PAGE</div>)
+}
+
+export default Main;

+ 16 - 0
src/store/auth/actions.js

@@ -0,0 +1,16 @@
+import * as types from './types';
+
+export const runAuth = (payload) => ({
+  type: types.RUN_AUTH,
+  payload
+})
+
+export const runAuthSubmit = (payload) => ({
+  type: types.RUN_AUTH_SUCCESS,
+  payload
+})
+
+export const runAuthFail = (payload) => ({
+  type: types.RUN_AUTH_FAIL,
+  payload
+})

+ 23 - 0
src/store/auth/reducer.js

@@ -0,0 +1,23 @@
+import * as types from './types';
+
+const initialState = {
+  auth: null
+}
+
+export const authReducer = (state = initialState, action) => {
+  switch(action.type) {
+    case types.RUN_AUTH: {
+      return { ...state }
+    }
+    case types.RUN_AUTH_SUCCESS: {
+      return { ...state }
+    }
+    case types.RUN_AUTH_FAIL: {
+      return { ...state }
+    }
+
+    default: {
+      return state;
+    }
+  }
+}

+ 15 - 0
src/store/auth/saga.js

@@ -0,0 +1,15 @@
+import { call, put, takeLatest } from 'redux-saga/effects';
+
+import * as types from './types';
+import * as actions from './actions'
+
+function* runAuth({ payload }) {
+  const credentials = yield window.btoa(`${payload.email}:${payload.password}`)
+
+  console.log('credentials', credentials);
+}
+
+export default function* auth() {
+  // yield takeEvery(types.GET_FAKE_DATA, getFilms);
+  yield takeLatest(types.RUN_AUTH, runAuth);
+}

+ 3 - 0
src/store/auth/types.js

@@ -0,0 +1,3 @@
+export const RUN_AUTH = 'RUN_AUTH';
+export const RUN_AUTH_SUCCESS = 'RUN_AUTH_SUCCESS';
+export const RUN_AUTH_FAIL = 'RUN_AUTH_FAIL';

+ 2 - 0
src/store/root-reducer.js

@@ -2,10 +2,12 @@ import { combineReducers } from 'redux';
 
 import { fakeReducer } from './fake/reducer';
 import { countReducer } from './counter/reducer';
+import { authReducer } from './auth/reducer'; 
 
 const reducers = combineReducers({
   fake: fakeReducer,
   counter: countReducer,
+  auth: authReducer,
 });
 
 export const rootReducer = (state, action) => {

+ 2 - 0
src/store/saga.js

@@ -1,7 +1,9 @@
 import { fork } from 'redux-saga/effects';
 
 import films from './fake/saga';
+import auth from './auth/saga';
 
 export default function* rootSaga() {
   yield fork(films)
+  yield fork(auth)
 }

+ 451 - 0
src/styles/_reset.scss

@@ -0,0 +1,451 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+   v2.0-modified | 20110126
+   License: none (public domain)
+*/
+
+html,
+body,
+div,
+span,
+applet,
+object,
+iframe,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+blockquote,
+pre,
+a,
+abbr,
+acronym,
+address,
+big,
+cite,
+code,
+del,
+dfn,
+em,
+img,
+ins,
+kbd,
+q,
+s,
+samp,
+small,
+strike,
+strong,
+sub,
+sup,
+tt,
+var,
+b,
+u,
+i,
+center,
+dl,
+dt,
+dd,
+ol,
+ul,
+li,
+fieldset,
+form,
+label,
+legend,
+table,
+caption,
+tbody,
+tfoot,
+thead,
+tr,
+th,
+td,
+article,
+aside,
+canvas,
+details,
+embed,
+figure,
+figcaption,
+footer,
+header,
+hgroup,
+menu,
+nav,
+output,
+ruby,
+section,
+summary,
+time,
+mark,
+audio,
+video {
+	margin: 0;
+	padding: 0;
+	border: 0;
+	font-size: 100%;
+	font: inherit;
+	vertical-align: baseline;
+}
+
+/* make sure to set some focus styles for accessibility */
+:focus {
+	outline: 0;
+}
+
+/* HTML5 display-role reset for older browsers */
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+menu,
+nav,
+section {
+	display: block;
+}
+
+body {
+	line-height: 1;
+  font-family: 'Martel Sans', sans-serif;
+}
+
+ol,
+ul {
+	list-style: none;
+}
+
+blockquote,
+q {
+	quotes: none;
+}
+
+blockquote:before,
+blockquote:after,
+q:before,
+q:after {
+	content: '';
+	content: none;
+}
+
+table {
+	border-collapse: collapse;
+	border-spacing: 0;
+}
+
+input[type='search']::-webkit-search-cancel-button,
+input[type='search']::-webkit-search-decoration,
+input[type='search']::-webkit-search-results-button,
+input[type='search']::-webkit-search-results-decoration {
+	-webkit-appearance: none;
+	-moz-appearance: none;
+}
+
+input[type='search'] {
+	-webkit-appearance: none;
+	-moz-appearance: none;
+	-webkit-box-sizing: content-box;
+	-moz-box-sizing: content-box;
+	box-sizing: content-box;
+}
+
+textarea {
+	overflow: auto;
+	vertical-align: top;
+	resize: vertical;
+}
+
+/**
+ * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
+ */
+
+audio,
+canvas,
+video {
+	display: inline-block;
+	*display: inline;
+	*zoom: 1;
+	max-width: 100%;
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+	display: none;
+	height: 0;
+}
+
+/**
+ * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
+ * Known issue: no IE 6 support.
+ */
+
+[hidden] {
+	display: none;
+}
+
+/**
+ * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using
+ *    `em` units.
+ * 2. Prevent iOS text size adjust after orientation change, without disabling
+ *    user zoom.
+ */
+
+html {
+	font-size: 100%; /* 1 */
+	-webkit-text-size-adjust: 100%; /* 2 */
+	-ms-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Address `outline` inconsistency between Chrome and other browsers.
+ */
+
+a:focus {
+	outline: thin dotted;
+}
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+
+a:active,
+a:hover {
+	outline: 0;
+}
+
+/**
+ * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3.
+ * 2. Improve image quality when scaled in IE 7.
+ */
+
+img {
+	border: 0; /* 1 */
+	-ms-interpolation-mode: bicubic; /* 2 */
+}
+
+/**
+ * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
+ */
+
+figure {
+	margin: 0;
+}
+
+/**
+ * Correct margin displayed oddly in IE 6/7.
+ */
+
+form {
+	margin: 0;
+}
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+	border: 1px solid #c0c0c0;
+	margin: 0 2px;
+	padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct color not being inherited in IE 6/7/8/9.
+ * 2. Correct text not wrapping in Firefox 3.
+ * 3. Correct alignment displayed oddly in IE 6/7.
+ */
+
+legend {
+	border: 0; /* 1 */
+	padding: 0;
+	white-space: normal; /* 2 */
+	*margin-left: -7px; /* 3 */
+}
+
+/**
+ * 1. Correct font size not being inherited in all browsers.
+ * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5,
+ *    and Chrome.
+ * 3. Improve appearance and consistency in all browsers.
+ */
+
+button,
+input,
+select,
+textarea {
+	font-size: 100%; /* 1 */
+	margin: 0; /* 2 */
+	vertical-align: baseline; /* 3 */
+	*vertical-align: middle; /* 3 */
+}
+
+/**
+ * Address Firefox 3+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+button,
+input {
+	line-height: normal;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
+ * Correct `select` style inheritance in Firefox 4+ and Opera.
+ */
+
+button,
+select {
+	text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ *    and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ *    `input` and others.
+ * 4. Remove inner spacing in IE 7 without affecting normal text inputs.
+ *    Known issue: inner spacing remains in IE 6.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+	-webkit-appearance: button; /* 2 */
+	cursor: pointer; /* 3 */
+	*overflow: visible; /* 4 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+	cursor: default;
+}
+
+/**
+ * 1. Address box sizing set to content-box in IE 8/9.
+ * 2. Remove excess padding in IE 8/9.
+ * 3. Remove excess padding in IE 7.
+ *    Known issue: excess padding remains in IE 6.
+ */
+
+input[type='checkbox'],
+input[type='radio'] {
+	box-sizing: border-box; /* 1 */
+	padding: 0; /* 2 */
+	*height: 13px; /* 3 */
+	*width: 13px; /* 3 */
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
+ *    (include `-moz` to future-proof).
+ */
+
+input[type='search'] {
+	-webkit-appearance: textfield; /* 1 */
+	-moz-box-sizing: content-box;
+	-webkit-box-sizing: content-box; /* 2 */
+	box-sizing: content-box;
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari 5 and Chrome
+ * on OS X.
+ */
+
+input[type='search']::-webkit-search-cancel-button,
+input[type='search']::-webkit-search-decoration {
+	-webkit-appearance: none;
+}
+
+/**
+ * Remove inner padding and border in Firefox 3+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+	border: 0;
+	padding: 0;
+}
+
+/**
+ * 1. Remove default vertical scrollbar in IE 6/7/8/9.
+ * 2. Improve readability and alignment in all browsers.
+ */
+
+textarea {
+	overflow: auto; /* 1 */
+	vertical-align: top; /* 2 */
+}
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+	border-collapse: collapse;
+	border-spacing: 0;
+}
+
+html,
+button,
+input,
+select,
+textarea {
+	color: #222;
+}
+
+::-moz-selection {
+	background: #b3d4fc;
+	text-shadow: none;
+}
+
+::selection {
+	background: #b3d4fc;
+	text-shadow: none;
+}
+
+img {
+	vertical-align: middle;
+}
+
+fieldset {
+	border: 0;
+	margin: 0;
+	padding: 0;
+}
+
+textarea {
+	resize: vertical;
+}
+
+label {
+  display: inline-block;
+}
+
+.chromeframe {
+	margin: 0.2em 0;
+	background: #ccc;
+	color: var(--base-black);
+	padding: 0.2em 0;
+}

+ 26 - 0
src/styles/index.scss

@@ -0,0 +1,26 @@
+@import url('https://fonts.googleapis.com/css2?family=Ubuntu:ital,wght@0,300;0,400;0,500;0,700;1,300;1,400;1,500;1,700&display=swap');
+// font-family: 'Ubuntu', sans-serif;
+
+
+:root {
+	--base-white: #fff;
+	--base-black: #000;
+
+	--button-green: #4bc7b4;
+	--button-green-hover: #35aa98;
+	--button-blue: #528ef9;
+	--button-blue-hover: #216ef7;
+	--button-red: #fb7373;
+	--button-red-hover: #fa4141;
+
+	--primary: #1890ff;
+
+	--text-color: #2C3831;
+	--medium-gray: #d8dde1;
+}
+
+body {
+	font-family: 'Ubuntu', sans-serif;
+}
+
+@import './reset.scss';