diff --git a/package-lock.json b/package-lock.json index 39ab1b1..77a2a88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -243,6 +243,15 @@ "csstype": "^2.2.0" } }, + "@types/react-avatar-editor": { + "version": "10.3.4", + "resolved": "https://registry.npmjs.org/@types/react-avatar-editor/-/react-avatar-editor-10.3.4.tgz", + "integrity": "sha512-yulle6pZw+7jCxq156WbIqT0qwuLtzxt53fCIm1op0IFUotChAYYSZmKQR2+4jjhToHrX53nR83Slvr8H6ar5Q==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-dom": { "version": "16.9.0", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.0.tgz", @@ -416,6 +425,12 @@ } } }, + "@types/zxcvbn": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@types/zxcvbn/-/zxcvbn-4.4.0.tgz", + "integrity": "sha512-GQLOT+SN20a+AI51y3fAimhyTF4Y0RG+YP3gf91OibIZ7CJmPFgoZi+ZR5a+vRbS01LbQosITWum4ATmJ1Z6Pg==", + "dev": true + }, "@webassemblyjs/ast": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", @@ -6110,6 +6125,14 @@ "prop-types": "^15.6.2" } }, + "react-avatar-editor": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/react-avatar-editor/-/react-avatar-editor-11.0.7.tgz", + "integrity": "sha512-GbNYBd1/L1QyuU9VRvOW0hSkW1R0XSneOWZFgqI5phQf6dX+dF/G3/AjiJ0hv3JWh2irMQ7DL0oYDKzwtTnNBQ==", + "requires": { + "prop-types": "^15.5.8" + } + }, "react-dom": { "version": "16.9.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.9.0.tgz", @@ -8786,6 +8809,11 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true + }, + "zxcvbn": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/zxcvbn/-/zxcvbn-4.4.2.tgz", + "integrity": "sha1-KOwXzwl0PtyrBW3dixsGJizHPDA=" } } } diff --git a/package.json b/package.json index 153aa57..d73ae13 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,9 @@ "private": true, "scripts": { "start": "webpack-dev-server --config webpack.config.ts", - "build": "webpack --config webpack.config.ts", + "build": "npm run build:dev", + "build:dev": "webpack --config webpack.config.ts", + "build:prod": "webpack --mode production --config webpack.config.ts", "deploy:batch:dev": "az storage blob upload-batch -s ./dist/ -d $web --account-name flexordev", "deploy:batch:prod": "az storage blob upload-batch -s ./dist/ -d $web --account-name flexor", "deploy:config:dev": "az storage blob upload -f ./config/dev.json -c flexordev -n config.json", @@ -19,6 +21,7 @@ "@types/lodash": "^4.14.138", "@types/mini-css-extract-plugin": "^0.8.0", "@types/react": "^16.9.2", + "@types/react-avatar-editor": "^10.3.4", "@types/react-dom": "^16.9.0", "@types/react-redux": "^7.1.2", "@types/react-router-dom": "^4.3.5", @@ -26,6 +29,7 @@ "@types/uuid": "^3.4.5", "@types/webpack": "^4.39.1", "@types/webpack-dev-server": "^3.1.7", + "@types/zxcvbn": "^4.4.0", "bulma": "^0.7.5", "css-loader": "^3.2.0", "html-webpack-plugin": "^3.2.0", @@ -51,6 +55,7 @@ "lodash": "^4.17.15", "normalizr": "^3.4.1", "react": "^16.9.0", + "react-avatar-editor": "^11.0.7", "react-dom": "^16.9.0", "react-redux": "^7.1.1", "react-router-dom": "^5.0.1", @@ -58,6 +63,7 @@ "redux-logger": "^3.0.6", "redux-thunk": "^2.3.0", "reselect": "^4.0.0", - "uuid": "^3.3.3" + "uuid": "^3.3.3", + "zxcvbn": "^4.4.2" } } diff --git a/src/actions/registration.ts b/src/actions/registration.ts index 8086cd5..7d108d0 100644 --- a/src/actions/registration.ts +++ b/src/actions/registration.ts @@ -1,13 +1,62 @@ import { Action } from 'redux' +import { setFieldNotification } from 'src/actions/forms' +import { startRequest, finishRequest } from 'src/actions/requests' +import { fetchGroupAvailability } from 'src/api/registration' +import { AppThunkAction } from 'src/types' -export interface SetPageAction extends Action { - type: 'REGISTRATION_SET_PAGE' +const FETCH_GROUP_AVAILABILITY_ID = 'FETCH_GROUP_AVAILABILITY' +const FETCH_USER_AVAILABILITY_ID = 'FETCH_USER_AVAILABILITY' + +export interface SetStepAction extends Action { + type: 'REGISTRATION_SET_STEP' payload: number } -export type RegistrationActions = SetPageAction +export type RegistrationActions = SetStepAction -export const setPage = (page: number): SetPageAction => ({ - type: 'REGISTRATION_SET_PAGE', - payload: page, +export const setStep = (step: number): SetStepAction => ({ + type: 'REGISTRATION_SET_STEP', + payload: step, }) + +export const checkGroupAvailability = (name: string): AppThunkAction => { + return async dispatch => { + dispatch(startRequest(FETCH_GROUP_AVAILABILITY_ID)) + + try { + const response = await fetchGroupAvailability(name) + + if (response.available) { + dispatch(setFieldNotification('group-name', 'success', `${response.id} is available`)) + } else { + dispatch(setFieldNotification('group-name', 'error', `${response.id} isn't available`)) + } + + dispatch(finishRequest(FETCH_GROUP_AVAILABILITY_ID, true)) + } catch (err) { + dispatch(finishRequest(FETCH_GROUP_AVAILABILITY_ID, false)) + throw err + } + } +} + +export const checkUserAvailability = (name: string): AppThunkAction => { + return async dispatch => { + dispatch(startRequest(FETCH_USER_AVAILABILITY_ID)) + + try { + const response = await fetchGroupAvailability(name) + + if (response.available) { + dispatch(setFieldNotification('user-id', 'success', `${response.id} is available`)) + } else { + dispatch(setFieldNotification('user-id', 'error', `${response.id} isn't available`)) + } + + dispatch(finishRequest(FETCH_USER_AVAILABILITY_ID, true)) + } catch (err) { + dispatch(finishRequest(FETCH_USER_AVAILABILITY_ID, false)) + throw err + } + } +} diff --git a/src/api/registration.ts b/src/api/registration.ts new file mode 100644 index 0000000..863f4de --- /dev/null +++ b/src/api/registration.ts @@ -0,0 +1,26 @@ +import { fetch } from './fetch' + +interface AvailabilityResponse { + id: string + available: boolean +} + +export async function fetchGroupAvailability(name: string) { + return await fetch({ + path: '/api/group/available', + method: 'post', + body: { + name, + }, + }) +} + +export async function fetchUserAvailability(name: string) { + return await fetch({ + path: '/api/user/available', + method: 'post', + body: { + name, + }, + }) +} diff --git a/src/components/app/app.scss b/src/components/app/app.scss index d442d44..507f38f 100644 --- a/src/components/app/app.scss +++ b/src/components/app/app.scss @@ -20,8 +20,8 @@ $title-weight: 400; @import "../../../node_modules/bulma/sass/base/_all.sass"; @import "../../../node_modules/bulma/sass/form/_all.sass"; @import "../../../node_modules/bulma/sass/grid/columns.sass"; -// @import "../../../node_modules/bulma/sass/grid/tiles.sass"; @import "../../../node_modules/bulma/sass/elements/button.sass"; +@import "../../../node_modules/bulma/sass/elements/icon.sass"; @import "../../../node_modules/bulma/sass/elements/notification.sass"; @import "../../../node_modules/bulma/sass/elements/other.sass"; @import "../../../node_modules/bulma/sass/elements/title.sass"; @@ -48,6 +48,10 @@ div.main-content { div#navigation { flex-grow: 1; + + div { + margin: 1rem 0px; + } } footer { diff --git a/src/components/app/app.tsx b/src/components/app/app.tsx index 49033c0..e93eb56 100644 --- a/src/components/app/app.tsx +++ b/src/components/app/app.tsx @@ -1,6 +1,8 @@ import React, { FC } from 'react' import { BrowserRouter as Router, Route, Link } from 'react-router-dom' +import Footer from '../footer' +import NavigationMenu from '../navigation-menu' import NotificationContainer from '../notification-container' import Spinner from '../spinner' import UserInfo from '../user-info' @@ -21,10 +23,8 @@ interface Props { fetching: boolean } -const Divider: FC = () => <>  ⚬   - const App: FC = ({ collapsed, fetching }) => { - const mainMenuWidth = 300 + const mainMenuWidth = 250 const mainColumnLeftMargin = collapsed ? 0 : mainMenuWidth return ( @@ -36,27 +36,11 @@ const App: FC = ({ collapsed, fetching }) => {
- + {fetching && } - - -
-
- Home - - Developers - - About - -

© 2019 Flexor.cc

-
-
+