From 61af12d17c640e3435a16739fe10c7f00f37ea28 Mon Sep 17 00:00:00 2001 From: Thai <42761684+thaigillespie@users.noreply.github.com> Date: Thu, 27 Aug 2020 11:24:08 -0700 Subject: [PATCH 01/29] Made changes so auth on openapi schema works. (#143) * Made changes so auth on openapi schema works. Now requests that require BE to check token will be sent with Bearer JWT from FE as a token. * Made changes based on review on PR #143 * Okay actual changes reflecting review on PR #143. --- package.json | 2 +- src/pages/App/index.js | 10 +- src/pages/EventSignInPage/index.js | 5 + src/services/ApiConfigStore.ts | 2 +- src/services/ApiEvents.ts | 80 +++++++++- src/services/api/.openapi-generator/FILES | 36 +++-- src/services/api/apis/EventApi.ts | 45 ++++++ src/services/api/apis/UserApi.ts | 137 ++++++++++++++++++ src/services/api/apis/index.ts | 1 + .../api/models/AppUserInductionClass.ts | 84 +++++++++++ .../api/models/AppUserProfileResponse.ts | 107 ++++++++++++++ .../api/models/AppUserRolesResponse.ts | 70 +++++++++ src/services/api/models/index.ts | 3 + 13 files changed, 561 insertions(+), 21 deletions(-) create mode 100644 src/services/api/apis/UserApi.ts create mode 100644 src/services/api/models/AppUserInductionClass.ts create mode 100644 src/services/api/models/AppUserProfileResponse.ts create mode 100644 src/services/api/models/AppUserRolesResponse.ts diff --git a/package.json b/package.json index 99474d8f..d336a394 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "storybook": "start-storybook -p 6006 -s public", "build-storybook": "build-storybook -s public", "precodegen": "rimraf src/api/*", - "codegen": "npx openapi-generator generate -i http://dev.api.hknucsd.com/api/docs/json -g typescript-fetch --additional-properties=typescriptThreePlus=true -o src/api/" + "codegen": "npx openapi-generator generate -i http://dev.api.hknucsd.com/api/docs/json -g typescript-fetch --additional-properties=typescriptThreePlus=true -o src/services/api" }, "eslintConfig": { "extends": "react-app" diff --git a/src/pages/App/index.js b/src/pages/App/index.js index 348d2862..fcf00e8f 100644 --- a/src/pages/App/index.js +++ b/src/pages/App/index.js @@ -25,9 +25,11 @@ import { InducteeRoutingPermission, OfficerRoutingPermission, } from '@HOCs/RoutingPermissions'; +import ApiConfigStore from '@Services/ApiConfigStore'; const INITIAL_STATES = { userClaims: null, + userToken: null, isLoading: true, }; @@ -42,18 +44,20 @@ class App extends React.Component { firebase.auth().onAuthStateChanged(async user => { if (user) { const tokenResult = await user.getIdTokenResult(); - const { claims } = tokenResult; + const { claims, token } = tokenResult; this.setState({ userClaims: { userId: claims.user_id, userRoles: getRolesFromClaims(claims), }, + userToken: token, isLoading: false, }); } else { this.setState({ userClaims: null, + userToken: null, isLoading: false, }); } @@ -61,6 +65,10 @@ class App extends React.Component { } setClaims = claims => { + const { userToken } = this.state; + + ApiConfigStore.setToken(userToken); + this.setState({ userClaims: { userId: claims.user_id, diff --git a/src/pages/EventSignInPage/index.js b/src/pages/EventSignInPage/index.js index 44cbaec3..3c4d6b59 100644 --- a/src/pages/EventSignInPage/index.js +++ b/src/pages/EventSignInPage/index.js @@ -9,6 +9,7 @@ import styles from './styles'; import HKN_TRIDENT_LOGO from '@Images/hkn-trident.png'; import { Loading } from '@SharedComponents'; import { getEventById } from '@Services/events'; +import { signInToEvent } from '@Services/ApiEvents'; class EventSignInPage extends React.Component { constructor(props) { @@ -39,8 +40,12 @@ class EventSignInPage extends React.Component { } handleSubmit = (values, setSubmitting) => { + const { eventId } = this.state; + console.log(values); + signInToEvent(eventId, values); + setSubmitting(false); }; diff --git a/src/services/ApiConfigStore.ts b/src/services/ApiConfigStore.ts index 65a82af4..3863b601 100644 --- a/src/services/ApiConfigStore.ts +++ b/src/services/ApiConfigStore.ts @@ -19,7 +19,7 @@ class ApiConfigStoreClass { // this is gonna be called on login setToken(token: string) { - this.configParams.apiKey = token; + this.configParams.accessToken = token; this.config = new Configuration(this.configParams); } } diff --git a/src/services/ApiEvents.ts b/src/services/ApiEvents.ts index eff75291..acbd0ff8 100644 --- a/src/services/ApiEvents.ts +++ b/src/services/ApiEvents.ts @@ -1,5 +1,20 @@ -import { EventApi, EventControllerGetEventRequest } from './api/apis/EventApi'; -import { MultipleEventResponse, EventResponse } from './api/models'; +import { + EventApi, + EventControllerGetEventRequest, + EventControllerCreateEventRequest, + EventControllerDeleteEventRequest, + EventControllerUpdateEventRequest, + EventControllerSignInToEventRequest, + EventControllerRsvpForEventRequest, +} from './api/apis/EventApi'; +import { + MultipleEventResponse, + EventResponse, + EventRequest, + AppUserEventRequest, + AttendanceResponse, + RSVPResponse, +} from './api/models'; import ApiConfigStore from './ApiConfigStore'; import { Configuration } from './api/runtime'; @@ -17,3 +32,64 @@ export async function getEventById(eventID: number): Promise { }; return eventApi.eventControllerGetEvent(request); } + +export async function createEvent( + eventRequest: EventRequest +): Promise { + const apiConfig: Configuration = ApiConfigStore.getApiConfig(); + const eventApi: EventApi = new EventApi(apiConfig); + const request: EventControllerCreateEventRequest = { + eventRequest, + }; + + return eventApi.eventControllerCreateEvent(request); +} + +export async function updateEvent(eventID: number, eventRequest: EventRequest) { + const apiConfig: Configuration = ApiConfigStore.getApiConfig(); + const eventApi: EventApi = new EventApi(apiConfig); + const request: EventControllerUpdateEventRequest = { + eventID, + eventRequest, + }; + + return eventApi.eventControllerUpdateEvent(request); +} + +export async function deleteEvent(eventID: number): Promise { + const apiConfig: Configuration = ApiConfigStore.getApiConfig(); + const eventApi: EventApi = new EventApi(apiConfig); + const request: EventControllerDeleteEventRequest = { + eventID, + }; + + return eventApi.eventControllerDeleteEvent(request); +} + +export async function signInToEvent( + eventID: number, + appUserEventRequest: AppUserEventRequest +): Promise { + const apiConfig: Configuration = ApiConfigStore.getApiConfig(); + const eventApi: EventApi = new EventApi(apiConfig); + const request: EventControllerSignInToEventRequest = { + eventID, + appUserEventRequest, + }; + + return eventApi.eventControllerSignInToEvent(request); +} + +export async function rsvpToEvent( + eventID: number, + appUserEventRequest: AppUserEventRequest +): Promise { + const apiConfig: Configuration = ApiConfigStore.getApiConfig(); + const eventApi: EventApi = new EventApi(apiConfig); + const request: EventControllerRsvpForEventRequest = { + eventID, + appUserEventRequest, + }; + + return eventApi.eventControllerRsvpForEvent(request); +} diff --git a/src/services/api/.openapi-generator/FILES b/src/services/api/.openapi-generator/FILES index f962ca06..1f453ddc 100644 --- a/src/services/api/.openapi-generator/FILES +++ b/src/services/api/.openapi-generator/FILES @@ -1,16 +1,20 @@ -apis/EventApi.ts -apis/index.ts -index.ts -models/AppUserEventRequest.ts -models/AppUserEventResponse.ts -models/AppUserPKPayload.ts -models/AttendanceResponse.ts -models/BaseEventPayload.ts -models/EventAttendanceResponse.ts -models/EventRSVPResponse.ts -models/EventRequest.ts -models/EventResponse.ts -models/MultipleEventResponse.ts -models/RSVPResponse.ts -models/index.ts -runtime.ts +apis\EventApi.ts +apis\UserApi.ts +apis\index.ts +index.ts +models\AppUserEventRequest.ts +models\AppUserEventResponse.ts +models\AppUserInductionClass.ts +models\AppUserPKPayload.ts +models\AppUserProfileResponse.ts +models\AppUserRolesResponse.ts +models\AttendanceResponse.ts +models\BaseEventPayload.ts +models\EventAttendanceResponse.ts +models\EventRSVPResponse.ts +models\EventRequest.ts +models\EventResponse.ts +models\MultipleEventResponse.ts +models\RSVPResponse.ts +models\index.ts +runtime.ts diff --git a/src/services/api/apis/EventApi.ts b/src/services/api/apis/EventApi.ts index 7f134912..d03dc85e 100644 --- a/src/services/api/apis/EventApi.ts +++ b/src/services/api/apis/EventApi.ts @@ -77,6 +77,15 @@ export class EventApi extends runtime.BaseAPI { headerParameters['Content-Type'] = 'application/json'; + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = + typeof token === 'function' ? token('TokenAuth', []) : token; + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } const response = await this.request({ path: `/api/events/`, method: 'POST', @@ -122,6 +131,15 @@ export class EventApi extends runtime.BaseAPI { const headerParameters: runtime.HTTPHeaders = {}; + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = + typeof token === 'function' ? token('TokenAuth', []) : token; + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } const response = await this.request({ path: `/api/events/{eventID}`.replace( `{${'eventID'}}`, @@ -246,6 +264,15 @@ export class EventApi extends runtime.BaseAPI { headerParameters['Content-Type'] = 'application/json'; + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = + typeof token === 'function' ? token('TokenAuth', []) : token; + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } const response = await this.request({ path: `/api/events/{eventID}/rsvp`.replace( `{${'eventID'}}`, @@ -296,6 +323,15 @@ export class EventApi extends runtime.BaseAPI { headerParameters['Content-Type'] = 'application/json'; + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = + typeof token === 'function' ? token('TokenAuth', []) : token; + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } const response = await this.request({ path: `/api/events/{eventID}/signin`.replace( `{${'eventID'}}`, @@ -346,6 +382,15 @@ export class EventApi extends runtime.BaseAPI { headerParameters['Content-Type'] = 'application/json'; + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = + typeof token === 'function' ? token('TokenAuth', []) : token; + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } const response = await this.request({ path: `/api/events/{eventID}`.replace( `{${'eventID'}}`, diff --git a/src/services/api/apis/UserApi.ts b/src/services/api/apis/UserApi.ts new file mode 100644 index 00000000..8444d877 --- /dev/null +++ b/src/services/api/apis/UserApi.ts @@ -0,0 +1,137 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * HKN API + * HKN API + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import * as runtime from '../runtime'; +import { + AppUserProfileResponse, + AppUserProfileResponseFromJSON, + AppUserProfileResponseToJSON, + AppUserRolesResponse, + AppUserRolesResponseFromJSON, + AppUserRolesResponseToJSON, +} from '../models'; + +export interface UserControllerGetUserProfileRequest { + userID: number; +} + +export interface UserControllerGetUserRoleRequest { + userID: number; +} + +/** + * + */ +export class UserApi extends runtime.BaseAPI { + /** + * Get user profile + */ + async userControllerGetUserProfileRaw( + requestParameters: UserControllerGetUserProfileRequest + ): Promise> { + if ( + requestParameters.userID === null || + requestParameters.userID === undefined + ) { + throw new runtime.RequiredError( + 'userID', + 'Required parameter requestParameters.userID was null or undefined when calling userControllerGetUserProfile.' + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + const response = await this.request({ + path: `/api/users/{userID}`.replace( + `{${'userID'}}`, + encodeURIComponent(String(requestParameters.userID)) + ), + method: 'GET', + headers: headerParameters, + query: queryParameters, + }); + + return new runtime.JSONApiResponse(response, jsonValue => + AppUserProfileResponseFromJSON(jsonValue) + ); + } + + /** + * Get user profile + */ + async userControllerGetUserProfile( + requestParameters: UserControllerGetUserProfileRequest + ): Promise { + const response = await this.userControllerGetUserProfileRaw( + requestParameters + ); + return await response.value(); + } + + /** + * Get user role + */ + async userControllerGetUserRoleRaw( + requestParameters: UserControllerGetUserRoleRequest + ): Promise> { + if ( + requestParameters.userID === null || + requestParameters.userID === undefined + ) { + throw new runtime.RequiredError( + 'userID', + 'Required parameter requestParameters.userID was null or undefined when calling userControllerGetUserRole.' + ); + } + + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = + typeof token === 'function' ? token('TokenAuth', []) : token; + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } + const response = await this.request({ + path: `/api/users/{userID}/roles`.replace( + `{${'userID'}}`, + encodeURIComponent(String(requestParameters.userID)) + ), + method: 'GET', + headers: headerParameters, + query: queryParameters, + }); + + return new runtime.JSONApiResponse(response, jsonValue => + AppUserRolesResponseFromJSON(jsonValue) + ); + } + + /** + * Get user role + */ + async userControllerGetUserRole( + requestParameters: UserControllerGetUserRoleRequest + ): Promise { + const response = await this.userControllerGetUserRoleRaw(requestParameters); + return await response.value(); + } +} diff --git a/src/services/api/apis/index.ts b/src/services/api/apis/index.ts index 614dfc9c..2c5754d1 100644 --- a/src/services/api/apis/index.ts +++ b/src/services/api/apis/index.ts @@ -1 +1,2 @@ export * from './EventApi'; +export * from './UserApi'; diff --git a/src/services/api/models/AppUserInductionClass.ts b/src/services/api/models/AppUserInductionClass.ts new file mode 100644 index 00000000..58b87eb7 --- /dev/null +++ b/src/services/api/models/AppUserInductionClass.ts @@ -0,0 +1,84 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * HKN API + * HKN API + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +/** + * + * @export + * @interface AppUserInductionClass + */ +export interface AppUserInductionClass { + /** + * + * @type {string} + * @memberof AppUserInductionClass + */ + quarter: string; + /** + * + * @type {string} + * @memberof AppUserInductionClass + */ + name: string; + /** + * + * @type {string} + * @memberof AppUserInductionClass + */ + startDate: string; + /** + * + * @type {string} + * @memberof AppUserInductionClass + */ + endDate: string; +} + +export function AppUserInductionClassFromJSON( + json: any +): AppUserInductionClass { + return AppUserInductionClassFromJSONTyped(json, false); +} + +export function AppUserInductionClassFromJSONTyped( + json: any, + ignoreDiscriminator: boolean +): AppUserInductionClass { + if (json === undefined || json === null) { + return json; + } + return { + quarter: json['quarter'], + name: json['name'], + startDate: json['startDate'], + endDate: json['endDate'], + }; +} + +export function AppUserInductionClassToJSON( + value?: AppUserInductionClass | null +): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + quarter: value.quarter, + name: value.name, + startDate: value.startDate, + endDate: value.endDate, + }; +} diff --git a/src/services/api/models/AppUserProfileResponse.ts b/src/services/api/models/AppUserProfileResponse.ts new file mode 100644 index 00000000..b6cb0d7f --- /dev/null +++ b/src/services/api/models/AppUserProfileResponse.ts @@ -0,0 +1,107 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * HKN API + * HKN API + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import { + AppUserInductionClass, + AppUserInductionClassFromJSON, + AppUserInductionClassFromJSONTyped, + AppUserInductionClassToJSON, +} from './'; + +/** + * + * @export + * @interface AppUserProfileResponse + */ +export interface AppUserProfileResponse { + /** + * + * @type {string} + * @memberof AppUserProfileResponse + */ + firstName: string; + /** + * + * @type {string} + * @memberof AppUserProfileResponse + */ + lastName: string; + /** + * + * @type {string} + * @memberof AppUserProfileResponse + */ + email: string; + /** + * + * @type {string} + * @memberof AppUserProfileResponse + */ + major: string; + /** + * + * @type {string} + * @memberof AppUserProfileResponse + */ + graduationYear: string; + /** + * + * @type {AppUserInductionClass} + * @memberof AppUserProfileResponse + */ + inductionClass: AppUserInductionClass; +} + +export function AppUserProfileResponseFromJSON( + json: any +): AppUserProfileResponse { + return AppUserProfileResponseFromJSONTyped(json, false); +} + +export function AppUserProfileResponseFromJSONTyped( + json: any, + ignoreDiscriminator: boolean +): AppUserProfileResponse { + if (json === undefined || json === null) { + return json; + } + return { + firstName: json['firstName'], + lastName: json['lastName'], + email: json['email'], + major: json['major'], + graduationYear: json['graduationYear'], + inductionClass: AppUserInductionClassFromJSON(json['inductionClass']), + }; +} + +export function AppUserProfileResponseToJSON( + value?: AppUserProfileResponse | null +): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + firstName: value.firstName, + lastName: value.lastName, + email: value.email, + major: value.major, + graduationYear: value.graduationYear, + inductionClass: AppUserInductionClassToJSON(value.inductionClass), + }; +} diff --git a/src/services/api/models/AppUserRolesResponse.ts b/src/services/api/models/AppUserRolesResponse.ts new file mode 100644 index 00000000..c2a10cb9 --- /dev/null +++ b/src/services/api/models/AppUserRolesResponse.ts @@ -0,0 +1,70 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * HKN API + * HKN API + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +/** + * + * @export + * @interface AppUserRolesResponse + */ +export interface AppUserRolesResponse { + /** + * + * @type {string} + * @memberof AppUserRolesResponse + */ + role: AppUserRolesResponseRoleEnum; +} + +export function AppUserRolesResponseFromJSON(json: any): AppUserRolesResponse { + return AppUserRolesResponseFromJSONTyped(json, false); +} + +export function AppUserRolesResponseFromJSONTyped( + json: any, + ignoreDiscriminator: boolean +): AppUserRolesResponse { + if (json === undefined || json === null) { + return json; + } + return { + role: json['role'], + }; +} + +export function AppUserRolesResponseToJSON( + value?: AppUserRolesResponse | null +): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + role: value.role, + }; +} + +/** + * @export + * @enum {string} + */ +export enum AppUserRolesResponseRoleEnum { + Admin = 'admin', + Officer = 'officer', + Member = 'member', + Inductee = 'inductee', + Guest = 'guest', +} diff --git a/src/services/api/models/index.ts b/src/services/api/models/index.ts index a05471b3..e5581367 100644 --- a/src/services/api/models/index.ts +++ b/src/services/api/models/index.ts @@ -1,6 +1,9 @@ export * from './AppUserEventRequest'; export * from './AppUserEventResponse'; +export * from './AppUserInductionClass'; export * from './AppUserPKPayload'; +export * from './AppUserProfileResponse'; +export * from './AppUserRolesResponse'; export * from './AttendanceResponse'; export * from './BaseEventPayload'; export * from './EventAttendanceResponse'; From 0b2d466dfb6a22187a6f56a467176ab4d2a3b322 Mon Sep 17 00:00:00 2001 From: Godwin Pang Date: Sun, 30 Aug 2020 20:35:34 -0700 Subject: [PATCH 02/29] SignUp Page small screen fix. (#144) * Add autochanged tsconfig. * Make signup page responsive. * Drop minWidth on dropdowns to prevent overflow. --- .../dropdowns/MajorDropdownField/index.js | 6 +- .../dropdowns/MajorDropdownField/styles.js | 7 -- .../dropdowns/YearDropdownField/index.js | 8 +-- .../dropdowns/YearDropdownField/styles.js | 7 -- .../SignUpPage/components/SignUpForm/index.js | 70 ++++++++++--------- .../components/SignUpForm/styles.js | 4 +- src/pages/SignUpPage/index.js | 36 ++++++++-- src/pages/SignUpPage/styles.js | 32 ++++----- tsconfig.json | 54 +++++++------- 9 files changed, 112 insertions(+), 112 deletions(-) delete mode 100644 src/components/dropdowns/MajorDropdownField/styles.js delete mode 100644 src/components/dropdowns/YearDropdownField/styles.js diff --git a/src/components/dropdowns/MajorDropdownField/index.js b/src/components/dropdowns/MajorDropdownField/index.js index fc7e21ba..23eeb326 100644 --- a/src/components/dropdowns/MajorDropdownField/index.js +++ b/src/components/dropdowns/MajorDropdownField/index.js @@ -1,11 +1,8 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import { withStyles } from '@material-ui/core/styles'; import GenericDropdownField from '../base'; -import styles from './styles'; - import ELIGIBLE_MAJORS from '@Constants/eligibleMajors'; const createFullMajorTitle = (department, major) => { @@ -37,7 +34,6 @@ const MajorDropdownField = props => { return ( ({ - root: { - minWidth: '273.333px', - }, -}); - -export default styles; diff --git a/src/components/dropdowns/YearDropdownField/index.js b/src/components/dropdowns/YearDropdownField/index.js index 8dce3db3..78c7761a 100644 --- a/src/components/dropdowns/YearDropdownField/index.js +++ b/src/components/dropdowns/YearDropdownField/index.js @@ -1,12 +1,9 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { getYear } from 'date-fns'; -import { withStyles } from '@material-ui/core/styles'; import GenericDropdownField from '../base'; -import styles from './styles'; - const yearDropdownChoices = (minYear, maxYear) => { const yearChoices = []; @@ -18,11 +15,10 @@ const yearDropdownChoices = (minYear, maxYear) => { }; const YearDropdownField = props => { - const { classes, name, label, minYear, maxYear, ...otherProps } = props; + const { name, label, minYear, maxYear, ...otherProps } = props; return ( ({ - root: { - minWidth: '124.667px', - }, -}); - -export default styles; diff --git a/src/pages/SignUpPage/components/SignUpForm/index.js b/src/pages/SignUpPage/components/SignUpForm/index.js index 5af3ebb9..5a05c684 100644 --- a/src/pages/SignUpPage/components/SignUpForm/index.js +++ b/src/pages/SignUpPage/components/SignUpForm/index.js @@ -35,12 +35,13 @@ const SignUpForm = props => { > {({ submitForm, isSubmitting }) => (
- + - + @@ -49,6 +50,7 @@ const SignUpForm = props => { @@ -57,53 +59,53 @@ const SignUpForm = props => { - - - - + + - - - + + + - - - - + + - + - + - + - + + + {format(parseISO(event.startDate), 'PP')} -{' '} + {format(parseISO(event.startDate), 'p')} to{' '} + {format(parseISO(event.endDate), 'p')} + + + + {event.location} + + )} diff --git a/src/pages/EventDetailsPage/components/EventDetails/index.js b/src/pages/EventDetailsPage/components/EventDetails/index.js index 73eec3ed..28d320e5 100644 --- a/src/pages/EventDetailsPage/components/EventDetails/index.js +++ b/src/pages/EventDetailsPage/components/EventDetails/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import { Typography, Card, Button, Grid } from '@material-ui/core'; +import { Typography, Button, Grid } from '@material-ui/core'; import { withStyles } from '@material-ui/core/styles'; import { Link } from 'react-router-dom'; import { format, parseISO } from 'date-fns'; @@ -11,7 +11,7 @@ import Links from './Links'; import styles from './styles'; import { OfficerRenderPermission } from '@HOCs/RenderPermissions'; -import { Tags } from '@SharedComponents'; +import { Tags, Card } from '@SharedComponents'; import * as ROUTES from '@Constants/routes'; function EventDetailsComponent(props) { @@ -40,93 +40,97 @@ function EventDetailsComponent(props) { const eventType = type || 'Event'; return ( -
- - - - - - - {name} - - - + + + + + + + + + {name} + + + - - {OfficerRenderPermission(DeleteEditButtons)({ eventId })} + + {OfficerRenderPermission(DeleteEditButtons)({ eventId })} + - - - - - - - Hosts:{' '} - {hosts.map(host => ( - - {`${host.firstName} ${host.lastName}`} - - ))} - - - - - - - - Location: {location} - - - - - Start Time:{' '} - - {format(parseISO(startDate), 'PPP p')} + + + + + Hosts:{' '} + {hosts.map(host => ( + + {`${host.firstName} ${host.lastName}`} - - + ))} + + - - - End Time:{' '} - - {format(parseISO(endDate), 'PPP p')} + + + + + Location: {location} + + + + + + Start Time:{' '} + + {format(parseISO(startDate), 'PPP p')} + + + + + + + End Time:{' '} + + {format(parseISO(endDate), 'PPP p')} + - + - - - - - {OfficerRenderPermission(Links)({ urls })} - + + + + {OfficerRenderPermission(Links)({ urls })} + - - - Description: {description} - + + + Description: {description} + + - - - - -
+ +
+ + + + +
); } diff --git a/src/pages/EventDetailsPage/components/EventDetails/styles.js b/src/pages/EventDetailsPage/components/EventDetails/styles.js index 5e963799..7dd0b0e0 100644 --- a/src/pages/EventDetailsPage/components/EventDetails/styles.js +++ b/src/pages/EventDetailsPage/components/EventDetails/styles.js @@ -1,13 +1,5 @@ const styles = () => ({ - root: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - }, eventDetailsCard: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', width: '536px', }, firstRow: { diff --git a/src/pages/EventEditPage/event_edit.js b/src/pages/EventEditPage/event_edit.js index 489a2c38..fb7022b7 100644 --- a/src/pages/EventEditPage/event_edit.js +++ b/src/pages/EventEditPage/event_edit.js @@ -1,9 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Card } from '@material-ui/core'; import EventEditForm from './components/EventEditForm'; +import { Card } from '@SharedComponents'; import { getEventById, updateEvent } from '@Services/EventService'; class EventEditPage extends React.Component { diff --git a/src/pages/EventRsvpPage/index.js b/src/pages/EventRsvpPage/index.js index afd5f3f5..3f78d54b 100644 --- a/src/pages/EventRsvpPage/index.js +++ b/src/pages/EventRsvpPage/index.js @@ -1,16 +1,15 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Avatar, Card, Typography, Grid } from '@material-ui/core'; +import { Avatar, Typography, Grid } from '@material-ui/core'; import { withStyles } from '@material-ui/core/styles'; import EventRsvpForm from './components/EventRsvpForm'; import styles from './styles'; import HKN_TRIDENT_LOGO from '@Images/hkn-trident.png'; -import { Loading, PublicPageLayout } from '@SharedComponents'; +import { Loading, Card, PublicPageLayout } from '@SharedComponents'; import { getEventById, rsvpToEvent } from '@Services/EventService'; - class EventRsvpPage extends React.Component { constructor(props) { const { diff --git a/src/pages/EventSignInPage/index.js b/src/pages/EventSignInPage/index.js index 40cbe0b5..7603518e 100644 --- a/src/pages/EventSignInPage/index.js +++ b/src/pages/EventSignInPage/index.js @@ -1,16 +1,15 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Avatar, Card, Typography, Grid } from '@material-ui/core'; +import { Avatar, Typography, Grid } from '@material-ui/core'; import { withStyles } from '@material-ui/core/styles'; import EventSignInForm from './components/EventSignInForm'; import styles from './styles'; import HKN_TRIDENT_LOGO from '@Images/hkn-trident.png'; -import { Loading, PublicPageLayout } from '@SharedComponents'; +import { Loading, Card, PublicPageLayout } from '@SharedComponents'; import { getEventById, signInToEvent } from '@Services/EventService'; - class EventSignInPage extends React.Component { constructor(props) { const { diff --git a/src/pages/InducteePointsPage/PointDetail.js b/src/pages/InducteePointsPage/PointDetail.js index a83ccb3b..8b297166 100644 --- a/src/pages/InducteePointsPage/PointDetail.js +++ b/src/pages/InducteePointsPage/PointDetail.js @@ -3,16 +3,11 @@ import React from 'react'; import { compose } from 'recompose'; import PropTypes from 'prop-types'; -import { - Grid, - Card, - CardHeader, - CardContent, - Typography, -} from '@material-ui/core'; +import { Grid, Typography } from '@material-ui/core'; import { grey } from '@material-ui/core/colors'; import { withStyles } from '@material-ui/core/styles'; +import { Card } from '@SharedComponents'; import { getUserEvent } from '@Services/events'; const styles = theme => ({ @@ -56,23 +51,17 @@ class PointDetail extends React.Component { alignItems='flex-start' > - - - - {eventNames.map(eventName => ( - {eventName} - ))} - + + {eventNames.map(eventName => ( + {eventName} + ))} - - - - {officerSigns.map(officerName => ( - {officerName} - ))} - + + {officerSigns.map(officerName => ( + {officerName} + ))}
diff --git a/src/pages/PointsPage/point_display.js b/src/pages/PointsPage/point_display.js index e4baa1c4..84c5dd2b 100644 --- a/src/pages/PointsPage/point_display.js +++ b/src/pages/PointsPage/point_display.js @@ -1,10 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import { withStyles } from '@material-ui/core/styles'; -import { compose } from 'recompose'; -import { Card, CardContent, Typography, Grid } from '@material-ui/core'; +import { Typography, Grid } from '@material-ui/core'; import { format } from 'date-fns'; +import { Card } from '@SharedComponents'; + const styles = () => ({ card: { minWidth: 200, @@ -59,22 +60,17 @@ class PointDisplay extends React.Component { key={event.event_name} > - - - {event.event_name} - - - {format(event.date, 'PP')} - - - {`Officer: ${event.officer}`} -
- {`Points: ${event.value}`} -
-
+ + {event.event_name} + + + {format(event.date, 'PP')} + + + {`Officer: ${event.officer}`} +
+ {`Points: ${event.value}`} +
); @@ -93,4 +89,4 @@ PointDisplay.propTypes = { points: PropTypes.arrayOf(PropTypes.object).isRequired, }; -export default compose(withStyles(styles))(PointDisplay); +export default withStyles(styles)(PointDisplay); diff --git a/src/pages/ProfileEditPage/index.js b/src/pages/ProfileEditPage/index.js index 7250382d..2da25ca6 100644 --- a/src/pages/ProfileEditPage/index.js +++ b/src/pages/ProfileEditPage/index.js @@ -1,13 +1,13 @@ import React from 'react'; import PropTypes from 'prop-types'; import { withStyles } from '@material-ui/core/styles'; -import { Card, CardContent, Grid } from '@material-ui/core'; +import { Grid } from '@material-ui/core'; import { Formik, Form } from 'formik'; import schema from './schema'; import styles from './styles'; -import { FormLayout } from '@SharedComponents'; +import { FormLayout, Card } from '@SharedComponents'; import { getAccountSection, getPersonalInfoSection, @@ -78,15 +78,13 @@ class ProfileEditPage extends React.Component { - - - + diff --git a/src/pages/ProfilePage/index.js b/src/pages/ProfilePage/index.js index 6c3a231c..992f0e43 100644 --- a/src/pages/ProfilePage/index.js +++ b/src/pages/ProfilePage/index.js @@ -1,11 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import { withStyles } from '@material-ui/core/styles'; -import { Card, CardContent, Grid } from '@material-ui/core'; +import { Grid } from '@material-ui/core'; import styles from './styles'; -import { FormLayout } from '@SharedComponents'; +import { FormLayout, Card } from '@SharedComponents'; import { getAccountSection, getPersonalInfoSection, @@ -66,14 +66,12 @@ class ProfilePage extends React.Component { return ( - - - + ); diff --git a/src/pages/SignUpPage/index.js b/src/pages/SignUpPage/index.js index 13306fb1..a1f41d04 100644 --- a/src/pages/SignUpPage/index.js +++ b/src/pages/SignUpPage/index.js @@ -1,10 +1,11 @@ import React from 'react'; -import { Avatar, Card, Grid, CardContent } from '@material-ui/core'; +import { Avatar, Grid } from '@material-ui/core'; import { withStyles } from '@material-ui/core/styles'; import SignUpForm from './components/SignUpForm'; import styles from './styles'; +import { Card } from '@SharedComponents'; import { PublicPageLayout } from '@SharedComponents/layouts'; import { createUserAccountFromSignup } from '@Services/auth'; import HKN_TRIDENT_LOGO from '@Images/hkn-trident.png'; @@ -33,23 +34,21 @@ class SignUpPage extends React.Component { return ( - - - - - - - - - + + + + - + + + +
); diff --git a/tsconfig.json b/tsconfig.json index 75325e2f..d4fca364 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,27 +1,27 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react", - "baseUrl": "." - }, - "include": [ - "src" - ], - "extends": "./tsconfig.paths.json" -} +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react", + "baseUrl": "." + }, + "include": [ + "src" + ], + "extends": "./tsconfig.paths.json" +} From 0f32248a50e999dc103b19db5f1f974433b477e1 Mon Sep 17 00:00:00 2001 From: Thai <42761684+thaigillespie@users.noreply.github.com> Date: Tue, 8 Sep 2020 14:49:12 -0700 Subject: [PATCH 08/29] Added email verification to signup workflow, integrated auth api from backend. (#153) * Added email verification to signup workflow, integrated auth api from backend. Now frontend just needs to call createNewUser() from src/services/AuthService to start the signup workflow, then finishes it off with email verif. being handled in frontend. * Added try catch for calls to a firebase service in signup's handleSubmit. * Fixed duplicate imports in SignUpPage. --- package.json | 2 +- src/pages/SignUpPage/index.js | 38 ++++++- src/services/AuthService.ts | 14 +++ src/services/api/.openapi-generator/FILES | 50 +++++----- src/services/api/.openapi-generator/VERSION | 2 +- src/services/api/apis/AuthApi.ts | 67 +++++++++++++ src/services/api/apis/UserApi.ts | 9 ++ src/services/api/apis/index.ts | 1 + .../api/models/AppUserProfileResponse.ts | 26 ++++- .../api/models/AppUserSignupRequest.ts | 98 +++++++++++++++++++ src/services/api/models/index.ts | 1 + 11 files changed, 277 insertions(+), 31 deletions(-) create mode 100644 src/services/AuthService.ts create mode 100644 src/services/api/apis/AuthApi.ts create mode 100644 src/services/api/models/AppUserSignupRequest.ts diff --git a/package.json b/package.json index 9bec38a1..7f2a485f 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "storybook": "start-storybook -p 6006 -s public", "build-storybook": "build-storybook -s public", "precodegen": "rimraf src/api/*", - "codegen": "npx openapi-generator generate -i http://dev.api.hknucsd.com/api/docs/json -g typescript-fetch --additional-properties=typescriptThreePlus=true -o src/services/api" + "codegen": "npx openapi-generator generate -i https://dev-api.hknucsd.com/api/docs/json -g typescript-fetch --additional-properties=typescriptThreePlus=true -o src/services/api" }, "eslintConfig": { "extends": "react-app" diff --git a/src/pages/SignUpPage/index.js b/src/pages/SignUpPage/index.js index a1f41d04..ac85e5ee 100644 --- a/src/pages/SignUpPage/index.js +++ b/src/pages/SignUpPage/index.js @@ -7,8 +7,13 @@ import styles from './styles'; import { Card } from '@SharedComponents'; import { PublicPageLayout } from '@SharedComponents/layouts'; -import { createUserAccountFromSignup } from '@Services/auth'; import HKN_TRIDENT_LOGO from '@Images/hkn-trident.png'; +import { createNewUser } from '@Services/AuthService'; +import { + doSignInWithEmailAndPassword, + doSendVerificationEmail, + doSignOut, +} from '@Services/auth'; const INITIAL_STATE = {}; @@ -20,12 +25,39 @@ class SignUpPage extends React.Component { } handleSubmit = async (values, setSubmitting) => { + const { email, firstName, lastName, major, gradYear, password } = values; const signupSubmission = { - ...values, + email, + firstName, + lastName, + major, + password, + graduationYear: gradYear.toString(), }; - await createUserAccountFromSignup(signupSubmission); + try { + await createNewUser(signupSubmission); + } catch { + console.log('Create new user failed'); + setSubmitting(false); + return; + } + try { + await doSignInWithEmailAndPassword(email, password, false); + } catch { + console.log('Sign in failed'); + setSubmitting(false); + return; + } + + try { + await doSendVerificationEmail(); + } catch { + console.log('Send verification email failed.'); + } + + await doSignOut(); setSubmitting(false); }; diff --git a/src/services/AuthService.ts b/src/services/AuthService.ts new file mode 100644 index 00000000..aea2a2e0 --- /dev/null +++ b/src/services/AuthService.ts @@ -0,0 +1,14 @@ +import { AuthApi, AuthControllerSignUpUserRequest } from './api/apis/AuthApi'; +import { AppUserSignupRequest, AppUserResponse } from './api/models'; +import ApiConfigStore from './ApiConfigStore'; +import { Configuration } from './api/runtime'; + +export async function createNewUser( + appUserSignupRequest: AppUserSignupRequest +): Promise { + const apiConfig: Configuration = ApiConfigStore.getApiConfig(); + const authApi: AuthApi = new AuthApi(apiConfig); + const request: AuthControllerSignUpUserRequest = { appUserSignupRequest }; + + return authApi.authControllerSignUpUser(request); +} diff --git a/src/services/api/.openapi-generator/FILES b/src/services/api/.openapi-generator/FILES index a34b2707..426df699 100644 --- a/src/services/api/.openapi-generator/FILES +++ b/src/services/api/.openapi-generator/FILES @@ -1,26 +1,28 @@ -apis\EventApi.ts -apis\UserApi.ts -apis\index.ts +apis/AuthApi.ts +apis/EventApi.ts +apis/UserApi.ts +apis/index.ts index.ts -models\AppUserEventRequest.ts -models\AppUserEventResponse.ts -models\AppUserInductionClass.ts -models\AppUserNameResponse.ts -models\AppUserPKPayload.ts -models\AppUserPostRequest.ts -models\AppUserProfileResponse.ts -models\AppUserResponse.ts -models\AppUserRolesResponse.ts -models\AttendanceResponse.ts -models\BaseEventPayload.ts -models\EventAttendanceResponse.ts -models\EventRSVPResponse.ts -models\EventRequest.ts -models\EventResponse.ts -models\MultipleAppUserResponse.ts -models\MultipleEventResponse.ts -models\MultipleUserNameResponse.ts -models\MultipleUserQuery.ts -models\RSVPResponse.ts -models\index.ts +models/AppUserEventRequest.ts +models/AppUserEventResponse.ts +models/AppUserInductionClass.ts +models/AppUserNameResponse.ts +models/AppUserPKPayload.ts +models/AppUserPostRequest.ts +models/AppUserProfileResponse.ts +models/AppUserResponse.ts +models/AppUserRolesResponse.ts +models/AppUserSignupRequest.ts +models/AttendanceResponse.ts +models/BaseEventPayload.ts +models/EventAttendanceResponse.ts +models/EventRSVPResponse.ts +models/EventRequest.ts +models/EventResponse.ts +models/MultipleAppUserResponse.ts +models/MultipleEventResponse.ts +models/MultipleUserNameResponse.ts +models/MultipleUserQuery.ts +models/RSVPResponse.ts +models/index.ts runtime.ts diff --git a/src/services/api/.openapi-generator/VERSION b/src/services/api/.openapi-generator/VERSION index 8836c812..1a487e1a 100644 --- a/src/services/api/.openapi-generator/VERSION +++ b/src/services/api/.openapi-generator/VERSION @@ -1 +1 @@ -5.0.0-beta \ No newline at end of file +5.0.0-beta2 \ No newline at end of file diff --git a/src/services/api/apis/AuthApi.ts b/src/services/api/apis/AuthApi.ts new file mode 100644 index 00000000..7225963c --- /dev/null +++ b/src/services/api/apis/AuthApi.ts @@ -0,0 +1,67 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * HKN API + * HKN API + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import * as runtime from '../runtime'; +import { + AppUserResponse, + AppUserResponseFromJSON, + AppUserResponseToJSON, + AppUserSignupRequest, + AppUserSignupRequestFromJSON, + AppUserSignupRequestToJSON, +} from '../models'; + +export interface AuthControllerSignUpUserRequest { + appUserSignupRequest?: AppUserSignupRequest; +} + +/** + * + */ +export class AuthApi extends runtime.BaseAPI { + /** + * Sign up user + */ + async authControllerSignUpUserRaw( + requestParameters: AuthControllerSignUpUserRequest + ): Promise> { + const queryParameters: any = {}; + + const headerParameters: runtime.HTTPHeaders = {}; + + headerParameters['Content-Type'] = 'application/json'; + + const response = await this.request({ + path: `/api/auth/signup`, + method: 'POST', + headers: headerParameters, + query: queryParameters, + body: AppUserSignupRequestToJSON(requestParameters.appUserSignupRequest), + }); + + return new runtime.JSONApiResponse(response, jsonValue => + AppUserResponseFromJSON(jsonValue) + ); + } + + /** + * Sign up user + */ + async authControllerSignUpUser( + requestParameters: AuthControllerSignUpUserRequest + ): Promise { + const response = await this.authControllerSignUpUserRaw(requestParameters); + return await response.value(); + } +} diff --git a/src/services/api/apis/UserApi.ts b/src/services/api/apis/UserApi.ts index 397d9a38..401802d8 100644 --- a/src/services/api/apis/UserApi.ts +++ b/src/services/api/apis/UserApi.ts @@ -172,6 +172,15 @@ export class UserApi extends runtime.BaseAPI { const headerParameters: runtime.HTTPHeaders = {}; + if (this.configuration && this.configuration.accessToken) { + const token = this.configuration.accessToken; + const tokenString = + typeof token === 'function' ? token('TokenAuth', []) : token; + + if (tokenString) { + headerParameters['Authorization'] = `Bearer ${tokenString}`; + } + } const response = await this.request({ path: `/api/users/{userID}`.replace( `{${'userID'}}`, diff --git a/src/services/api/apis/index.ts b/src/services/api/apis/index.ts index 2c5754d1..0bcd85fd 100644 --- a/src/services/api/apis/index.ts +++ b/src/services/api/apis/index.ts @@ -1,2 +1,3 @@ +export * from './AuthApi'; export * from './EventApi'; export * from './UserApi'; diff --git a/src/services/api/models/AppUserProfileResponse.ts b/src/services/api/models/AppUserProfileResponse.ts index b6cb0d7f..8c3b7f37 100644 --- a/src/services/api/models/AppUserProfileResponse.ts +++ b/src/services/api/models/AppUserProfileResponse.ts @@ -61,7 +61,13 @@ export interface AppUserProfileResponse { * @type {AppUserInductionClass} * @memberof AppUserProfileResponse */ - inductionClass: AppUserInductionClass; + inductionClass?: AppUserInductionClass; + /** + * + * @type {string} + * @memberof AppUserProfileResponse + */ + role: AppUserProfileResponseRoleEnum; } export function AppUserProfileResponseFromJSON( @@ -83,7 +89,10 @@ export function AppUserProfileResponseFromJSONTyped( email: json['email'], major: json['major'], graduationYear: json['graduationYear'], - inductionClass: AppUserInductionClassFromJSON(json['inductionClass']), + inductionClass: !exists(json, 'inductionClass') + ? undefined + : AppUserInductionClassFromJSON(json['inductionClass']), + role: json['role'], }; } @@ -103,5 +112,18 @@ export function AppUserProfileResponseToJSON( major: value.major, graduationYear: value.graduationYear, inductionClass: AppUserInductionClassToJSON(value.inductionClass), + role: value.role, }; } + +/** + * @export + * @enum {string} + */ +export enum AppUserProfileResponseRoleEnum { + Admin = 'admin', + Officer = 'officer', + Member = 'member', + Inductee = 'inductee', + Guest = 'guest', +} diff --git a/src/services/api/models/AppUserSignupRequest.ts b/src/services/api/models/AppUserSignupRequest.ts new file mode 100644 index 00000000..556baca9 --- /dev/null +++ b/src/services/api/models/AppUserSignupRequest.ts @@ -0,0 +1,98 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * HKN API + * HKN API + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +/** + * + * @export + * @interface AppUserSignupRequest + */ +export interface AppUserSignupRequest { + /** + * + * @type {string} + * @memberof AppUserSignupRequest + */ + email: string; + /** + * + * @type {string} + * @memberof AppUserSignupRequest + */ + firstName: string; + /** + * + * @type {string} + * @memberof AppUserSignupRequest + */ + lastName: string; + /** + * + * @type {string} + * @memberof AppUserSignupRequest + */ + major: string; + /** + * + * @type {string} + * @memberof AppUserSignupRequest + */ + graduationYear: string; + /** + * + * @type {string} + * @memberof AppUserSignupRequest + */ + password: string; +} + +export function AppUserSignupRequestFromJSON(json: any): AppUserSignupRequest { + return AppUserSignupRequestFromJSONTyped(json, false); +} + +export function AppUserSignupRequestFromJSONTyped( + json: any, + ignoreDiscriminator: boolean +): AppUserSignupRequest { + if (json === undefined || json === null) { + return json; + } + return { + email: json['email'], + firstName: json['firstName'], + lastName: json['lastName'], + major: json['major'], + graduationYear: json['graduationYear'], + password: json['password'], + }; +} + +export function AppUserSignupRequestToJSON( + value?: AppUserSignupRequest | null +): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + email: value.email, + firstName: value.firstName, + lastName: value.lastName, + major: value.major, + graduationYear: value.graduationYear, + password: value.password, + }; +} diff --git a/src/services/api/models/index.ts b/src/services/api/models/index.ts index 57fa2d7b..2e2729e0 100644 --- a/src/services/api/models/index.ts +++ b/src/services/api/models/index.ts @@ -7,6 +7,7 @@ export * from './AppUserPostRequest'; export * from './AppUserProfileResponse'; export * from './AppUserResponse'; export * from './AppUserRolesResponse'; +export * from './AppUserSignupRequest'; export * from './AttendanceResponse'; export * from './BaseEventPayload'; export * from './EventAttendanceResponse'; From 04abeebc0abd188705fbf24b16afe29a04dc248d Mon Sep 17 00:00:00 2001 From: Godwin Pang Date: Wed, 9 Sep 2020 15:07:33 -0700 Subject: [PATCH 09/29] Add card with vertical tabs. (#156) * Add card with vertical tabs. * Add border, grid, remove ripple. --- .../cards/CardWithVerticalTabs.stories.tsx | 26 ++++++++++ src/components/cards/CardWithVerticalTabs.tsx | 49 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 src/components/cards/CardWithVerticalTabs.stories.tsx create mode 100644 src/components/cards/CardWithVerticalTabs.tsx diff --git a/src/components/cards/CardWithVerticalTabs.stories.tsx b/src/components/cards/CardWithVerticalTabs.stories.tsx new file mode 100644 index 00000000..f7d711cf --- /dev/null +++ b/src/components/cards/CardWithVerticalTabs.stories.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import { Story, Meta } from '@storybook/react'; + +import { + CardWithVerticalTabs, + CardWithVerticalTabsProps, +} from './CardWithVerticalTabs'; + +export default { + title: 'Cards/Card with Vertical Tabs', + component: CardWithVerticalTabs, +} as Meta; + +const Template: Story = args => { + const { items } = args; + return ; +}; + +export const SampleCardWithVerticalTabs = Template.bind({}); +SampleCardWithVerticalTabs.args = { + items: [ + { title: 'Title 1', element:

Title 1

}, + { title: 'Title 2', element:

Title 2

}, + { title: 'Title 3', element:

Title 3

}, + ], +}; diff --git a/src/components/cards/CardWithVerticalTabs.tsx b/src/components/cards/CardWithVerticalTabs.tsx new file mode 100644 index 00000000..d95a6999 --- /dev/null +++ b/src/components/cards/CardWithVerticalTabs.tsx @@ -0,0 +1,49 @@ +import React, { useState } from 'react'; +import { + Grid, + Tabs as MuiTabs, + Tab as MuiTab, + makeStyles, + Theme, +} from '@material-ui/core'; + +import { Card } from './Card'; + +export interface CardWithVerticalTabsProps { + items: { title: string; element: JSX.Element }[]; +} + +const useStyles = makeStyles((theme: Theme) => ({ + tabs: { borderRight: `1px solid ${theme.palette.divider}` }, +})); + +export function CardWithVerticalTabs({ items }: CardWithVerticalTabsProps) { + const [index, setIndex] = useState(0); + const classes = useStyles(); + + const handleChange = (_: React.ChangeEvent, newValue: number) => { + setIndex(newValue); + }; + + const tabElements: JSX.Element[] = items.map(item => ( + + )); + + return ( + + + + + {tabElements} + + + {items[index].element} + + + ); +} From 69b60c36a9f35003a0a522df6a648fe5b5e845f0 Mon Sep 17 00:00:00 2001 From: Godwin Pang Date: Wed, 9 Sep 2020 15:17:12 -0700 Subject: [PATCH 10/29] Replace match.params as props with hooks (#155) * Install react-router types. * Migrate EventDetailsPage to useParam hook + typescript. * Migrate EventSignInForm to useParam hook + typescript. * Migrate EventRsvpPage to useParam hook + typescript. * Migrate EventEditPage to useParam hook + typescript. * Migrate ProfilePages to hooks + useParams hook. * Add yup typing. * Add @types to dependencies instead of devDep. @types should be in dev dep to prevent build size bloat, but somehow netlify builds fail when @types are not deps. * Modify CardProps to take in className. * Change pages to use new Card component. * Make className optional for cards. --- package-lock.json | 16 +++ package.json | 2 + src/components/cards/Card.tsx | 5 +- .../formSections/PersonalInfoSection.js | 4 +- src/pages/EventDetailsPage/index.js | 57 ---------- src/pages/EventDetailsPage/index.tsx | 30 +++++ .../components/EventEditForm/edit_form.js | 2 +- src/pages/EventEditPage/event_edit.js | 92 --------------- src/pages/EventEditPage/event_edit.tsx | 61 ++++++++++ .../EventEditPage/{index.js => index.ts} | 6 +- .../components/EventRsvpForm/index.js | 5 +- src/pages/EventRsvpPage/index.js | 104 ----------------- src/pages/EventRsvpPage/index.tsx | 67 +++++++++++ .../EventRsvpPage/{styles.js => styles.ts} | 6 +- .../components/EventSignInForm/index.js | 5 +- src/pages/EventSignInPage/index.js | 104 ----------------- src/pages/EventSignInPage/index.tsx | 67 +++++++++++ .../EventSignInPage/{styles.js => styles.ts} | 6 +- src/pages/ProfileEditPage/index.js | 105 ------------------ src/pages/ProfileEditPage/index.tsx | 84 ++++++++++++++ .../ProfileEditPage/{schema.js => schema.ts} | 0 .../ProfileEditPage/{styles.js => styles.ts} | 6 +- src/pages/ProfilePage/index.js | 89 --------------- src/pages/ProfilePage/index.tsx | 54 +++++++++ .../ProfilePage/{styles.js => styles.ts} | 6 +- 25 files changed, 412 insertions(+), 571 deletions(-) delete mode 100644 src/pages/EventDetailsPage/index.js create mode 100644 src/pages/EventDetailsPage/index.tsx delete mode 100644 src/pages/EventEditPage/event_edit.js create mode 100644 src/pages/EventEditPage/event_edit.tsx rename src/pages/EventEditPage/{index.js => index.ts} (96%) delete mode 100644 src/pages/EventRsvpPage/index.js create mode 100644 src/pages/EventRsvpPage/index.tsx rename src/pages/EventRsvpPage/{styles.js => styles.ts} (78%) delete mode 100644 src/pages/EventSignInPage/index.js create mode 100644 src/pages/EventSignInPage/index.tsx rename src/pages/EventSignInPage/{styles.js => styles.ts} (79%) delete mode 100644 src/pages/ProfileEditPage/index.js create mode 100644 src/pages/ProfileEditPage/index.tsx rename src/pages/ProfileEditPage/{schema.js => schema.ts} (100%) rename src/pages/ProfileEditPage/{styles.js => styles.ts} (53%) delete mode 100644 src/pages/ProfilePage/index.js create mode 100644 src/pages/ProfilePage/index.tsx rename src/pages/ProfilePage/{styles.js => styles.ts} (53%) diff --git a/package-lock.json b/package-lock.json index e2c27e9e..b25fb454 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4503,6 +4503,16 @@ "@types/react": "*" } }, + "@types/react-router": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.8.tgz", + "integrity": "sha512-HzOyJb+wFmyEhyfp4D4NYrumi+LQgQL/68HvJO+q6XtuHSDvw6Aqov7sCAhjbNq3bUPgPqbdvjXC5HeB2oEAPg==", + "dev": true, + "requires": { + "@types/history": "*", + "@types/react": "*" + } + }, "@types/react-syntax-highlighter": { "version": "11.0.4", "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.4.tgz", @@ -4647,6 +4657,12 @@ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" }, + "@types/yup": { + "version": "0.29.6", + "resolved": "https://registry.npmjs.org/@types/yup/-/yup-0.29.6.tgz", + "integrity": "sha512-YPDo5L5uHyxQ4UkyJST+33stD8Z6IT9fvmKyaPAGxkZ6q19foEi6sQGkmqBvzSyRPdstFEeJiS2rKuTn8rfO5g==", + "dev": true + }, "@typescript-eslint/eslint-plugin": { "version": "3.9.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.9.1.tgz", diff --git a/package.json b/package.json index 7f2a485f..161607e1 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "@storybook/react": "^6.0.13", "@types/react": "^16.9.46", "@types/react-dom": "^16.9.8", + "@types/react-router": "^5.1.8", + "@types/yup": "^0.29.6", "@typescript-eslint/parser": "^3.9.1", "classnames": "^2.2.6", "customize-cra": "^0.9.1", diff --git a/src/components/cards/Card.tsx b/src/components/cards/Card.tsx index 64bf04ff..34200d25 100644 --- a/src/components/cards/Card.tsx +++ b/src/components/cards/Card.tsx @@ -8,11 +8,12 @@ import { export interface CardProps { title?: string; children: JSX.Element; + className?: string; } -export function Card({ children, title, ...props }: CardProps): JSX.Element { +export function Card({ children, title, className }: CardProps): JSX.Element { return ( - + {title ? : null} {children} diff --git a/src/components/formSections/PersonalInfoSection.js b/src/components/formSections/PersonalInfoSection.js index 4b7513d0..09840d9a 100644 --- a/src/components/formSections/PersonalInfoSection.js +++ b/src/components/formSections/PersonalInfoSection.js @@ -12,7 +12,7 @@ const getPersonalInfoSection = params => { readOnly = params.readOnly; } - const { firstName, lastName, major, gradYear } = params || {}; + const { firstName, lastName, major, graduationYear } = params || {}; return { title: 'Personal Info', @@ -50,7 +50,7 @@ const getPersonalInfoSection = params => {
{readOnly ? ( - + ) : ( )} diff --git a/src/pages/EventDetailsPage/index.js b/src/pages/EventDetailsPage/index.js deleted file mode 100644 index b82fa75d..00000000 --- a/src/pages/EventDetailsPage/index.js +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import EventDetailsComponent from './components/EventDetails'; - -import { Loading } from '@SharedComponents'; -import { getEventById } from '@Services/EventService'; - -class EventDetailsPage extends React.Component { - constructor(props) { - const { - match: { - params: { id }, - }, - } = props; - - super(props); - - this.state = { - eventId: id, - eventInfo: null, - }; - } - - componentDidMount() { - const { eventId } = this.state; - - getEventById(eventId) - .then(eventObj => { - this.setState({ eventInfo: eventObj }); - }) - .catch(err => { - console.log(err); - }); - } - - render() { - const { eventId, eventInfo } = this.state; - - const EventDetails = - eventInfo == null ? ( - - ) : ( - - ); - - return EventDetails; - } -} - -EventDetailsPage.propTypes = { - match: PropTypes.shape({ - params: PropTypes.shape(PropTypes.string.isRequired).isRequired, - }).isRequired, -}; - -export default EventDetailsPage; diff --git a/src/pages/EventDetailsPage/index.tsx b/src/pages/EventDetailsPage/index.tsx new file mode 100644 index 00000000..e55a37e2 --- /dev/null +++ b/src/pages/EventDetailsPage/index.tsx @@ -0,0 +1,30 @@ +import React, { useState, useEffect } from 'react'; +import { useParams } from 'react-router'; + +import EventDetailsComponent from './components/EventDetails'; + +import { Loading } from '@SharedComponents'; +import { getEventById } from '@Services/EventService'; +import { EventResponse } from '@Services/api/models'; + +function EventDetailsPage(): JSX.Element { + const { id } = useParams(); + const [eventInfo, setEventInfo] = useState(null); + + useEffect(() => { + const getEvent = async () => { + const eventResponse = await getEventById(id); + setEventInfo(eventResponse); + }; + + getEvent(); + }, [id]); + + return eventInfo == null ? ( + + ) : ( + + ); +} + +export default EventDetailsPage; diff --git a/src/pages/EventEditPage/components/EventEditForm/edit_form.js b/src/pages/EventEditPage/components/EventEditForm/edit_form.js index d64195cc..842e951d 100644 --- a/src/pages/EventEditPage/components/EventEditForm/edit_form.js +++ b/src/pages/EventEditPage/components/EventEditForm/edit_form.js @@ -180,7 +180,7 @@ EventEditForm.propTypes = { startDate: PropTypes.string.isRequired, endDate: PropTypes.string.isRequired, hosts: PropTypes.array.isRequired, - location: PropTypes.string.isRequired, + location: PropTypes.string, name: PropTypes.string.isRequired, type: PropTypes.string, status: PropTypes.string.isRequired, diff --git a/src/pages/EventEditPage/event_edit.js b/src/pages/EventEditPage/event_edit.js deleted file mode 100644 index fb7022b7..00000000 --- a/src/pages/EventEditPage/event_edit.js +++ /dev/null @@ -1,92 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import EventEditForm from './components/EventEditForm'; - -import { Card } from '@SharedComponents'; -import { getEventById, updateEvent } from '@Services/EventService'; - -class EventEditPage extends React.Component { - constructor(props) { - super(props); - const { - match: { - params: { eventId }, - }, - } = props; - this.state = { eventId, initialValues: {}, formLoading: true }; - } - - componentDidMount() { - const { eventId } = this.state; - - getEventById(eventId).then(eventInfo => { - this.setState({ - initialValues: { - ...eventInfo, - hosts: eventInfo.hosts.map(host => { - return { - id: host.id, - firstName: host.firstName, - lastName: host.lastName, - }; - }), - }, - formLoading: false, - }); - }); - } - - render() { - const { history } = this.props; - - const { eventId, initialValues, formLoading } = this.state; - - const handleCancel = () => { - history.push(`/events/${eventId}`); - }; - - const handleSubmit = (values, setSubmitting) => { - const submission = { - ...values, - hosts: values.hosts.map(host => { - return { - id: host.id, - }; - }), - }; - - updateEvent(eventId, submission).then(() => { - setSubmitting(false); - history.push(`/events/${eventId}`); - }); - }; - - return ( -
- {formLoading ? ( -
- ) : ( - - - - )} -
- ); - } -} - -EventEditPage.propTypes = { - match: PropTypes.shape({ - params: PropTypes.shape({ - eventId: PropTypes.string.isRequired, - }), - }).isRequired, -}; - -export default EventEditPage; diff --git a/src/pages/EventEditPage/event_edit.tsx b/src/pages/EventEditPage/event_edit.tsx new file mode 100644 index 00000000..7d288604 --- /dev/null +++ b/src/pages/EventEditPage/event_edit.tsx @@ -0,0 +1,61 @@ +import React, { useState, useEffect } from 'react'; +import { useHistory, useParams } from 'react-router'; + +import EventEditForm from './components/EventEditForm'; + +import { Card } from '@SharedComponents'; +import { getEventById, updateEvent } from '@Services/EventService'; +import { EventResponse, EventRequest } from '@Services/api/models'; + +function EventEditPage(): JSX.Element { + const { eventId: id } = useParams(); + const history = useHistory(); + const [event, setEvent] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const getEvent = async () => { + const eventResponse = await getEventById(id); + setEvent(eventResponse); + setLoading(false); + }; + getEvent(); + }, [id]); + + const handleCancel = () => { + history.push(`/events/${id}`); + }; + + const handleSubmit = async ( + values: EventRequest, + setSubmitting: (_: boolean) => void + ) => { + const eventRequest: EventRequest = { + ...values, + hosts: values.hosts.map(host => { + return { + id: host.id, + }; + }), + }; + + await updateEvent(id, eventRequest); + setSubmitting(false); + history.push(`/events/${id}`); + }; + + if (loading) { + return <>; + } + return ( + + + + ); +} + +export default EventEditPage; diff --git a/src/pages/EventEditPage/index.js b/src/pages/EventEditPage/index.ts similarity index 96% rename from src/pages/EventEditPage/index.js rename to src/pages/EventEditPage/index.ts index 2abbf6ea..b5a095af 100644 --- a/src/pages/EventEditPage/index.js +++ b/src/pages/EventEditPage/index.ts @@ -1,3 +1,3 @@ -import EventEditPage from './event_edit'; - -export default EventEditPage; +import EventEditPage from './event_edit'; + +export default EventEditPage; diff --git a/src/pages/EventRsvpPage/components/EventRsvpForm/index.js b/src/pages/EventRsvpPage/components/EventRsvpForm/index.js index 8bbbb0d3..f8a32d47 100644 --- a/src/pages/EventRsvpPage/components/EventRsvpForm/index.js +++ b/src/pages/EventRsvpPage/components/EventRsvpForm/index.js @@ -26,8 +26,9 @@ const EventRsvpForm = props => { { - handleSubmit(values, setSubmitting); + onSubmit={async (values, { setSubmitting, resetForm }) => { + await handleSubmit(values); + setSubmitting(false); resetForm({ values: '' }); }} > diff --git a/src/pages/EventRsvpPage/index.js b/src/pages/EventRsvpPage/index.js deleted file mode 100644 index 3f78d54b..00000000 --- a/src/pages/EventRsvpPage/index.js +++ /dev/null @@ -1,104 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Avatar, Typography, Grid } from '@material-ui/core'; -import { withStyles } from '@material-ui/core/styles'; - -import EventRsvpForm from './components/EventRsvpForm'; -import styles from './styles'; - -import HKN_TRIDENT_LOGO from '@Images/hkn-trident.png'; -import { Loading, Card, PublicPageLayout } from '@SharedComponents'; -import { getEventById, rsvpToEvent } from '@Services/EventService'; - -class EventRsvpPage extends React.Component { - constructor(props) { - const { - match: { - params: { id }, - }, - } = props; - - super(props); - - this.state = { - eventId: id, - eventInfo: null, - }; - } - - componentDidMount() { - const { eventId } = this.state; - - getEventById(eventId) - .then(eventObj => { - this.setState({ eventInfo: eventObj }); - }) - .catch(err => { - console.log(err); - }); - } - - handleSubmit = (values, setSubmitting) => { - const { eventId } = this.state; - rsvpToEvent(eventId, values); - - setSubmitting(false); - }; - - render() { - const { classes } = this.props; - const { eventInfo } = this.state; - - const EventRsvp = - eventInfo == null ? ( - - ) : ( - - - - - - - - - - - - {eventInfo.name} - - - - - Event RSVP - - - - - - - - - - - ); - - return EventRsvp; - } -} - -EventRsvpPage.propTypes = { - match: PropTypes.shape({ - params: PropTypes.shape(PropTypes.string.isRequired).isRequired, - }).isRequired, -}; - -export default withStyles(styles)(EventRsvpPage); diff --git a/src/pages/EventRsvpPage/index.tsx b/src/pages/EventRsvpPage/index.tsx new file mode 100644 index 00000000..5e9e3f71 --- /dev/null +++ b/src/pages/EventRsvpPage/index.tsx @@ -0,0 +1,67 @@ +import React, { useState, useEffect } from 'react'; +import { useParams } from 'react-router'; +import { Avatar, Typography, Grid } from '@material-ui/core'; + +import EventRsvpForm from './components/EventRsvpForm'; +import useStyles from './styles'; + +import HKN_TRIDENT_LOGO from '@Images/hkn-trident.png'; +import { Loading, Card, PublicPageLayout } from '@SharedComponents'; +import { getEventById, rsvpToEvent } from '@Services/EventService'; +import { EventResponse, AppUserEventRequest } from '@Services/api/models'; + +function EventRsvpPage(): JSX.Element { + const { id } = useParams(); + const [event, setEvent] = useState(null); + const classes = useStyles(); + + useEffect(() => { + const getEvent = async () => { + const eventResponse = await getEventById(id); + setEvent(eventResponse); + }; + getEvent(); + }, [id]); + + return event == null ? ( + + ) : ( + + + + + + + + + + + + {event.name} + + + + + Event RSVP + + + + + + + rsvpToEvent(id, appUserEventRequest) + } + /> + + + + + ); +} + +export default EventRsvpPage; diff --git a/src/pages/EventRsvpPage/styles.js b/src/pages/EventRsvpPage/styles.ts similarity index 78% rename from src/pages/EventRsvpPage/styles.js rename to src/pages/EventRsvpPage/styles.ts index 62e476e9..b63051e3 100644 --- a/src/pages/EventRsvpPage/styles.js +++ b/src/pages/EventRsvpPage/styles.ts @@ -1,4 +1,6 @@ -const styles = () => ({ +import { makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles({ root: { display: 'flex', flexDirection: 'column', @@ -22,4 +24,4 @@ const styles = () => ({ }, }); -export default styles; +export default useStyles; diff --git a/src/pages/EventSignInPage/components/EventSignInForm/index.js b/src/pages/EventSignInPage/components/EventSignInForm/index.js index 69b09681..be0d82fd 100644 --- a/src/pages/EventSignInPage/components/EventSignInForm/index.js +++ b/src/pages/EventSignInPage/components/EventSignInForm/index.js @@ -26,8 +26,9 @@ const EventSignInForm = props => { { - handleSubmit(values, setSubmitting); + onSubmit={async (values, { setSubmitting, resetForm }) => { + await handleSubmit(values); + setSubmitting(false); resetForm({ values: '' }); }} > diff --git a/src/pages/EventSignInPage/index.js b/src/pages/EventSignInPage/index.js deleted file mode 100644 index 7603518e..00000000 --- a/src/pages/EventSignInPage/index.js +++ /dev/null @@ -1,104 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Avatar, Typography, Grid } from '@material-ui/core'; -import { withStyles } from '@material-ui/core/styles'; - -import EventSignInForm from './components/EventSignInForm'; -import styles from './styles'; - -import HKN_TRIDENT_LOGO from '@Images/hkn-trident.png'; -import { Loading, Card, PublicPageLayout } from '@SharedComponents'; -import { getEventById, signInToEvent } from '@Services/EventService'; - -class EventSignInPage extends React.Component { - constructor(props) { - const { - match: { - params: { id }, - }, - } = props; - - super(props); - - this.state = { - eventId: id, - eventInfo: null, - }; - } - - componentDidMount() { - const { eventId } = this.state; - - getEventById(eventId) - .then(eventObj => { - this.setState({ eventInfo: eventObj }); - }) - .catch(err => { - console.log(err); - }); - } - - handleSubmit = (values, setSubmitting) => { - const { eventId } = this.state; - signInToEvent(eventId, values); - - setSubmitting(false); - }; - - render() { - const { classes } = this.props; - const { eventInfo } = this.state; - - const EventSignIn = - eventInfo == null ? ( - - ) : ( - - - - - - - - - - - - {eventInfo.name} - - - - - Event Sign In - - - - - - - - - - - ); - - return EventSignIn; - } -} - -EventSignInPage.propTypes = { - match: PropTypes.shape({ - params: PropTypes.shape(PropTypes.string.isRequired).isRequired, - }).isRequired, -}; - -export default withStyles(styles)(EventSignInPage); diff --git a/src/pages/EventSignInPage/index.tsx b/src/pages/EventSignInPage/index.tsx new file mode 100644 index 00000000..1099ac68 --- /dev/null +++ b/src/pages/EventSignInPage/index.tsx @@ -0,0 +1,67 @@ +import React, { useEffect, useState } from 'react'; +import { useParams } from 'react-router'; +import { Avatar, Typography, Grid } from '@material-ui/core'; + +import EventSignInForm from './components/EventSignInForm'; +import useStyles from './styles'; + +import HKN_TRIDENT_LOGO from '@Images/hkn-trident.png'; +import { Loading, Card, PublicPageLayout } from '@SharedComponents'; +import { getEventById, signInToEvent } from '@Services/EventService'; +import { EventResponse, AppUserEventRequest } from '@Services/api/models'; + +function EventSignInPage(): JSX.Element { + const { id } = useParams(); + const [event, setEvent] = useState(null); + const classes = useStyles(); + + useEffect(() => { + const getEvent = async () => { + const eventResponse: EventResponse = await getEventById(id); + setEvent(eventResponse); + }; + getEvent(); + }, [id]); + + return event == null ? ( + + ) : ( + + + + + + + + + + + + {event.name} + + + + + Event Sign In + + + + + + + signInToEvent(id, values) + } + /> + + + + + ); +} + +export default EventSignInPage; diff --git a/src/pages/EventSignInPage/styles.js b/src/pages/EventSignInPage/styles.ts similarity index 79% rename from src/pages/EventSignInPage/styles.js rename to src/pages/EventSignInPage/styles.ts index ed541d1e..e49871bf 100644 --- a/src/pages/EventSignInPage/styles.js +++ b/src/pages/EventSignInPage/styles.ts @@ -1,4 +1,6 @@ -const styles = () => ({ +import { makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles({ root: { display: 'flex', flexDirection: 'column', @@ -22,4 +24,4 @@ const styles = () => ({ }, }); -export default styles; +export default useStyles; diff --git a/src/pages/ProfileEditPage/index.js b/src/pages/ProfileEditPage/index.js deleted file mode 100644 index 2da25ca6..00000000 --- a/src/pages/ProfileEditPage/index.js +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { withStyles } from '@material-ui/core/styles'; -import { Grid } from '@material-ui/core'; -import { Formik, Form } from 'formik'; - -import schema from './schema'; -import styles from './styles'; - -import { FormLayout, Card } from '@SharedComponents'; -import { - getAccountSection, - getPersonalInfoSection, -} from '@SharedComponents/formSections'; -import { PROFILE_WITH_ID } from '@Constants/routes'; - -class ProfileEditPage extends React.Component { - constructor(props) { - super(props); - this.state = { - profile: null, - }; - } - - componentDidMount() { - // const { - // match: { - // params: { id }, - // }, - // } = this.props; - - this.setState({ - profile: { - firstName: 'Godwin', - lastName: 'Pang', - email: 'gypang@ucsd.edu', - major: 'Computer Engineering - ECE', - gradYear: 2021, - }, - }); - } - - render() { - const { - classes, - history, - match: { - params: { id }, - }, - } = this.props; - const { profile } = this.state; - - if (profile === null) { - return <>; - } - - const handleSave = (newProfile, setSubmitting) => { - // call API to save new profile - setSubmitting(false); - history.push(PROFILE_WITH_ID(id)); - }; - - const handleCancel = () => { - // TODO maybe throw up a modal - history.push(PROFILE_WITH_ID(id)); - }; - - const sections = [getAccountSection(), getPersonalInfoSection()]; - return ( - { - handleSave(values, setSubmitting); - }} - > - {({ submitForm }) => ( -
- - - - - -
- )} -
- ); - } -} - -ProfileEditPage.propTypes = { - match: PropTypes.shape({ - params: PropTypes.shape({ - id: PropTypes.string.isRequired, - }), - }).isRequired, -}; - -export default withStyles(styles)(ProfileEditPage); diff --git a/src/pages/ProfileEditPage/index.tsx b/src/pages/ProfileEditPage/index.tsx new file mode 100644 index 00000000..cadb33cb --- /dev/null +++ b/src/pages/ProfileEditPage/index.tsx @@ -0,0 +1,84 @@ +import React, { useState } from 'react'; +import { useParams, useHistory } from 'react-router'; +import { Grid } from '@material-ui/core'; +import { Formik, Form } from 'formik'; + +import schema from './schema'; +import useStyles from './styles'; + +import { Card, FormLayout } from '@SharedComponents'; +import { + getAccountSection, + getPersonalInfoSection, +} from '@SharedComponents/formSections'; +import { PROFILE_WITH_ID } from '@Constants/routes'; +import { + AppUserResponse, + AppUserResponseRoleEnum, + AppUserPostRequest, +} from '@Services/api/models'; + +function ProfileEditPage(): JSX.Element { + const [profile, setProfile] = useState(null); + const { id } = useParams(); + const history = useHistory(); + const classes = useStyles(); + + setProfile({ + id: 1, + firstName: 'Godwin', + lastName: 'Pang', + email: 'gypang@ucsd.edu', + major: 'Computer Engineering - ECE', + graduationYear: '2021', + role: AppUserResponseRoleEnum.Officer, + }); + + if (profile === null) { + return <>; + } + + const handleSave = ( + newProfile: AppUserPostRequest, + setSubmitting: (_: boolean) => void + ) => { + // call API to save new profile + setSubmitting(false); + history.push(PROFILE_WITH_ID(id)); + }; + + const handleCancel = () => { + // TODO maybe throw up a modal + history.push(PROFILE_WITH_ID(id)); + }; + + const sections = [getAccountSection(), getPersonalInfoSection()]; + return ( + { + // TODO fix this + handleSave((values as unknown) as AppUserPostRequest, setSubmitting); + }} + > + {({ submitForm }) => ( +
+ + + + + +
+ )} +
+ ); +} + +export default ProfileEditPage; diff --git a/src/pages/ProfileEditPage/schema.js b/src/pages/ProfileEditPage/schema.ts similarity index 100% rename from src/pages/ProfileEditPage/schema.js rename to src/pages/ProfileEditPage/schema.ts diff --git a/src/pages/ProfileEditPage/styles.js b/src/pages/ProfileEditPage/styles.ts similarity index 53% rename from src/pages/ProfileEditPage/styles.js rename to src/pages/ProfileEditPage/styles.ts index cc8ad185..4aa445de 100644 --- a/src/pages/ProfileEditPage/styles.js +++ b/src/pages/ProfileEditPage/styles.ts @@ -1,4 +1,6 @@ -const styles = () => ({ +import { makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles({ root: { width: '100%', }, @@ -10,4 +12,4 @@ const styles = () => ({ }, }); -export default styles; +export default useStyles; diff --git a/src/pages/ProfilePage/index.js b/src/pages/ProfilePage/index.js deleted file mode 100644 index 992f0e43..00000000 --- a/src/pages/ProfilePage/index.js +++ /dev/null @@ -1,89 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { withStyles } from '@material-ui/core/styles'; -import { Grid } from '@material-ui/core'; - -import styles from './styles'; - -import { FormLayout, Card } from '@SharedComponents'; -import { - getAccountSection, - getPersonalInfoSection, -} from '@SharedComponents/formSections'; -import { PROFILE_EDIT_WITH_ID } from '@Constants/routes'; - -class ProfilePage extends React.Component { - constructor(props) { - super(props); - this.state = { - profile: null, - }; - } - - componentDidMount() { - // const { - // match: { - // params: { id }, - // }, - // } = this.props; - // make db query here - this.setState({ - profile: { - firstName: 'Godwin', - lastName: 'Pang', - email: 'gypang@ucsd.edu', - major: 'Computer Engineering', - gradYear: 2021, - }, - }); - } - - render() { - const { - classes, - match: { - params: { id }, - }, - } = this.props; - const { profile } = this.state; - - if (profile === null) { - return <>; - } - - const { firstName, lastName, email, major, gradYear } = profile; - const sections = [ - getAccountSection({ readOnly: true, email }), - getPersonalInfoSection({ - readOnly: true, - firstName, - lastName, - major, - gradYear, - }), - ]; - - return ( - - - - - - ); - } -} - -export default withStyles(styles)(ProfilePage); - -ProfilePage.propTypes = { - match: PropTypes.shape({ - params: PropTypes.shape({ - id: PropTypes.string.isRequired, - }), - }).isRequired, -}; diff --git a/src/pages/ProfilePage/index.tsx b/src/pages/ProfilePage/index.tsx new file mode 100644 index 00000000..1a7e270d --- /dev/null +++ b/src/pages/ProfilePage/index.tsx @@ -0,0 +1,54 @@ +import React, { useState } from 'react'; +import { Grid } from '@material-ui/core'; + +import useStyles from './styles'; + +import { Card, FormLayout } from '@SharedComponents'; +import { + getAccountSection, + getPersonalInfoSection, +} from '@SharedComponents/formSections'; +import { PROFILE_EDIT_WITH_ID } from '@Constants/routes'; +import { AppUserResponse, AppUserResponseRoleEnum } from '@Services/api/models'; + +function ProfilePage(): JSX.Element { + const [profile, setProfile] = useState(null); + const classes = useStyles(); + + setProfile({ + id: 1, + firstName: 'Godwin', + lastName: 'Pang', + email: 'gypang@ucsd.edu', + major: 'Computer Engineering', + graduationYear: '2021', + role: AppUserResponseRoleEnum.Officer, + }); + + if (profile === null) { + return <>; + } + + const sections = [ + getAccountSection({ readOnly: true, ...profile }), + getPersonalInfoSection({ + readOnly: true, + ...profile, + }), + ]; + + return ( + + + + + + ); +} + +export default ProfilePage; diff --git a/src/pages/ProfilePage/styles.js b/src/pages/ProfilePage/styles.ts similarity index 53% rename from src/pages/ProfilePage/styles.js rename to src/pages/ProfilePage/styles.ts index cc8ad185..4aa445de 100644 --- a/src/pages/ProfilePage/styles.js +++ b/src/pages/ProfilePage/styles.ts @@ -1,4 +1,6 @@ -const styles = () => ({ +import { makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles({ root: { width: '100%', }, @@ -10,4 +12,4 @@ const styles = () => ({ }, }); -export default styles; +export default useStyles; From d685d297c82cda49b6adec78182e02ef0c0d3719 Mon Sep 17 00:00:00 2001 From: Godwin Pang Date: Thu, 10 Sep 2020 01:02:26 -0700 Subject: [PATCH 11/29] Migrate App + Context to Typescript (#158) * Add react-router-dom types. * Changes App + contexts to ts. * Add types for react-router-dom. * Fix prop typing. --- package-lock.json | 212 +++++++++++++++++++++++++++------------- package.json | 1 + src/contexts.js | 5 - src/contexts.ts | 10 ++ src/pages/App/index.js | 164 ------------------------------- src/pages/App/index.tsx | 143 +++++++++++++++++++++++++++ 6 files changed, 297 insertions(+), 238 deletions(-) delete mode 100644 src/contexts.js create mode 100644 src/contexts.ts delete mode 100644 src/pages/App/index.js create mode 100644 src/pages/App/index.tsx diff --git a/package-lock.json b/package-lock.json index b25fb454..411f45c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4507,12 +4507,21 @@ "version": "5.1.8", "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.8.tgz", "integrity": "sha512-HzOyJb+wFmyEhyfp4D4NYrumi+LQgQL/68HvJO+q6XtuHSDvw6Aqov7sCAhjbNq3bUPgPqbdvjXC5HeB2oEAPg==", - "dev": true, "requires": { "@types/history": "*", "@types/react": "*" } }, + "@types/react-router-dom": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.5.tgz", + "integrity": "sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw==", + "requires": { + "@types/history": "*", + "@types/react": "*", + "@types/react-router": "*" + } + }, "@types/react-syntax-highlighter": { "version": "11.0.4", "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.4.tgz", @@ -4660,8 +4669,7 @@ "@types/yup": { "version": "0.29.6", "resolved": "https://registry.npmjs.org/@types/yup/-/yup-0.29.6.tgz", - "integrity": "sha512-YPDo5L5uHyxQ4UkyJST+33stD8Z6IT9fvmKyaPAGxkZ6q19foEi6sQGkmqBvzSyRPdstFEeJiS2rKuTn8rfO5g==", - "dev": true + "integrity": "sha512-YPDo5L5uHyxQ4UkyJST+33stD8Z6IT9fvmKyaPAGxkZ6q19foEi6sQGkmqBvzSyRPdstFEeJiS2rKuTn8rfO5g==" }, "@typescript-eslint/eslint-plugin": { "version": "3.9.1", @@ -12670,19 +12678,23 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "aproba": { "version": "1.2.0", - "bundled": true + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, "are-we-there-yet": { "version": "1.1.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "requires": { "delegates": "^1.0.0", "readable-stream": "^2.0.6" @@ -12690,11 +12702,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -12702,57 +12716,69 @@ }, "chownr": { "version": "1.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==" }, "code-point-at": { "version": "1.1.0", - "bundled": true + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "concat-map": { "version": "0.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "core-util-is": { "version": "1.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "debug": { "version": "3.2.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "requires": { "ms": "^2.1.1" } }, "deep-extend": { "version": "0.6.0", - "bundled": true + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, "delegates": { "version": "1.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "detect-libc": { "version": "1.0.3", - "bundled": true + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, "fs-minipass": { "version": "1.2.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz", + "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==", "requires": { "minipass": "^2.2.1" } }, "fs.realpath": { "version": "1.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "requires": { "aproba": "^1.0.3", "console-control-strings": "^1.0.0", @@ -12766,7 +12792,8 @@ }, "glob": { "version": "7.1.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -12778,25 +12805,29 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, "iconv-lite": { "version": "0.4.24", - "bundled": true, + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "requires": { "safer-buffer": ">= 2.1.2 < 3" } }, "ignore-walk": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "requires": { "minimatch": "^3.0.4" } }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { "once": "^1.3.0", "wrappy": "1" @@ -12804,37 +12835,44 @@ }, "inherits": { "version": "2.0.4", - "bundled": true + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.5", - "bundled": true + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { "number-is-nan": "^1.0.0" } }, "isarray": { "version": "1.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "1.2.0", - "bundled": true + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "minipass": { "version": "2.3.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -12842,31 +12880,36 @@ }, "minizlib": { "version": "1.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "requires": { "minipass": "^2.2.1" } }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" }, "dependencies": { "minimist": { "version": "0.0.8", - "bundled": true + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" } } }, "ms": { "version": "2.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "needle": { "version": "2.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", + "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", "requires": { "debug": "^3.2.6", "iconv-lite": "^0.4.4", @@ -12875,7 +12918,8 @@ }, "node-pre-gyp": { "version": "0.13.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz", + "integrity": "sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ==", "requires": { "detect-libc": "^1.0.2", "mkdirp": "^0.5.1", @@ -12891,7 +12935,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "requires": { "abbrev": "1", "osenv": "^0.1.4" @@ -12899,11 +12944,13 @@ }, "npm-bundled": { "version": "1.0.6", - "bundled": true + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==" }, "npm-packlist": { "version": "1.4.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.4.tgz", + "integrity": "sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==", "requires": { "ignore-walk": "^3.0.1", "npm-bundled": "^1.0.1" @@ -12911,7 +12958,8 @@ }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "requires": { "are-we-there-yet": "~1.1.2", "console-control-strings": "~1.1.0", @@ -12921,30 +12969,36 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "object-assign": { "version": "4.1.1", - "bundled": true + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" } }, "os-homedir": { "version": "1.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-tmpdir": { "version": "1.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "requires": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" @@ -12952,11 +13006,13 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "process-nextick-args": { "version": "2.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "protobufjs": { "version": "5.0.3", @@ -12971,7 +13027,8 @@ }, "rc": { "version": "1.2.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -12981,7 +13038,8 @@ }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -12994,38 +13052,46 @@ }, "rimraf": { "version": "2.7.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "requires": { "glob": "^7.1.3" } }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safer-buffer": { "version": "2.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sax": { "version": "1.2.4", - "bundled": true + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "semver": { "version": "5.7.1", - "bundled": true + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "set-blocking": { "version": "2.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "signal-exit": { "version": "3.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -13034,25 +13100,29 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" } }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" } }, "strip-json-comments": { "version": "2.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, "tar": { "version": "4.4.10", - "bundled": true, + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", + "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", @@ -13065,22 +13135,26 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "wide-align": { "version": "1.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "requires": { "string-width": "^1.0.2 || 2" } }, "wrappy": { "version": "1.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "yallist": { "version": "3.0.3", - "bundled": true + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" } } }, diff --git a/package.json b/package.json index 161607e1..2960a5bf 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@types/react": "^16.9.46", "@types/react-dom": "^16.9.8", "@types/react-router": "^5.1.8", + "@types/react-router-dom": "^5.1.5", "@types/yup": "^0.29.6", "@typescript-eslint/parser": "^3.9.1", "classnames": "^2.2.6", diff --git a/src/contexts.js b/src/contexts.js deleted file mode 100644 index 6c007b48..00000000 --- a/src/contexts.js +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -// we might make more contexts... -// eslint-disable-next-line import/prefer-default-export -export const UserContext = React.createContext(null); diff --git a/src/contexts.ts b/src/contexts.ts new file mode 100644 index 00000000..fa92f3ca --- /dev/null +++ b/src/contexts.ts @@ -0,0 +1,10 @@ +import React from 'react'; + +// we might make more contexts... +// eslint-disable-next-line import/prefer-default-export +export interface UserContextValues { + userId: string; + userRoles: string[]; +} + +export const UserContext = React.createContext(null); diff --git a/src/pages/App/index.js b/src/pages/App/index.js deleted file mode 100644 index fcf00e8f..00000000 --- a/src/pages/App/index.js +++ /dev/null @@ -1,164 +0,0 @@ -import React from 'react'; -import { BrowserRouter, Route, Switch, Redirect } from 'react-router-dom'; -import * as firebase from 'firebase/app'; -import 'firebase/auth'; -import { hot } from 'react-hot-loader/root'; - -import { - SignInPage, - SignUpPage, - PointsPage, - InducteePointsPage, - ResumePage, - EventsPage, - CalendarPage, - EventEditPage, - EventDetailsPage, - EventSignInPage, - EventRsvpPage, -} from '@Pages'; -import { Loading } from '@SharedComponents'; -import { UserContext } from '@Contexts'; -import * as ROUTES from '@Constants/routes'; -import { getRolesFromClaims } from '@Services/claims'; -import { - InducteeRoutingPermission, - OfficerRoutingPermission, -} from '@HOCs/RoutingPermissions'; -import ApiConfigStore from '@Services/ApiConfigStore'; - -const INITIAL_STATES = { - userClaims: null, - userToken: null, - isLoading: true, -}; - -class App extends React.Component { - constructor(props) { - super(props); - - this.state = { ...INITIAL_STATES }; - } - - componentDidMount() { - firebase.auth().onAuthStateChanged(async user => { - if (user) { - const tokenResult = await user.getIdTokenResult(); - const { claims, token } = tokenResult; - - this.setState({ - userClaims: { - userId: claims.user_id, - userRoles: getRolesFromClaims(claims), - }, - userToken: token, - isLoading: false, - }); - } else { - this.setState({ - userClaims: null, - userToken: null, - isLoading: false, - }); - } - }); - } - - setClaims = claims => { - const { userToken } = this.state; - - ApiConfigStore.setToken(userToken); - - this.setState({ - userClaims: { - userId: claims.user_id, - userRoles: getRolesFromClaims(claims), - }, - }); - }; - - render() { - const { userClaims, isLoading } = this.state; - - if (isLoading) { - return ; - } - - return ( - - - - } - /> - } /> - } - /> - } - /> - InducteeRoutingPermission(EventsPage)(props)} - /> - InducteeRoutingPermission(PointsPage)(props)} - /> - InducteeRoutingPermission(ResumePage)(props)} - /> - - OfficerRoutingPermission(InducteePointsPage)(props) - } - /> - InducteeRoutingPermission(CalendarPage)(props)} - /> - - InducteeRoutingPermission(EventDetailsPage)(props) - } - /> - OfficerRoutingPermission(EventEditPage)(props)} - /> - {/* InducteeRoutingPermission(ProfilePage)(props)} - /> - InducteeRoutingPermission(ProfileEditPage)(props)} - /> */} - } /> - - - - ); - } -} - -export default process.env.NODE_ENV === 'development' ? hot(App) : App; diff --git a/src/pages/App/index.tsx b/src/pages/App/index.tsx new file mode 100644 index 00000000..06323435 --- /dev/null +++ b/src/pages/App/index.tsx @@ -0,0 +1,143 @@ +import React, { useState, useEffect } from 'react'; +import { BrowserRouter, Route, Switch, Redirect } from 'react-router-dom'; +import firebase from 'firebase/app'; +import 'firebase/auth'; +import { hot } from 'react-hot-loader/root'; + +import { + SignInPage, + SignUpPage, + PointsPage, + InducteePointsPage, + ResumePage, + EventsPage, + CalendarPage, + EventEditPage, + EventDetailsPage, + EventSignInPage, + EventRsvpPage, +} from '@Pages'; +import { Loading } from '@SharedComponents'; +import { UserContext, UserContextValues } from '@Contexts'; +import * as ROUTES from '@Constants/routes'; +import { getRolesFromClaims } from '@Services/claims'; +import { + InducteeRoutingPermission, + OfficerRoutingPermission, +} from '@HOCs/RoutingPermissions'; +import ApiConfigStore from '@Services/ApiConfigStore'; + +function App(): JSX.Element { + const [userClaims, setUserClaims] = useState(null); + const [userToken, setUserToken] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + firebase.auth().onAuthStateChanged(async user => { + if (user) { + const tokenResult = await user.getIdTokenResult(); + const { claims, token } = tokenResult; + + setUserClaims({ + userId: claims.user_id, + userRoles: getRolesFromClaims(claims), + }); + setUserToken(token); + setIsLoading(false); + } else { + setUserClaims(null); + setUserToken(null); + setIsLoading(false); + } + }); + }, []); + + // eslint-disable-next-line camelcase + const setClaims = (claims: { user_id: string }) => { + ApiConfigStore.setToken(userToken || ''); + + setUserClaims({ + userId: claims.user_id, + userRoles: getRolesFromClaims(claims), + }); + }; + + if (isLoading) { + return ; + } + + return ( + + + + } + /> + } /> + } + /> + } + /> + InducteeRoutingPermission(EventsPage)(props)} + /> + InducteeRoutingPermission(PointsPage)(props)} + /> + InducteeRoutingPermission(ResumePage)(props)} + /> + + OfficerRoutingPermission(InducteePointsPage)(props) + } + /> + InducteeRoutingPermission(CalendarPage)(props)} + /> + InducteeRoutingPermission(EventDetailsPage)(props)} + /> + OfficerRoutingPermission(EventEditPage)(props)} + /> + {/* InducteeRoutingPermission(ProfilePage)(props)} + /> + InducteeRoutingPermission(ProfileEditPage)(props)} + /> */} + } /> + + + + ); +} + +export default process.env.NODE_ENV === 'development' ? hot(App) : App; From 7e2f53b5ea37d43d7acf53e752222012078597cd Mon Sep 17 00:00:00 2001 From: Thai <42761684+thaigillespie@users.noreply.github.com> Date: Mon, 14 Sep 2020 21:35:03 -0700 Subject: [PATCH 12/29] Added create event button, page and form (#162) * Tsconfig changes again : ( * Added create event button, page and form. --- package-lock.json | 56 +++-- package.json | 1 + .../OfficerNameAutocomplete/index.tsx | 10 +- .../EventStatusDropdownField/index.tsx | 8 +- .../EventTypeDropdownField/index.tsx | 8 +- src/components/dropdowns/index.js | 10 +- src/components/index.js | 4 + src/constants/routes.js | 1 + src/pages/App/index.tsx | 6 + src/pages/CalendarPage/index.js | 86 +++++--- src/pages/CalendarPage/styles.js | 3 + .../components/EventCreationForm/index.tsx | 200 ++++++++++++++++++ .../components/EventCreationForm/schema.ts | 15 ++ src/pages/EventCreationPage/index.tsx | 41 ++++ src/pages/EventCreationPage/styles.ts | 9 + .../components/EventEditForm/edit_form.js | 9 +- src/pages/EventEditPage/event_edit.tsx | 7 +- src/pages/EventRsvpPage/index.tsx | 13 +- src/pages/EventSignInPage/index.tsx | 13 +- src/pages/QueriedEventPage/index.tsx | 27 +++ src/pages/index.js | 4 + tsconfig.json | 54 ++--- 22 files changed, 487 insertions(+), 98 deletions(-) rename src/{pages/EventEditPage/components => components/dropdowns}/EventStatusDropdownField/index.tsx (78%) rename src/{pages/EventEditPage/components => components/dropdowns}/EventTypeDropdownField/index.tsx (78%) create mode 100644 src/pages/EventCreationPage/components/EventCreationForm/index.tsx create mode 100644 src/pages/EventCreationPage/components/EventCreationForm/schema.ts create mode 100644 src/pages/EventCreationPage/index.tsx create mode 100644 src/pages/EventCreationPage/styles.ts create mode 100644 src/pages/QueriedEventPage/index.tsx diff --git a/package-lock.json b/package-lock.json index 411f45c1..eb61e93d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12716,7 +12716,7 @@ }, "chownr": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", + "resolved": false, "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==" }, "code-point-at": { @@ -12764,7 +12764,7 @@ }, "fs-minipass": { "version": "1.2.6", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz", + "resolved": false, "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==", "requires": { "minipass": "^2.2.1" @@ -12866,12 +12866,12 @@ }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": false, "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "minipass": { "version": "2.3.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "resolved": false, "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "requires": { "safe-buffer": "^5.1.2", @@ -12880,7 +12880,7 @@ }, "minizlib": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "resolved": false, "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "requires": { "minipass": "^2.2.1" @@ -12888,7 +12888,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" @@ -12896,7 +12896,7 @@ "dependencies": { "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": false, "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" } } @@ -13121,7 +13121,7 @@ }, "tar": { "version": "4.4.10", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", + "resolved": false, "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", "requires": { "chownr": "^1.1.1", @@ -13153,7 +13153,7 @@ }, "yallist": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "resolved": false, "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" } } @@ -18181,6 +18181,22 @@ "prepend-http": "^1.0.0", "query-string": "^4.1.0", "sort-keys": "^1.0.0" + }, + "dependencies": { + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" + } } }, "npm-run-path": { @@ -20218,12 +20234,13 @@ "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" }, "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "version": "6.13.2", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.13.2.tgz", + "integrity": "sha512-BMmDaUiLDFU1hlM38jTFcRt7HYiGP/zt1sRzrIWm5zpeEuO1rkbPS0ELI3uehoLuuhHDCS8u8lhFN3fEN4JzPQ==", "requires": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" + "decode-uri-component": "^0.2.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" } }, "querystring": { @@ -23714,6 +23731,11 @@ } } }, + "split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -23847,9 +23869,9 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" }, "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" }, "string-argv": { "version": "0.3.1", diff --git a/package.json b/package.json index 2960a5bf..a62876c2 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "material-table": "1.68.0", "material-ui": "^0.20.2", "prop-types": "^15.7.2", + "query-string": "^6.13.2", "react": "^16.13.1", "react-app-rewire-hot-loader": "^2.0.1", "react-app-rewired": "^2.1.6", diff --git a/src/components/autocomplete/OfficerNameAutocomplete/index.tsx b/src/components/autocomplete/OfficerNameAutocomplete/index.tsx index a794d825..a12ecc2f 100644 --- a/src/components/autocomplete/OfficerNameAutocomplete/index.tsx +++ b/src/components/autocomplete/OfficerNameAutocomplete/index.tsx @@ -4,7 +4,7 @@ import { BaseAutocomplete } from '../base'; import { getMultipleUsers, getUserNames } from '@Services/UserService'; -type OfficerNameData = { +export type OfficerNameData = { id: number; firstName: string; lastName: string; @@ -13,11 +13,11 @@ type OfficerNameData = { type OfficerAutocompleteProp = { name: string; label: string; - fullWidth: boolean; + fullWidth?: boolean; }; export const OfficerNameAutocomplete = (props: OfficerAutocompleteProp) => { - const { name, label, fullWidth } = props; + const { name, label, fullWidth = false } = props; const [officerNames, setOfficerNames] = useState([]); useEffect(() => { @@ -46,3 +46,7 @@ export const OfficerNameAutocomplete = (props: OfficerAutocompleteProp) => { /> ); }; + +OfficerNameAutocomplete.defaultProps = { + fullWidth: false, +}; diff --git a/src/pages/EventEditPage/components/EventStatusDropdownField/index.tsx b/src/components/dropdowns/EventStatusDropdownField/index.tsx similarity index 78% rename from src/pages/EventEditPage/components/EventStatusDropdownField/index.tsx rename to src/components/dropdowns/EventStatusDropdownField/index.tsx index 9650ff18..b493af3d 100644 --- a/src/pages/EventEditPage/components/EventStatusDropdownField/index.tsx +++ b/src/components/dropdowns/EventStatusDropdownField/index.tsx @@ -6,11 +6,11 @@ import { EventStatusEnum } from '@Services/EventService'; type EventStatusFieldProp = { name: string; label: string; - fullWidth: boolean; + fullWidth?: boolean; }; const EventStatusDropdownField = (props: EventStatusFieldProp) => { - const { name, label, fullWidth } = props; + const { name, label, fullWidth = false } = props; return ( { ); }; +EventStatusDropdownField.defaultProps = { + fullWidth: false, +}; + export default EventStatusDropdownField; diff --git a/src/pages/EventEditPage/components/EventTypeDropdownField/index.tsx b/src/components/dropdowns/EventTypeDropdownField/index.tsx similarity index 78% rename from src/pages/EventEditPage/components/EventTypeDropdownField/index.tsx rename to src/components/dropdowns/EventTypeDropdownField/index.tsx index 84c1ca41..2878125e 100644 --- a/src/pages/EventEditPage/components/EventTypeDropdownField/index.tsx +++ b/src/components/dropdowns/EventTypeDropdownField/index.tsx @@ -6,11 +6,11 @@ import { EventTypeEnum } from '@Services/EventService'; type EventTypeFieldProp = { name: string; label: string; - fullWidth: boolean; + fullWidth?: boolean; }; const EventTypeDropdownField = (props: EventTypeFieldProp) => { - const { name, label, fullWidth } = props; + const { name, label, fullWidth = false } = props; return ( { ); }; +EventTypeDropdownField.defaultProps = { + fullWidth: false, +}; + export default EventTypeDropdownField; diff --git a/src/components/dropdowns/index.js b/src/components/dropdowns/index.js index 4377ae4f..1ac64071 100644 --- a/src/components/dropdowns/index.js +++ b/src/components/dropdowns/index.js @@ -1,5 +1,13 @@ import MajorDropdownField from './MajorDropdownField'; import YearDropdownField from './YearDropdownField'; import AffiliateDropdownField from './AffiliateDropdownField'; +import EventStatusDropdownField from './EventStatusDropdownField'; +import EventTypeDropdownField from './EventTypeDropdownField'; -export { MajorDropdownField, YearDropdownField, AffiliateDropdownField }; +export { + MajorDropdownField, + YearDropdownField, + AffiliateDropdownField, + EventStatusDropdownField, + EventTypeDropdownField, +}; diff --git a/src/components/index.js b/src/components/index.js index df9f5f68..7f40eebb 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -3,6 +3,8 @@ import { MajorDropdownField, YearDropdownField, AffiliateDropdownField, + EventStatusDropdownField, + EventTypeDropdownField, } from './dropdowns'; import FormLayout from './FormLayout'; import InputField from './InputField'; @@ -22,6 +24,8 @@ export { MajorDropdownField, YearDropdownField, AffiliateDropdownField, + EventTypeDropdownField, + EventStatusDropdownField, FormLayout, InputField, Loading, diff --git a/src/constants/routes.js b/src/constants/routes.js index 38e2d343..c450705a 100644 --- a/src/constants/routes.js +++ b/src/constants/routes.js @@ -9,6 +9,7 @@ export const RESUME = '/resume'; export const INDUCTEES = '/inductees'; export const EVENT_EDIT = '/events/:eventId/edit'; export const EVENT_DETAILS = '/events/:id'; +export const EVENT_CREATION = '/events?create=true'; export const TEST = '/test'; export const PROFILE = '/profile/:id'; export const PROFILE_EDIT = '/profile/:id/edit'; diff --git a/src/pages/App/index.tsx b/src/pages/App/index.tsx index 06323435..3bade6b6 100644 --- a/src/pages/App/index.tsx +++ b/src/pages/App/index.tsx @@ -16,6 +16,7 @@ import { EventDetailsPage, EventSignInPage, EventRsvpPage, + QueriedEventPage, } from '@Pages'; import { Loading } from '@SharedComponents'; import { UserContext, UserContextValues } from '@Contexts'; @@ -113,6 +114,11 @@ function App(): JSX.Element { path={ROUTES.CALENDAR} render={props => InducteeRoutingPermission(CalendarPage)(props)} /> + } + /> - - + + + + + + + { + this.toggleView(); + }} + > + {view === 'calendar' ? 'list View' : 'calendar view'} + + - - - {view === 'calendar' ? ( - this.toggleEventClick(event)} - /> - ) : ( - this.toggleEventClick(event)} - /> + + + + + {view === 'calendar' ? ( + this.toggleEventClick(event)} + /> + ) : ( + this.toggleEventClick(event)} + /> + )} + + + + {selectedEvent && ( + + + + + )} - - - {selectedEvent && ( - - - - - )} + ); } diff --git a/src/pages/CalendarPage/styles.js b/src/pages/CalendarPage/styles.js index 99d26ca6..149fbaed 100644 --- a/src/pages/CalendarPage/styles.js +++ b/src/pages/CalendarPage/styles.js @@ -2,6 +2,9 @@ const styles = () => ({ root: { width: '100%', }, + buttons: { + marginBottom: '10px', + }, }); export default styles; diff --git a/src/pages/EventCreationPage/components/EventCreationForm/index.tsx b/src/pages/EventCreationPage/components/EventCreationForm/index.tsx new file mode 100644 index 00000000..e475c5f7 --- /dev/null +++ b/src/pages/EventCreationPage/components/EventCreationForm/index.tsx @@ -0,0 +1,200 @@ +import React from 'react'; +import { Formik, Field, Form } from 'formik'; +import { TextField } from 'formik-material-ui'; +import { DateTimePicker } from 'formik-material-ui-pickers'; +import DateFnsUtils from '@date-io/date-fns'; +import { Button, LinearProgress, Grid } from '@material-ui/core'; +import { MuiPickersUtilsProvider } from '@material-ui/pickers'; +import { formatISO } from 'date-fns'; + +import schema from './schema'; + +import { + OfficerNameAutocomplete, + EventTypeDropdownField, +} from '@SharedComponents'; +import { OfficerNameData } from '@SharedComponents/autocomplete/OfficerNameAutocomplete'; +import { EventTypeEnum } from '@Services/EventService'; +import { EventRequest } from '@Services/api/models'; + +interface InitialValuesType { + startDate: string; + endDate: string; + name: string; + location: string; + description: string; + fbURL: string; + canvaURL: string; + type: EventTypeEnum | undefined; + hosts: OfficerNameData[]; +} + +const INITIAL_VALUES: InitialValuesType = { + startDate: formatISO(new Date()), + endDate: formatISO(new Date()), + name: '', + type: EventTypeEnum.Social, + hosts: [], + location: '', + description: '', + fbURL: '', + canvaURL: '', +}; + +interface EventCreationFormProps { + handleSubmit: ( + values: EventRequest, + setSubmitting: (_: boolean) => void + ) => void; + handleCancel: () => void; +} + +export const EventCreationForm = (props: EventCreationFormProps) => { + const { handleSubmit, handleCancel } = props; + const urlObjects = [ + { + name: 'fbURL', + label: 'Facebook URL', + }, + { + name: 'canvaURL', + label: 'Canva URL', + }, + ]; + + return ( + + { + handleSubmit(values, setSubmitting); + }} + > + {({ submitForm, isSubmitting }) => ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + {urlObjects.map(urlObject => { + const { name, label } = urlObject; + + return ( + + + + ); + })} + + + + + + + + + + + + + + + + + + + + + + + + + + + + {isSubmitting && } + + )} +
+
+ ); +}; diff --git a/src/pages/EventCreationPage/components/EventCreationForm/schema.ts b/src/pages/EventCreationPage/components/EventCreationForm/schema.ts new file mode 100644 index 00000000..899cc68b --- /dev/null +++ b/src/pages/EventCreationPage/components/EventCreationForm/schema.ts @@ -0,0 +1,15 @@ +import * as Yup from 'yup'; + +const schema = Yup.object({ + startDate: Yup.string().required('Required'), + endDate: Yup.string().required('Required'), + name: Yup.string().required('Required'), + type: Yup.string().required('Required'), + location: Yup.string().required('Required'), + description: Yup.string().required('Required'), + hosts: Yup.array().required('Required'), + fbURL: Yup.string(), + canvaURL: Yup.string(), +}); + +export default schema; diff --git a/src/pages/EventCreationPage/index.tsx b/src/pages/EventCreationPage/index.tsx new file mode 100644 index 00000000..b5b48ed4 --- /dev/null +++ b/src/pages/EventCreationPage/index.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { useHistory } from 'react-router'; +import { Card } from '@material-ui/core'; + +import { EventCreationForm } from './components/EventCreationForm'; +import useStyles from './styles'; + +import * as ROUTES from '@Constants/routes'; +import { createEvent } from '@Services/EventService'; +import { EventRequest } from '@Services/api/models'; + +function EventCreationPage(): JSX.Element { + const history = useHistory(); + const classes = useStyles(); + + const handleSubmit = async ( + values: EventRequest, + setSubmitting: (_: boolean) => void + ) => { + const createdEvent = await createEvent(values); + const { id } = createdEvent; + + setSubmitting(false); + history.push(`/events/${id}`); + }; + + const handleCancel = () => { + history.push(ROUTES.CALENDAR); + }; + + return ( + + + + ); +} + +export default EventCreationPage; diff --git a/src/pages/EventCreationPage/styles.ts b/src/pages/EventCreationPage/styles.ts new file mode 100644 index 00000000..bfbce470 --- /dev/null +++ b/src/pages/EventCreationPage/styles.ts @@ -0,0 +1,9 @@ +import { makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles({ + root: { + padding: '16px', + }, +}); + +export default useStyles; diff --git a/src/pages/EventEditPage/components/EventEditForm/edit_form.js b/src/pages/EventEditPage/components/EventEditForm/edit_form.js index 842e951d..f11f5405 100644 --- a/src/pages/EventEditPage/components/EventEditForm/edit_form.js +++ b/src/pages/EventEditPage/components/EventEditForm/edit_form.js @@ -9,12 +9,13 @@ import { MuiPickersUtilsProvider } from '@material-ui/pickers'; import DateFnsUtils from '@date-io/date-fns'; import * as Yup from 'yup'; -import EventTypeDropdownField from '../EventTypeDropdownField'; -import EventStatusDropdownField from '../EventStatusDropdownField'; - import styles from './styles'; -import { OfficerNameAutocomplete } from '@SharedComponents'; +import { + EventTypeDropdownField, + EventStatusDropdownField, + OfficerNameAutocomplete, +} from '@SharedComponents'; const schema = Yup.object({ name: Yup.string().required('Required'), diff --git a/src/pages/EventEditPage/event_edit.tsx b/src/pages/EventEditPage/event_edit.tsx index 7d288604..68f45da7 100644 --- a/src/pages/EventEditPage/event_edit.tsx +++ b/src/pages/EventEditPage/event_edit.tsx @@ -7,8 +7,13 @@ import { Card } from '@SharedComponents'; import { getEventById, updateEvent } from '@Services/EventService'; import { EventResponse, EventRequest } from '@Services/api/models'; +interface ParamTypes { + eventId: string; +} + function EventEditPage(): JSX.Element { - const { eventId: id } = useParams(); + const { eventId } = useParams(); + const id = parseInt(eventId, 10); const history = useHistory(); const [event, setEvent] = useState(null); const [loading, setLoading] = useState(true); diff --git a/src/pages/EventRsvpPage/index.tsx b/src/pages/EventRsvpPage/index.tsx index 5e9e3f71..6df591c3 100644 --- a/src/pages/EventRsvpPage/index.tsx +++ b/src/pages/EventRsvpPage/index.tsx @@ -10,18 +10,23 @@ import { Loading, Card, PublicPageLayout } from '@SharedComponents'; import { getEventById, rsvpToEvent } from '@Services/EventService'; import { EventResponse, AppUserEventRequest } from '@Services/api/models'; +interface ParamTypes { + id: string; +} + function EventRsvpPage(): JSX.Element { - const { id } = useParams(); + const { id } = useParams(); + const eventID = parseInt(id, 10); const [event, setEvent] = useState(null); const classes = useStyles(); useEffect(() => { const getEvent = async () => { - const eventResponse = await getEventById(id); + const eventResponse = await getEventById(eventID); setEvent(eventResponse); }; getEvent(); - }, [id]); + }, [eventID]); return event == null ? ( @@ -54,7 +59,7 @@ function EventRsvpPage(): JSX.Element { - rsvpToEvent(id, appUserEventRequest) + rsvpToEvent(eventID, appUserEventRequest) } /> diff --git a/src/pages/EventSignInPage/index.tsx b/src/pages/EventSignInPage/index.tsx index 1099ac68..44933d36 100644 --- a/src/pages/EventSignInPage/index.tsx +++ b/src/pages/EventSignInPage/index.tsx @@ -10,18 +10,23 @@ import { Loading, Card, PublicPageLayout } from '@SharedComponents'; import { getEventById, signInToEvent } from '@Services/EventService'; import { EventResponse, AppUserEventRequest } from '@Services/api/models'; +interface ParamTypes { + id: string; +} + function EventSignInPage(): JSX.Element { - const { id } = useParams(); + const { id } = useParams(); + const eventID = parseInt(id, 10); const [event, setEvent] = useState(null); const classes = useStyles(); useEffect(() => { const getEvent = async () => { - const eventResponse: EventResponse = await getEventById(id); + const eventResponse: EventResponse = await getEventById(eventID); setEvent(eventResponse); }; getEvent(); - }, [id]); + }, [eventID]); return event == null ? ( @@ -54,7 +59,7 @@ function EventSignInPage(): JSX.Element { - signInToEvent(id, values) + signInToEvent(eventID, values) } /> diff --git a/src/pages/QueriedEventPage/index.tsx b/src/pages/QueriedEventPage/index.tsx new file mode 100644 index 00000000..48e52171 --- /dev/null +++ b/src/pages/QueriedEventPage/index.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { useLocation } from 'react-router'; +import queryString from 'query-string'; + +import EventCreationPage from '../EventCreationPage'; + +import { OfficerRoutingPermission } from '@HOCs/RoutingPermissions'; + +/* + * This is for conditional rendering of component(s) at /events endpoint based on + * the endpoint's query parameters + */ +function QueriedEventPage(): JSX.Element { + const location = useLocation(); + const values = queryString.parse(location.search); + const { create } = values; + + let PageToReturn = <>; + + if (create === 'true') { + PageToReturn = OfficerRoutingPermission(EventCreationPage)({}); + } + + return PageToReturn; +} + +export default QueriedEventPage; diff --git a/src/pages/index.js b/src/pages/index.js index 326fb8ff..8a609fe7 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -9,6 +9,8 @@ import EventEditPage from '@Pages/EventEditPage'; import EventDetailsPage from '@Pages/EventDetailsPage'; import EventSignInPage from '@Pages/EventSignInPage'; import EventRsvpPage from '@Pages/EventRsvpPage'; +import EventCreationPage from '@Pages/EventCreationPage'; +import QueriedEventPage from '@Pages/QueriedEventPage'; export { SignInPage, @@ -22,4 +24,6 @@ export { EventDetailsPage, EventSignInPage, EventRsvpPage, + EventCreationPage, + QueriedEventPage, }; diff --git a/tsconfig.json b/tsconfig.json index d4fca364..75325e2f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,27 +1,27 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react", - "baseUrl": "." - }, - "include": [ - "src" - ], - "extends": "./tsconfig.paths.json" -} +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react", + "baseUrl": "." + }, + "include": [ + "src" + ], + "extends": "./tsconfig.paths.json" +} From 0777f84acdc803c3f40c96baba97b1ddf19d3de3 Mon Sep 17 00:00:00 2001 From: Thai <42761684+thaigillespie@users.noreply.github.com> Date: Mon, 14 Sep 2020 23:22:47 -0700 Subject: [PATCH 13/29] Took out most usages of process.env and replaced with config file. (#161) * Took out most usages of process.env and replaced with config file. Except for src/ServiceWorker.js and ./cypress/plugins because I don't think it's a super good idea to change anything in those. * Made changes based on review+discussions from PR #161. --- .eslintrc | 7 ++++++- config-overrides.js | 1 + src/config.ts | 23 +++++++++++++++++++++++ src/index.tsx | 17 +++++++++-------- src/pages/App/index.tsx | 3 ++- src/services/ApiConfigStore.ts | 7 +++---- src/services/auth.js | 18 ------------------ tsconfig.paths.json | 3 ++- 8 files changed, 46 insertions(+), 33 deletions(-) create mode 100644 src/config.ts diff --git a/.eslintrc b/.eslintrc index b39c1616..85f69afd 100644 --- a/.eslintrc +++ b/.eslintrc @@ -78,6 +78,10 @@ { "pattern": "@Contexts/*", "group": "internal" + }, + { + "pattern": "@Config/*", + "group": "internal" } ] } @@ -94,7 +98,8 @@ ["@Pages", "./src/pages"], ["@HOCs", "./src/HOCs"], ["@Images", "./src/images"], - ["@Contexts", "./src/contexts"] + ["@Contexts", "./src/contexts"], + ["@Config", "./src/config"] ], "extensions": [".js", ".jsx", ".json", ".ts", ".tsx"] }, diff --git a/config-overrides.js b/config-overrides.js index 7eb24347..99daa082 100644 --- a/config-overrides.js +++ b/config-overrides.js @@ -19,5 +19,6 @@ module.exports = override( '@HOCs': path.resolve(__dirname, './src/HOCs'), '@Images': path.resolve(__dirname, './src/images'), '@Contexts': path.resolve(__dirname, './src/contexts'), + '@Config': path.resolve(__dirname, './src/config'), }) ); diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 00000000..4677d170 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,23 @@ +type Config = { + apiKey?: string; + authDomain?: string; + databaseURL?: string; + projectID?: string; + storageBucket?: string; + messagingSenderID?: string; + appID?: string; + apiURL?: string; + nodeEnv?: string; +}; + +export const config: Config = { + apiKey: process.env.REACT_APP_API_KEY, + authDomain: process.env.REACT_APP_AUTH_DOMAIN, + databaseURL: process.env.REACT_APP_DATABASE_URL, + projectID: process.env.REACT_APP_PROJECT_ID, + storageBucket: process.env.REACT_APP_STORAGE_BUCKET, + messagingSenderID: process.env.REACT_APP_MESSAGING_SENDER_ID, + appID: process.env.REACT_APP_APP_ID, + apiURL: process.env.REACT_APP_API_URL, + nodeEnv: process.env.NODE_ENV, +}; diff --git a/src/index.tsx b/src/index.tsx index d0d51f3b..8a8c2464 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,17 +6,18 @@ import 'firebase/firestore'; import * as serviceWorker from './serviceWorker'; import App from '@Pages/App'; +import { config } from '@Config'; -const config = { - apiKey: process.env.REACT_APP_API_KEY, - authDomain: process.env.REACT_APP_AUTH_DOMAIN, - databaseURL: process.env.REACT_APP_DATABASE_URL, - projectId: process.env.REACT_APP_PROJECT_ID, - storageBucket: process.env.REACT_APP_STORAGE_BUCKET, - messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID, +const appConfig = { + apiKey: config.apiKey, + authDomain: config.authDomain, + databaseURL: config.databaseURL, + projectId: config.projectID, + storageBucket: config.storageBucket, + messagingSenderId: config.messagingSenderID, }; -firebase.initializeApp(config); +firebase.initializeApp(appConfig); document.body.style.height = '100%'; document.body.style.margin = '0'; diff --git a/src/pages/App/index.tsx b/src/pages/App/index.tsx index 3bade6b6..dfcfaef7 100644 --- a/src/pages/App/index.tsx +++ b/src/pages/App/index.tsx @@ -27,6 +27,7 @@ import { OfficerRoutingPermission, } from '@HOCs/RoutingPermissions'; import ApiConfigStore from '@Services/ApiConfigStore'; +import { config } from '@Config'; function App(): JSX.Element { const [userClaims, setUserClaims] = useState(null); @@ -146,4 +147,4 @@ function App(): JSX.Element { ); } -export default process.env.NODE_ENV === 'development' ? hot(App) : App; +export default config.nodeEnv === 'development' ? hot(App) : App; diff --git a/src/services/ApiConfigStore.ts b/src/services/ApiConfigStore.ts index 14376758..fbe338e1 100644 --- a/src/services/ApiConfigStore.ts +++ b/src/services/ApiConfigStore.ts @@ -2,13 +2,12 @@ import { ConfigurationParameters, Configuration } from './api'; +import { config } from '@Config'; + class ApiConfigStoreClass { private configParams: ConfigurationParameters = { // TODO: load this from a config instead - basePath: - process.env.NODE_ENV === 'development' - ? 'http://localhost:3001' - : 'https://dev-api.hknucsd.com', + basePath: config.apiURL, }; private config: Configuration = new Configuration(this.configParams); diff --git a/src/services/auth.js b/src/services/auth.js index d746c0fb..ade571c7 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -1,10 +1,6 @@ import * as firebase from 'firebase/app'; import 'firebase/auth'; -import SIGNUP_ENDPOINT from '@Constants/endpoints'; - -const SIGN_UP_URL = `${process.env.REACT_APP_API_BASE_URL}${SIGNUP_ENDPOINT}`; - // Auth API const doCreateUserWithEmailAndPassword = (email, password) => { return firebase.auth().createUserWithEmailAndPassword(email, password); @@ -44,19 +40,6 @@ const getCurrentUserClaims = async () => { return tokenResult.claims; }; -const createUserAccountFromSignup = async signupSubmission => { - const response = await fetch(SIGN_UP_URL, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(signupSubmission), - }); - - return response; -}; - export { doCreateUserWithEmailAndPassword, doSignInWithEmailAndPassword, @@ -65,5 +48,4 @@ export { doSendVerificationEmail, doPasswordUpdate, getCurrentUserClaims, - createUserAccountFromSignup, }; diff --git a/tsconfig.paths.json b/tsconfig.paths.json index e3cc3c97..9b202231 100644 --- a/tsconfig.paths.json +++ b/tsconfig.paths.json @@ -14,7 +14,8 @@ "@HOCs/*": ["src/HOCs/*"], "@Images": ["src/images"], "@Images/*": ["src/images/*"], - "@Contexts": ["src/contexts"] + "@Contexts": ["src/contexts"], + "@Config": ["src/config"] } } } From 7dea57e889d6b453b23949a5f617a0a0d3f2259e Mon Sep 17 00:00:00 2001 From: Thai <42761684+thaigillespie@users.noreply.github.com> Date: Wed, 16 Sep 2020 00:17:21 -0700 Subject: [PATCH 14/29] UserContextValues's userRole in App now uses backend api to get user's role (#164) * UserContextValues's userRole in App now uses backend api to get user's role. It used to get the roles from firebase custom claims. * Add fixes. * Remove unused function. Co-authored-by: gypang --- src/pages/App/index.tsx | 29 ++++++++++++++++++++--------- src/pages/SignInPage/index.js | 22 +++++----------------- src/services/auth.js | 9 +++++---- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/pages/App/index.tsx b/src/pages/App/index.tsx index dfcfaef7..4aa6ec73 100644 --- a/src/pages/App/index.tsx +++ b/src/pages/App/index.tsx @@ -21,7 +21,7 @@ import { import { Loading } from '@SharedComponents'; import { UserContext, UserContextValues } from '@Contexts'; import * as ROUTES from '@Constants/routes'; -import { getRolesFromClaims } from '@Services/claims'; +import { getUserRole } from '@Services/UserService'; import { InducteeRoutingPermission, OfficerRoutingPermission, @@ -31,7 +31,6 @@ import { config } from '@Config'; function App(): JSX.Element { const [userClaims, setUserClaims] = useState(null); - const [userToken, setUserToken] = useState(null); const [isLoading, setIsLoading] = useState(true); useEffect(() => { @@ -40,27 +39,39 @@ function App(): JSX.Element { const tokenResult = await user.getIdTokenResult(); const { claims, token } = tokenResult; + // TODO if there's no change then don't set state to + // save a rerender + ApiConfigStore.setToken(token || ''); + + const id = parseInt(claims.user_id, 10); + const userRole = await getUserRole(id); + setUserClaims({ userId: claims.user_id, - userRoles: getRolesFromClaims(claims), + userRoles: [userRole.role], }); - setUserToken(token); setIsLoading(false); } else { setUserClaims(null); - setUserToken(null); setIsLoading(false); } }); }, []); // eslint-disable-next-line camelcase - const setClaims = (claims: { user_id: string }) => { - ApiConfigStore.setToken(userToken || ''); + const setClaims = async ( + // eslint-disable-next-line camelcase + userID: string, + token: string + ): Promise => { + ApiConfigStore.setToken(token); + + const id = parseInt(userID, 10); + const userRole = await getUserRole(id); setUserClaims({ - userId: claims.user_id, - userRoles: getRolesFromClaims(claims), + userId: userID, + userRoles: [userRole.role], }); }; diff --git a/src/pages/SignInPage/index.js b/src/pages/SignInPage/index.js index ceff7009..65e5c2d3 100644 --- a/src/pages/SignInPage/index.js +++ b/src/pages/SignInPage/index.js @@ -31,7 +31,7 @@ import { doSignOut, doSendVerificationEmail, doPasswordReset, - getCurrentUserClaims, + getCurrentUserIDAndToken, } from '@Services/auth'; const styles = theme => ({ @@ -118,29 +118,17 @@ class SignInPage extends React.Component { this.state = { ...INITIAL_STATE }; } - componentDidMount() { - const { history } = this.props; - this.listener = firebase.auth().onAuthStateChanged(authUser => { - if (authUser && authUser.isEmailVerified) { - history.push(ROUTES.HOME); - } - }); - } - - componentWillUnmount() { - this.listener(); - } - handleSignIn = event => { const { email, password, checked } = this.state; const { history, setClaims } = this.props; doSignInWithEmailAndPassword(email, password, checked) .then(() => { - return getCurrentUserClaims(); + return getCurrentUserIDAndToken(); }) - .then(claims => { - return setClaims(claims); + .then(authObj => { + const { userID, token } = authObj; + return setClaims(userID, token); }) .then(() => { if (firebase.auth().currentUser.emailVerified) { diff --git a/src/services/auth.js b/src/services/auth.js index ade571c7..7535fa3f 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -35,9 +35,10 @@ const doSendVerificationEmail = () => { return firebase.auth().currentUser.sendEmailVerification(); }; -const getCurrentUserClaims = async () => { - const tokenResult = await firebase.auth().currentUser.getIdTokenResult(); - return tokenResult.claims; +const getCurrentUserIDAndToken = async () => { + const { uid } = firebase.auth().currentUser; + const token = await firebase.auth().currentUser.getIdToken(); + return { userID: uid, token }; }; export { @@ -47,5 +48,5 @@ export { doPasswordReset, doSendVerificationEmail, doPasswordUpdate, - getCurrentUserClaims, + getCurrentUserIDAndToken, }; From 3877f4f7208bd38afd9b20625a066db77dddafa2 Mon Sep 17 00:00:00 2001 From: Thai <42761684+thaigillespie@users.noreply.github.com> Date: Wed, 16 Sep 2020 16:59:14 -0700 Subject: [PATCH 15/29] Create Event now works properly and as intended. (#165) * Tsconfig changes again : ( * Fixed merge issue * Create Event now works properly and as intended. After creating an event, the browser redirects to the newly created event's EventDetails page. Note that fbURL and canvaURL both have to either be empty or filled out with a proper URL string, otherwise a status code 400 will be sent from the backend server. * Added url validation to create event form's schema's fbURL + canvaURL. * Changed process.env in src/index.js to using config. --- .../components/EventCreationForm/schema.ts | 6 ++- src/pages/EventCreationPage/index.tsx | 11 +++- src/services/api/.openapi-generator/FILES | 52 +++++++++---------- src/services/api/.openapi-generator/VERSION | 2 +- 4 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/pages/EventCreationPage/components/EventCreationForm/schema.ts b/src/pages/EventCreationPage/components/EventCreationForm/schema.ts index 899cc68b..1df403e4 100644 --- a/src/pages/EventCreationPage/components/EventCreationForm/schema.ts +++ b/src/pages/EventCreationPage/components/EventCreationForm/schema.ts @@ -8,8 +8,10 @@ const schema = Yup.object({ location: Yup.string().required('Required'), description: Yup.string().required('Required'), hosts: Yup.array().required('Required'), - fbURL: Yup.string(), - canvaURL: Yup.string(), + fbURL: Yup.string().url('Please either enter a valid URL or leave it blank'), + canvaURL: Yup.string().url( + 'Please either enter a valid URL or leave it blank' + ), }); export default schema; diff --git a/src/pages/EventCreationPage/index.tsx b/src/pages/EventCreationPage/index.tsx index b5b48ed4..dba47bdc 100644 --- a/src/pages/EventCreationPage/index.tsx +++ b/src/pages/EventCreationPage/index.tsx @@ -17,7 +17,16 @@ function EventCreationPage(): JSX.Element { values: EventRequest, setSubmitting: (_: boolean) => void ) => { - const createdEvent = await createEvent(values); + const submission = { + ...values, + hosts: values.hosts.map(host => { + return { id: host.id }; + }), + fbURL: values.fbURL === '' ? undefined : values.fbURL, + canvaURL: values.canvaURL === '' ? undefined : values.canvaURL, + }; + + const createdEvent = await createEvent(submission); const { id } = createdEvent; setSubmitting(false); diff --git a/src/services/api/.openapi-generator/FILES b/src/services/api/.openapi-generator/FILES index 426df699..dfb244f0 100644 --- a/src/services/api/.openapi-generator/FILES +++ b/src/services/api/.openapi-generator/FILES @@ -1,28 +1,28 @@ -apis/AuthApi.ts -apis/EventApi.ts -apis/UserApi.ts -apis/index.ts +apis\AuthApi.ts +apis\EventApi.ts +apis\UserApi.ts +apis\index.ts index.ts -models/AppUserEventRequest.ts -models/AppUserEventResponse.ts -models/AppUserInductionClass.ts -models/AppUserNameResponse.ts -models/AppUserPKPayload.ts -models/AppUserPostRequest.ts -models/AppUserProfileResponse.ts -models/AppUserResponse.ts -models/AppUserRolesResponse.ts -models/AppUserSignupRequest.ts -models/AttendanceResponse.ts -models/BaseEventPayload.ts -models/EventAttendanceResponse.ts -models/EventRSVPResponse.ts -models/EventRequest.ts -models/EventResponse.ts -models/MultipleAppUserResponse.ts -models/MultipleEventResponse.ts -models/MultipleUserNameResponse.ts -models/MultipleUserQuery.ts -models/RSVPResponse.ts -models/index.ts +models\AppUserEventRequest.ts +models\AppUserEventResponse.ts +models\AppUserInductionClass.ts +models\AppUserNameResponse.ts +models\AppUserPKPayload.ts +models\AppUserPostRequest.ts +models\AppUserProfileResponse.ts +models\AppUserResponse.ts +models\AppUserRolesResponse.ts +models\AppUserSignupRequest.ts +models\AttendanceResponse.ts +models\BaseEventPayload.ts +models\EventAttendanceResponse.ts +models\EventRSVPResponse.ts +models\EventRequest.ts +models\EventResponse.ts +models\MultipleAppUserResponse.ts +models\MultipleEventResponse.ts +models\MultipleUserNameResponse.ts +models\MultipleUserQuery.ts +models\RSVPResponse.ts +models\index.ts runtime.ts diff --git a/src/services/api/.openapi-generator/VERSION b/src/services/api/.openapi-generator/VERSION index 1a487e1a..8836c812 100644 --- a/src/services/api/.openapi-generator/VERSION +++ b/src/services/api/.openapi-generator/VERSION @@ -1 +1 @@ -5.0.0-beta2 \ No newline at end of file +5.0.0-beta \ No newline at end of file From 807ea7ecc9fa6348304e63724343d70c2a73ac54 Mon Sep 17 00:00:00 2001 From: Thai <42761684+thaigillespie@users.noreply.github.com> Date: Thu, 24 Sep 2020 10:37:39 -0700 Subject: [PATCH 16/29] Add alert for successful sign up. (#170) * Put an alert for successful sign up. * Retrigger GH checks on PR #169. --- package.json | 2 +- src/pages/SignUpPage/index.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index a62876c2..265cf55c 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "storybook": "start-storybook -p 6006 -s public", "build-storybook": "build-storybook -s public", "precodegen": "rimraf src/api/*", - "codegen": "npx openapi-generator generate -i https://dev-api.hknucsd.com/api/docs/json -g typescript-fetch --additional-properties=typescriptThreePlus=true -o src/services/api" + "codegen": "npx openapi-generator generate -i https://api.hknucsd.com/api/docs/json -g typescript-fetch --additional-properties=typescriptThreePlus=true -o src/services/api" }, "eslintConfig": { "extends": "react-app" diff --git a/src/pages/SignUpPage/index.js b/src/pages/SignUpPage/index.js index ac85e5ee..0c68e9c1 100644 --- a/src/pages/SignUpPage/index.js +++ b/src/pages/SignUpPage/index.js @@ -59,6 +59,7 @@ class SignUpPage extends React.Component { await doSignOut(); setSubmitting(false); + alert('You have successfully signed up for an account.'); }; render() { From a266ddfeef3c206b7397461e26a0d7f0fa70b423 Mon Sep 17 00:00:00 2001 From: Thai <42761684+thaigillespie@users.noreply.github.com> Date: Fri, 25 Sep 2020 00:45:07 -0700 Subject: [PATCH 17/29] Fixed some stuff and added minor changes requested during demo. (#167) --- src/components/NavBar/index.js | 4 +- src/components/Tags/index.js | 6 ++- src/pages/CalendarPage/index.js | 18 ++++---- .../components/EventDetails/index.js | 4 +- .../components/EventEditForm/edit_form.js | 42 ++++++++++++------- src/services/claims.js | 23 +++------- 6 files changed, 51 insertions(+), 46 deletions(-) diff --git a/src/components/NavBar/index.js b/src/components/NavBar/index.js index 988b7841..b9626fb4 100644 --- a/src/components/NavBar/index.js +++ b/src/components/NavBar/index.js @@ -40,8 +40,8 @@ const INITIAL_STATES = { }; class NavBar extends React.Component { - constructor(props) { - super(props); + constructor() { + super(); this.state = { ...INITIAL_STATES }; } diff --git a/src/components/Tags/index.js b/src/components/Tags/index.js index 6a18b8b5..c55171c2 100644 --- a/src/components/Tags/index.js +++ b/src/components/Tags/index.js @@ -11,7 +11,11 @@ function Tags({ classes, tags }) { return (
{tags.map(tag => ( - + ))}
); diff --git a/src/pages/CalendarPage/index.js b/src/pages/CalendarPage/index.js index cd56fef8..0fdf0072 100644 --- a/src/pages/CalendarPage/index.js +++ b/src/pages/CalendarPage/index.js @@ -7,9 +7,10 @@ import EventCard from './components/EventCard'; import EventList from './components/EventList'; import styles from './styles'; +import * as ROUTES from '@Constants/routes'; +import { OfficerRenderPermission } from '@HOCs/RenderPermissions'; import { getAllEvents } from '@Services/EventService'; import { Button } from '@SharedComponents'; -import * as ROUTES from '@Constants/routes'; class CalendarPage extends React.Component { constructor() { @@ -65,15 +66,14 @@ class CalendarPage extends React.Component { - + }, + })} Start Time:{' '} - {format(parseISO(startDate), 'PPP p')} + {format(parseISO(startDate), 'PPP h:mm aaaa')} @@ -93,7 +93,7 @@ function EventDetailsComponent(props) { End Time:{' '} - {format(parseISO(endDate), 'PPP p')} + {format(parseISO(endDate), 'PPP h:mm aaaa')} diff --git a/src/pages/EventEditPage/components/EventEditForm/edit_form.js b/src/pages/EventEditPage/components/EventEditForm/edit_form.js index f11f5405..156231c4 100644 --- a/src/pages/EventEditPage/components/EventEditForm/edit_form.js +++ b/src/pages/EventEditPage/components/EventEditForm/edit_form.js @@ -26,14 +26,23 @@ const schema = Yup.object({ description: Yup.string(), fbURL: Yup.string(), canvaURL: Yup.string(), - rsvpURL: Yup.string(), - signInURL: Yup.string(), }); const EventEditForm = props => { const { handleSubmit, handleCancel, classes, initialValues } = props; - const { fbURL, canvaURL, rsvpURL, signInURL } = initialValues; - const urls = { fbURL, canvaURL, rsvpURL, signInURL }; + const { fbURL, canvaURL } = initialValues; + const urlObjects = [ + { + name: 'fbURL', + label: 'Facebook URL', + url: fbURL, + }, + { + name: 'canvaURL', + label: 'Canva URL', + url: canvaURL, + }, + ]; return ( @@ -117,16 +126,21 @@ const EventEditForm = props => { - {Object.keys(urls).map(url => ( - - ))} + {urlObjects.map(urlObject => { + const { name, label, url } = urlObject; + + return ( + + ); + })} diff --git a/src/services/claims.js b/src/services/claims.js index 06069528..582740ea 100644 --- a/src/services/claims.js +++ b/src/services/claims.js @@ -1,26 +1,13 @@ -const getRolesFromClaims = claims => { - const keys = Object.keys(claims); - const roleClaims = []; - - if (keys.includes('officer')) { - roleClaims.push('officer'); - } - - if (keys.includes('member')) { - roleClaims.push('member'); - } - - if (keys.includes('inductee')) { - roleClaims.push('inductee'); - } +const isAdmin = userContext => { + const { userRoles } = userContext; - return roleClaims; + return userRoles.includes('admin'); }; const isOfficer = userContext => { const { userRoles } = userContext; - return userRoles.includes('officer'); + return userRoles.includes('admin') || userRoles.includes('officer'); }; const isMember = userContext => { @@ -39,4 +26,4 @@ const isInductee = userContext => { ); }; -export { getRolesFromClaims, isOfficer, isMember, isInductee }; +export { isAdmin, isOfficer, isMember, isInductee }; From eeeab8a97bf5b4f094717838c48b7089c75707dc Mon Sep 17 00:00:00 2001 From: Thai <42761684+thaigillespie@users.noreply.github.com> Date: Fri, 25 Sep 2020 01:05:47 -0700 Subject: [PATCH 18/29] Automatic rsvp + sign in buttons for affiliates on event details page (#168) * Added automatic rsvp + sign in buttons for affiliates. * Merged master into rsvp_signin_buttons. --- src/components/buttons/Button.tsx | 2 +- .../components/DeleteEditButtons/index.js | 2 +- .../components/EventDetails/Links.js | 90 ------------ .../EventDetails/{index.js => index.tsx} | 137 +++++++++--------- .../EventDetails/{styles.js => styles.tsx} | 13 +- .../components/Links/Links.tsx | 80 ++++++++++ .../components/Links/styles.ts | 16 ++ .../components/RSVPButton.tsx | 26 ++++ .../components/SignInButton.ts | 26 ++++ src/pages/EventDetailsPage/index.tsx | 13 +- 10 files changed, 235 insertions(+), 170 deletions(-) delete mode 100644 src/pages/EventDetailsPage/components/EventDetails/Links.js rename src/pages/EventDetailsPage/components/EventDetails/{index.js => index.tsx} (54%) rename src/pages/EventDetailsPage/components/EventDetails/{styles.js => styles.tsx} (53%) create mode 100644 src/pages/EventDetailsPage/components/Links/Links.tsx create mode 100644 src/pages/EventDetailsPage/components/Links/styles.ts create mode 100644 src/pages/EventDetailsPage/components/RSVPButton.tsx create mode 100644 src/pages/EventDetailsPage/components/SignInButton.ts diff --git a/src/components/buttons/Button.tsx b/src/components/buttons/Button.tsx index 645ed093..0a0aecc7 100644 --- a/src/components/buttons/Button.tsx +++ b/src/components/buttons/Button.tsx @@ -4,7 +4,7 @@ import { ButtonProps as MuiButtonProps, } from '@material-ui/core'; -export interface ButtonProps { +export interface ButtonProps extends MuiButtonProps { primary?: boolean; secondary?: boolean; positive?: boolean; diff --git a/src/pages/EventDetailsPage/components/DeleteEditButtons/index.js b/src/pages/EventDetailsPage/components/DeleteEditButtons/index.js index c10b2be5..ca32e897 100644 --- a/src/pages/EventDetailsPage/components/DeleteEditButtons/index.js +++ b/src/pages/EventDetailsPage/components/DeleteEditButtons/index.js @@ -70,7 +70,7 @@ const DeleteEditButtons = props => { }; DeleteEditButtons.propTypes = { - eventId: PropTypes.string.isRequired, + eventId: PropTypes.number.isRequired, }; export default withStyles(styles)(DeleteEditButtons); diff --git a/src/pages/EventDetailsPage/components/EventDetails/Links.js b/src/pages/EventDetailsPage/components/EventDetails/Links.js deleted file mode 100644 index 87adf54f..00000000 --- a/src/pages/EventDetailsPage/components/EventDetails/Links.js +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { withStyles } from '@material-ui/core/styles'; -import { grey } from '@material-ui/core/colors'; -import { - List, - ListItem, - ListItemText, - Typography, - Link, -} from '@material-ui/core'; - -const styles = () => ({ - root: { - backgroundColor: grey[200], - }, - title: { - backgroundColor: grey[400], - }, - list_item_text: { - fontSize: '16px', - }, -}); - -function Links(props) { - const { classes, urls } = props; - const { fb, canva, rsvp, signin } = urls; - - // .map for list - return ( -
- - Links - - - - - - - - - - - - - - - - - - - - - - - - -
- ); -} - -Links.propTypes = { - urls: PropTypes.shape({ - fb: PropTypes.string, - canva: PropTypes.string, - rsvp: PropTypes.string.isRequired, - signin: PropTypes.string.isRequired, - }), -}; - -Links.defaultProps = { - urls: { - fb: '', - canva: '', - }, -}; - -export default withStyles(styles)(Links); diff --git a/src/pages/EventDetailsPage/components/EventDetails/index.js b/src/pages/EventDetailsPage/components/EventDetails/index.tsx similarity index 54% rename from src/pages/EventDetailsPage/components/EventDetails/index.js rename to src/pages/EventDetailsPage/components/EventDetails/index.tsx index b1b70e7b..e57ab1f1 100644 --- a/src/pages/EventDetailsPage/components/EventDetails/index.js +++ b/src/pages/EventDetailsPage/components/EventDetails/index.tsx @@ -1,66 +1,79 @@ import React from 'react'; -import { Typography, Button, Grid } from '@material-ui/core'; -import { withStyles } from '@material-ui/core/styles'; -import { Link } from 'react-router-dom'; +import { Typography, Grid } from '@material-ui/core'; +import { useHistory } from 'react-router'; import { format, parseISO } from 'date-fns'; -import PropTypes from 'prop-types'; import DeleteEditButtons from '../DeleteEditButtons'; +import Links from '../Links/Links'; +import SignInButton from '../SignInButton'; +import RSVPButton from '../RSVPButton'; -import Links from './Links'; -import styles from './styles'; +import useStyles from './styles'; -import { OfficerRenderPermission } from '@HOCs/RenderPermissions'; -import { Tags, Card } from '@SharedComponents'; +import { + OfficerRenderPermission, + InducteeRenderPermission, +} from '@HOCs/RenderPermissions'; import * as ROUTES from '@Constants/routes'; +import { Tags, Card, Button } from '@SharedComponents'; +import { EventResponse as EventInfo } from '@Services/api/models'; + +interface EventDetailsComponentProps { + eventInfo: EventInfo; + eventId: number; +} + +function EventDetailsComponent(props: EventDetailsComponentProps) { + const { eventInfo, eventId } = props; + const classes = useStyles(); + const history = useHistory(); -function EventDetailsComponent(props) { - const { classes, eventInfo, eventId } = props; const { endDate, hosts, - location, + location = '', description, name, startDate, - type, - fbURL, - canvaURL, + type = 'Event', + fbURL = null, + canvaURL = null, signInURL, rsvpURL, } = eventInfo; const urls = { - fb: fbURL, - canva: canvaURL, - signin: signInURL, - rsvp: rsvpURL, + fb: { + url: fbURL, + label: 'Facebook', + }, + canva: { url: canvaURL, label: 'Canva' }, }; - const eventType = type || 'Event'; - return ( - + - + - + {name} - + - - {OfficerRenderPermission(DeleteEditButtons)({ eventId })} + + {OfficerRenderPermission(DeleteEditButtons)({ + eventId, + })} - + Hosts:{' '} @@ -72,7 +85,7 @@ function EventDetailsComponent(props) { - + @@ -104,11 +117,33 @@ function EventDetailsComponent(props) { - - {OfficerRenderPermission(Links)({ urls })} + + + + {InducteeRenderPermission(Links)({ + urls, + signIn: { url: signInURL, label: 'Sign In Form' }, + rsvp: { url: rsvpURL, label: 'RSVP Form' }, + })} + + + + + + + + + + - + Description: {description} @@ -121,11 +156,11 @@ function EventDetailsComponent(props) { @@ -134,32 +169,4 @@ function EventDetailsComponent(props) { ); } -EventDetailsComponent.propTypes = { - eventInfo: PropTypes.shape({ - startDate: PropTypes.string.isRequired, - endDate: PropTypes.string.isRequired, - hosts: PropTypes.arrayOf( - PropTypes.shape({ id: PropTypes.number.isRequired }) - ).isRequired, - location: PropTypes.string, - name: PropTypes.string.isRequired, - type: PropTypes.string, - description: PropTypes.string.isRequired, - fbURL: PropTypes.string, - canvaURL: PropTypes.string, - rsvpURL: PropTypes.string.isRequired, - signInURL: PropTypes.string.isRequired, - }), - eventId: PropTypes.string.isRequired, -}; - -EventDetailsComponent.defaultProps = { - eventInfo: { - location: '', - type: '', - fbURL: '', - canvaURL: '', - }, -}; - -export default withStyles(styles)(EventDetailsComponent); +export default EventDetailsComponent; diff --git a/src/pages/EventDetailsPage/components/EventDetails/styles.js b/src/pages/EventDetailsPage/components/EventDetails/styles.tsx similarity index 53% rename from src/pages/EventDetailsPage/components/EventDetails/styles.js rename to src/pages/EventDetailsPage/components/EventDetails/styles.tsx index 7dd0b0e0..98efeb09 100644 --- a/src/pages/EventDetailsPage/components/EventDetails/styles.js +++ b/src/pages/EventDetailsPage/components/EventDetails/styles.tsx @@ -1,10 +1,9 @@ -const styles = () => ({ +import { makeStyles } from '@material-ui/core'; + +const useStyles = makeStyles({ eventDetailsCard: { width: '536px', }, - firstRow: { - marginTop: '10px', - }, title: { wordWrap: 'break-word', }, @@ -14,10 +13,6 @@ const styles = () => ({ hostName: { marginBottom: '5px', }, - calendarButton: { - marginTop: '25px', - marginBottom: '25px', - }, }); -export default styles; +export default useStyles; diff --git a/src/pages/EventDetailsPage/components/Links/Links.tsx b/src/pages/EventDetailsPage/components/Links/Links.tsx new file mode 100644 index 00000000..c29b4b42 --- /dev/null +++ b/src/pages/EventDetailsPage/components/Links/Links.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { + List, + ListItem, + ListItemText, + Typography, + Link, +} from '@material-ui/core'; + +import useStyles from './styles'; + +import { OfficerRenderPermission } from '@HOCs/RenderPermissions'; + +interface URLObject { + url: string; + label: string; +} + +interface LinksProps { + urls: { + [key: string]: URLObject; + }; + signIn: URLObject; + rsvp: URLObject; +} + +function Links(props: LinksProps) { + const { urls, signIn, rsvp } = props; + const classes = useStyles(); + + const SignInLink = () => ( + + + + + + ); + + const RSVPLink = () => ( + + + + + + ); + + return ( +
+ + Links + + + + {Object.values(urls).map(urlObj => { + return ( + + + + + + ); + })} + + {OfficerRenderPermission(SignInLink)({})} + {OfficerRenderPermission(RSVPLink)({})} + +
+ ); +} + +export default Links; diff --git a/src/pages/EventDetailsPage/components/Links/styles.ts b/src/pages/EventDetailsPage/components/Links/styles.ts new file mode 100644 index 00000000..f1c24f7c --- /dev/null +++ b/src/pages/EventDetailsPage/components/Links/styles.ts @@ -0,0 +1,16 @@ +import { makeStyles } from '@material-ui/core'; +import { grey } from '@material-ui/core/colors'; + +const useStyles = makeStyles({ + root: { + backgroundColor: grey[200], + }, + title: { + backgroundColor: grey[400], + }, + list_item_text: { + fontSize: '16px', + }, +}); + +export default useStyles; diff --git a/src/pages/EventDetailsPage/components/RSVPButton.tsx b/src/pages/EventDetailsPage/components/RSVPButton.tsx new file mode 100644 index 00000000..4b7b0a21 --- /dev/null +++ b/src/pages/EventDetailsPage/components/RSVPButton.tsx @@ -0,0 +1,26 @@ +import { InducteeRenderPermission } from '@HOCs/RenderPermissions'; +import { Button } from '@SharedComponents'; +import { rsvpToEvent } from '@Services/EventService'; + +interface SignInButtonProps { + eventId: number; + children?: string; +} + +function RSVPButton({ eventId, children = 'RSVP' }: SignInButtonProps) { + const eventRequestPayloadFiller = { + email: 'filler@filler.filler', + firstName: '', + lastName: '', + major: '', + }; + + return InducteeRenderPermission(Button)({ + children, + primary: true, + positive: true, + onClick: () => rsvpToEvent(eventId, eventRequestPayloadFiller), + }); +} + +export default RSVPButton; diff --git a/src/pages/EventDetailsPage/components/SignInButton.ts b/src/pages/EventDetailsPage/components/SignInButton.ts new file mode 100644 index 00000000..b87360c1 --- /dev/null +++ b/src/pages/EventDetailsPage/components/SignInButton.ts @@ -0,0 +1,26 @@ +import { InducteeRenderPermission } from '@HOCs/RenderPermissions'; +import { Button } from '@SharedComponents'; +import { signInToEvent } from '@Services/EventService'; + +interface SignInButtonProps { + eventId: number; + children?: string; +} + +function SignInButton({ eventId, children = 'Sign In' }: SignInButtonProps) { + const eventRequestPayloadFiller = { + email: 'filler@filler.filler', + firstName: '', + lastName: '', + major: '', + }; + + return InducteeRenderPermission(Button)({ + children, + primary: true, + positive: true, + onClick: () => signInToEvent(eventId, eventRequestPayloadFiller), + }); +} + +export default SignInButton; diff --git a/src/pages/EventDetailsPage/index.tsx b/src/pages/EventDetailsPage/index.tsx index e55a37e2..03711854 100644 --- a/src/pages/EventDetailsPage/index.tsx +++ b/src/pages/EventDetailsPage/index.tsx @@ -7,23 +7,28 @@ import { Loading } from '@SharedComponents'; import { getEventById } from '@Services/EventService'; import { EventResponse } from '@Services/api/models'; +interface EventID { + id: string; +} + function EventDetailsPage(): JSX.Element { - const { id } = useParams(); + const { id } = useParams(); + const eventId = parseInt(id, 10); const [eventInfo, setEventInfo] = useState(null); useEffect(() => { const getEvent = async () => { - const eventResponse = await getEventById(id); + const eventResponse = await getEventById(eventId); setEventInfo(eventResponse); }; getEvent(); - }, [id]); + }, [eventId]); return eventInfo == null ? ( ) : ( - + ); } From 757499884fe8dc982ae4df19f41373882f9f7402 Mon Sep 17 00:00:00 2001 From: Thai <42761684+thaigillespie@users.noreply.github.com> Date: Sat, 26 Sep 2020 22:12:44 -0700 Subject: [PATCH 19/29] Added docs for path alias, date-fns and directory structure. (#171) --- README.md | 10 ++-- guides/adding_new_firebase_functions.md | 10 ---- .../miscellaneous/date_time_library_to_use.md | 7 +++ guides/path_alias/add_path_alias.md | 51 +++++++++++++++++++ guides/path_alias/path_alias.md | 41 +++++++++++++++ 5 files changed, 106 insertions(+), 13 deletions(-) delete mode 100644 guides/adding_new_firebase_functions.md create mode 100644 guides/miscellaneous/date_time_library_to_use.md create mode 100644 guides/path_alias/add_path_alias.md create mode 100644 guides/path_alias/path_alias.md diff --git a/README.md b/README.md index 7c59b047..5117d63e 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,13 @@ npm run start | **src/components** | Reusable components go here | | **src/pages** | Pages go here - a page is a component that is directly rendered by a Route in our react router | | **src/constants** | Constants go here | -| **src/images** | Contains images used throughout project | -| src/contexts.js | React contexts go here | -| src/index.js | Entry point of app | +| **src/HOCs** | Higher-order components used for frontend RBAC go here | +| **src/services** | Service functions used by React components, either to make an HTTP request to backend or process information or extract data, go here | +| src/storybook | Introduction to storybook and non-component specific stories go here | +| src/images | Contains images used throughout project | +| src/contexts.ts | React contexts go here | +| src/config.ts | Environment variables are gathered into one single config object here | +| src/index.tsx | Entry point of app | | src/serviceWorker.js | Runs separately from the main browser thread, intercepting network requests, caching or retrieving resources from the cache, and delivering push messages | | .env | Environment variables | | firebase.json | Defines Firebase Hosting configuration | diff --git a/guides/adding_new_firebase_functions.md b/guides/adding_new_firebase_functions.md deleted file mode 100644 index acceb2db..00000000 --- a/guides/adding_new_firebase_functions.md +++ /dev/null @@ -1,10 +0,0 @@ -# Adding New Firebase Functions - -## Definition - -We define a firebase function as a function that interacts with the backend, which is currently Firebase. - -All firebase functions may be found in the src/services folder. - -For example, to create a function that gets events, you should add the corresponding function in src/services/events.js. -Please make sure to return promise wrapping JS objects/arrays and not snapshots! The user of the firebase functions should not have to know about what you're doing with the snapshots! diff --git a/guides/miscellaneous/date_time_library_to_use.md b/guides/miscellaneous/date_time_library_to_use.md new file mode 100644 index 00000000..be71fd55 --- /dev/null +++ b/guides/miscellaneous/date_time_library_to_use.md @@ -0,0 +1,7 @@ +# Date Time Library To Use + +Use **date-fns** for any of your date time needs. Do not use **moment.js** or any other DateTime library, nor should you directly use the DateTime API offered by JS/TS. + +We used to have moment.js as our main DateTime library, but switched to date-fns for very good reasons that can be found [here](https://github.com/you-dont-need/You-Dont-Need-Momentjs). + +## Use date-fns please! diff --git a/guides/path_alias/add_path_alias.md b/guides/path_alias/add_path_alias.md new file mode 100644 index 00000000..58b234d6 --- /dev/null +++ b/guides/path_alias/add_path_alias.md @@ -0,0 +1,51 @@ +# Add Path Aliases + +Follow the steps below to add your own path alias. + +### 1. Determine the name and the destination to point to for your path alias + +The name of the path alias should be in UpperCamelCase and it should be as short and concise as possible. + +The destination to point to should not coincide with the destination that an already existing path alias points to. We don't want multiple path aliases pointing to the same place. + +### 2. Navigate to ./tsconfig.paths.json and add your path alias + +Go to the `paths` property of `compilerOptions` and add a new key-value pair + +- `"@YourPathAlias": ["DestinationPathToReplace"]` + +Example: `"@Pages": ["src/pages"]` + +### 3. Navigate to ./config-overrides.js and add your path alias + +Go to the line starting with `module.exports` (should be at the end of the file) then look at `addWebpackAlias`. Add your path alias as a parameter to `addWebpackAlias` + +- `'@YourPathAlias': path.resolve(__dirname, 'DestinationPathToReplace')` + +Example: `'@Images': path.resolve(__dirname, './src/images')` + +### 4. Navigate to ./eslintrc and add your path alias + +Go to the `rules` property of the config JSON object, then to the `import/order` property of `rules`. Then, go to the `pathGroups` property and add your path alias + +``` + { + "pattern": "@YourPathAlias/*", + "group": "internal" + } +``` + +Example: + +``` +{ + "pattern": "@Constants/*", + "group": "internal" +} +``` + +### 5. Restart VSCode to start using the path alias + +Now you can use the path aliases whenever you need to, making import statements much cleaner and easier to read! + +**Please make sure that your path alias string and destination path string all match up across all three files mentioned** diff --git a/guides/path_alias/path_alias.md b/guides/path_alias/path_alias.md new file mode 100644 index 00000000..e6c19d12 --- /dev/null +++ b/guides/path_alias/path_alias.md @@ -0,0 +1,41 @@ +# Path Aliases + +## Definition + +A path alias is a string mapped to a specific file/folder path that can be either relative or absolute. + +A path alias still represents the actual file/folder path and has the option of reaching to the subfolder(s) and subfile(s) of the path it replaces (if that path leads to a folder). + +## Rationale + +Writing the filepaths for import statements can be a big pain. An example of this is: + +- The folder **src/pages/EventDetailsPage/components/EventDetails** has an index.tsx file. This index file imports components Tag, Card and Button from **src/components**. +- This means that to import Tag, Card and Button from **src/components** to index.tsx without using path aliases, a developer would have to write an import statement like this: `import { Tag, Card, Button } from '../../../../components';`. +- This is rather cumbersome to write and unclean to look at, which will multiply even more if there are multiple files/folders to import from. + +To greatly alleviate this issue, path aliases were onboarded. They help make the import paths a lot less burdensome to write and to look at. + +## Available Path Aliases + +Currently in this codebase, there are 8 path aliases in total. They are: + +- `@Pages` - Points to **src/pages** +- `@Constants` - Points to **src/constants** +- `@SharedComponents` - Points to **src/components** +- `@Services` - Points to **src/services** +- `@HOCs` - Points to **src/HOCs** +- `@Images` - Points to **src/images** +- `@Contexts` - Points to **src/contexts** +- `@Config` - Points to **src/config** + +## Usage + +`import from '';` + +To use the example elaborated earlier in the _Rationale_ paragraph, using path aliases would give: +`import { Tag, Card, Button } from '@SharedComponents';` + +## Add More Path Aliases + +Refer to this [doc](./add_path_alias.md). From d64fdcd6090356be2737c50990bda556995b86e2 Mon Sep 17 00:00:00 2001 From: Godwin Pang Date: Sun, 30 Aug 2020 20:35:34 -0700 Subject: [PATCH 20/29] SignUp Page small screen fix. (#144) * Add autochanged tsconfig. * Make signup page responsive. * Drop minWidth on dropdowns to prevent overflow. Rebase commits from modal_refactor onto master --- tsconfig.json | 54 +++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 75325e2f..d4fca364 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,27 +1,27 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react", - "baseUrl": "." - }, - "include": [ - "src" - ], - "extends": "./tsconfig.paths.json" -} +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react", + "baseUrl": "." + }, + "include": [ + "src" + ], + "extends": "./tsconfig.paths.json" +} From c286a9272c38cb77c8c266ea34eecfd2d22b49f0 Mon Sep 17 00:00:00 2001 From: Thai <42761684+thaigillespie@users.noreply.github.com> Date: Fri, 4 Sep 2020 23:12:08 -0700 Subject: [PATCH 21/29] Event API integration complete (#145) * Got a lot of event api integration done, but not all. Still event edit page left, probably the trickiest one of them all. Other than that not sure if there is anything else that uses event services (if there are then we can refactor with the new event api functions). Just set up the user api functions file, have not done any integration for user api. * Added autocompletion component * Finished with integration for event edit page. * Added changes to address review of PR #145. * Removed duplicate config to turn off import/prefer-default-export. * Just for deploy preview. Rebase modal_factor's commits onto master --- .../OfficerNameAutocomplete/index.tsx | 4 -- .../EventStatusDropdownField/index.tsx | 26 +++++++++ .../EventTypeDropdownField/index.tsx | 26 +++++++++ tsconfig.json | 54 +++++++++---------- 4 files changed, 79 insertions(+), 31 deletions(-) create mode 100644 src/pages/EventEditPage/components/EventStatusDropdownField/index.tsx create mode 100644 src/pages/EventEditPage/components/EventTypeDropdownField/index.tsx diff --git a/src/components/autocomplete/OfficerNameAutocomplete/index.tsx b/src/components/autocomplete/OfficerNameAutocomplete/index.tsx index a12ecc2f..6ca7cdc4 100644 --- a/src/components/autocomplete/OfficerNameAutocomplete/index.tsx +++ b/src/components/autocomplete/OfficerNameAutocomplete/index.tsx @@ -46,7 +46,3 @@ export const OfficerNameAutocomplete = (props: OfficerAutocompleteProp) => { /> ); }; - -OfficerNameAutocomplete.defaultProps = { - fullWidth: false, -}; diff --git a/src/pages/EventEditPage/components/EventStatusDropdownField/index.tsx b/src/pages/EventEditPage/components/EventStatusDropdownField/index.tsx new file mode 100644 index 00000000..9650ff18 --- /dev/null +++ b/src/pages/EventEditPage/components/EventStatusDropdownField/index.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; + +import GenericDropdownField from '@SharedComponents/dropdowns/base'; +import { EventStatusEnum } from '@Services/EventService'; + +type EventStatusFieldProp = { + name: string; + label: string; + fullWidth: boolean; +}; + +const EventStatusDropdownField = (props: EventStatusFieldProp) => { + const { name, label, fullWidth } = props; + + return ( + + ); +}; + +export default EventStatusDropdownField; diff --git a/src/pages/EventEditPage/components/EventTypeDropdownField/index.tsx b/src/pages/EventEditPage/components/EventTypeDropdownField/index.tsx new file mode 100644 index 00000000..84c1ca41 --- /dev/null +++ b/src/pages/EventEditPage/components/EventTypeDropdownField/index.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; + +import GenericDropdownField from '@SharedComponents/dropdowns/base'; +import { EventTypeEnum } from '@Services/EventService'; + +type EventTypeFieldProp = { + name: string; + label: string; + fullWidth: boolean; +}; + +const EventTypeDropdownField = (props: EventTypeFieldProp) => { + const { name, label, fullWidth } = props; + + return ( + + ); +}; + +export default EventTypeDropdownField; diff --git a/tsconfig.json b/tsconfig.json index d4fca364..75325e2f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,27 +1,27 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react", - "baseUrl": "." - }, - "include": [ - "src" - ], - "extends": "./tsconfig.paths.json" -} +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react", + "baseUrl": "." + }, + "include": [ + "src" + ], + "extends": "./tsconfig.paths.json" +} From 7ca93cb2b4337c0bd0b51e66890ed9d8b167a3c4 Mon Sep 17 00:00:00 2001 From: Godwin Pang Date: Tue, 8 Sep 2020 12:46:55 -0700 Subject: [PATCH 22/29] Abstract away card component. (#154) * Add Card component with story. * Replace card usage throughout codebase. (EventDetails is messed up.) * Add prop passthrough to Card. * Fix EventDetails component. * Add title prop to card. * Refactor usage of CardHeader. * Change Card export syntax. Rebase the commits from modal_refactor to master for modal_refactor's PR --- tsconfig.json | 54 +++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 75325e2f..d4fca364 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,27 +1,27 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react", - "baseUrl": "." - }, - "include": [ - "src" - ], - "extends": "./tsconfig.paths.json" -} +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react", + "baseUrl": "." + }, + "include": [ + "src" + ], + "extends": "./tsconfig.paths.json" +} From 37399f6e40224986587bd551e73f325752fcbcc1 Mon Sep 17 00:00:00 2001 From: Thai <42761684+thaigillespie@users.noreply.github.com> Date: Tue, 8 Sep 2020 14:49:12 -0700 Subject: [PATCH 23/29] Added email verification to signup workflow, integrated auth api from backend. (#153) * Added email verification to signup workflow, integrated auth api from backend. Now frontend just needs to call createNewUser() from src/services/AuthService to start the signup workflow, then finishes it off with email verif. being handled in frontend. * Added try catch for calls to a firebase service in signup's handleSubmit. * Fixed duplicate imports in SignUpPage. Rebase modal_refactor's 3 commits onto master (in progress) --- src/services/api/.openapi-generator/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/api/.openapi-generator/VERSION b/src/services/api/.openapi-generator/VERSION index 8836c812..1a487e1a 100644 --- a/src/services/api/.openapi-generator/VERSION +++ b/src/services/api/.openapi-generator/VERSION @@ -1 +1 @@ -5.0.0-beta \ No newline at end of file +5.0.0-beta2 \ No newline at end of file From 3a66160da9c1889b2bb8d69f173a120f8ba44e33 Mon Sep 17 00:00:00 2001 From: Godwin Pang Date: Wed, 9 Sep 2020 15:17:12 -0700 Subject: [PATCH 24/29] Replace match.params as props with hooks (#155) * Install react-router types. * Migrate EventDetailsPage to useParam hook + typescript. * Migrate EventSignInForm to useParam hook + typescript. * Migrate EventRsvpPage to useParam hook + typescript. * Migrate EventEditPage to useParam hook + typescript. * Migrate ProfilePages to hooks + useParams hook. * Add yup typing. * Add @types to dependencies instead of devDep. @types should be in dev dep to prevent build size bloat, but somehow netlify builds fail when @types are not deps. * Modify CardProps to take in className. * Change pages to use new Card component. * Make className optional for cards. Rebase the commits of modal_refactor to master (in progress) --- package-lock.json | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index eb61e93d..67c4db29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6594,15 +6594,6 @@ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==" }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, "bl": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", @@ -11260,12 +11251,6 @@ } } }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "optional": true - }, "filefy": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/filefy/-/filefy-0.1.10.tgz", @@ -15125,7 +15110,6 @@ "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "optional": true, "requires": { - "bindings": "^1.5.0", "nan": "^2.12.1" } }, @@ -25650,7 +25634,6 @@ "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "optional": true, "requires": { - "bindings": "^1.5.0", "nan": "^2.12.1" } }, @@ -26238,7 +26221,6 @@ "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "optional": true, "requires": { - "bindings": "^1.5.0", "nan": "^2.12.1" } }, From 2723836d086d9a6fd08443628142b6903c10d6d0 Mon Sep 17 00:00:00 2001 From: Thai <42761684+thaigillespie@users.noreply.github.com> Date: Mon, 14 Sep 2020 21:35:03 -0700 Subject: [PATCH 25/29] Added create event button, page and form (#162) * Tsconfig changes again : ( * Added create event button, page and form. Rebase modal_refactor's commits onto master branch (in progress) --- .../OfficerNameAutocomplete/index.tsx | 4 ++ .../EventStatusDropdownField/index.tsx | 26 --------- .../EventTypeDropdownField/index.tsx | 26 --------- tsconfig.json | 54 +++++++++---------- 4 files changed, 31 insertions(+), 79 deletions(-) delete mode 100644 src/pages/EventEditPage/components/EventStatusDropdownField/index.tsx delete mode 100644 src/pages/EventEditPage/components/EventTypeDropdownField/index.tsx diff --git a/src/components/autocomplete/OfficerNameAutocomplete/index.tsx b/src/components/autocomplete/OfficerNameAutocomplete/index.tsx index 6ca7cdc4..a12ecc2f 100644 --- a/src/components/autocomplete/OfficerNameAutocomplete/index.tsx +++ b/src/components/autocomplete/OfficerNameAutocomplete/index.tsx @@ -46,3 +46,7 @@ export const OfficerNameAutocomplete = (props: OfficerAutocompleteProp) => { /> ); }; + +OfficerNameAutocomplete.defaultProps = { + fullWidth: false, +}; diff --git a/src/pages/EventEditPage/components/EventStatusDropdownField/index.tsx b/src/pages/EventEditPage/components/EventStatusDropdownField/index.tsx deleted file mode 100644 index 9650ff18..00000000 --- a/src/pages/EventEditPage/components/EventStatusDropdownField/index.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import * as React from 'react'; - -import GenericDropdownField from '@SharedComponents/dropdowns/base'; -import { EventStatusEnum } from '@Services/EventService'; - -type EventStatusFieldProp = { - name: string; - label: string; - fullWidth: boolean; -}; - -const EventStatusDropdownField = (props: EventStatusFieldProp) => { - const { name, label, fullWidth } = props; - - return ( - - ); -}; - -export default EventStatusDropdownField; diff --git a/src/pages/EventEditPage/components/EventTypeDropdownField/index.tsx b/src/pages/EventEditPage/components/EventTypeDropdownField/index.tsx deleted file mode 100644 index 84c1ca41..00000000 --- a/src/pages/EventEditPage/components/EventTypeDropdownField/index.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import * as React from 'react'; - -import GenericDropdownField from '@SharedComponents/dropdowns/base'; -import { EventTypeEnum } from '@Services/EventService'; - -type EventTypeFieldProp = { - name: string; - label: string; - fullWidth: boolean; -}; - -const EventTypeDropdownField = (props: EventTypeFieldProp) => { - const { name, label, fullWidth } = props; - - return ( - - ); -}; - -export default EventTypeDropdownField; diff --git a/tsconfig.json b/tsconfig.json index d4fca364..75325e2f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,27 +1,27 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react", - "baseUrl": "." - }, - "include": [ - "src" - ], - "extends": "./tsconfig.paths.json" -} +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react", + "baseUrl": "." + }, + "include": [ + "src" + ], + "extends": "./tsconfig.paths.json" +} From f3504152c168ae908827d7e98892765ec6632f91 Mon Sep 17 00:00:00 2001 From: Thai <42761684+thaigillespie@users.noreply.github.com> Date: Wed, 16 Sep 2020 16:59:14 -0700 Subject: [PATCH 26/29] Create Event now works properly and as intended. (#165) * Tsconfig changes again : ( * Fixed merge issue * Create Event now works properly and as intended. After creating an event, the browser redirects to the newly created event's EventDetails page. Note that fbURL and canvaURL both have to either be empty or filled out with a proper URL string, otherwise a status code 400 will be sent from the backend server. * Added url validation to create event form's schema's fbURL + canvaURL. * Changed process.env in src/index.js to using config. --- src/services/api/.openapi-generator/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/api/.openapi-generator/VERSION b/src/services/api/.openapi-generator/VERSION index 1a487e1a..8836c812 100644 --- a/src/services/api/.openapi-generator/VERSION +++ b/src/services/api/.openapi-generator/VERSION @@ -1 +1 @@ -5.0.0-beta2 \ No newline at end of file +5.0.0-beta \ No newline at end of file From ae8c0d6e94e791ba44f72661519df4251c3c8a64 Mon Sep 17 00:00:00 2001 From: Thai Date: Thu, 10 Sep 2020 13:00:59 -0700 Subject: [PATCH 27/29] Refactored modals to using TS and simplified code for modals. Also added a story for modals to storybook. --- src/components/index.js | 4 +- src/components/modals/base/BaseModal.js | 48 ------- src/components/modals/base/BaseModal.tsx | 36 ++++++ src/components/modals/base/ButtonWithModal.js | 43 ------- .../modals/base/ButtonWithModal.tsx | 74 +++++++++++ src/components/modals/index.js | 119 ------------------ src/components/modals/index.tsx | 56 +++++++++ src/components/modals/modals.stories.tsx | 62 +++++++++ .../components/DeleteEditButtons/index.js | 32 +++-- 9 files changed, 248 insertions(+), 226 deletions(-) delete mode 100644 src/components/modals/base/BaseModal.js create mode 100644 src/components/modals/base/BaseModal.tsx delete mode 100644 src/components/modals/base/ButtonWithModal.js create mode 100644 src/components/modals/base/ButtonWithModal.tsx delete mode 100644 src/components/modals/index.js create mode 100644 src/components/modals/index.tsx create mode 100644 src/components/modals/modals.stories.tsx diff --git a/src/components/index.js b/src/components/index.js index 7f40eebb..6b986481 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -9,7 +9,6 @@ import { import FormLayout from './FormLayout'; import InputField from './InputField'; import Loading from './Loading'; -import { ButtonWithConfirmationModal, ButtonWithAlertModal } from './modals'; import { Card } from './cards'; import { PublicPageLayout } from './layouts'; import NavBar from './NavBar'; @@ -17,6 +16,7 @@ import Table from './Table'; import Tags from './Tags'; export { OfficerNameAutocomplete } from './autocomplete'; +export { ButtonWithConfirmationModal, ButtonWithAlertModal } from './modals'; export { Button, @@ -29,8 +29,6 @@ export { FormLayout, InputField, Loading, - ButtonWithConfirmationModal, - ButtonWithAlertModal, NavBar, Table, Tags, diff --git a/src/components/modals/base/BaseModal.js b/src/components/modals/base/BaseModal.js deleted file mode 100644 index 6e23b9ac..00000000 --- a/src/components/modals/base/BaseModal.js +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { - Dialog, - DialogActions, - DialogContent, - DialogContentText, - DialogTitle, -} from '@material-ui/core'; - -const BaseModal = ({ title, contentText, open, handleClose, children }) => { - const closeModalWithCallback = actionButtonFunc => { - handleClose(); - - if (actionButtonFunc != null) { - actionButtonFunc(); - } - }; - - return ( - - {title} - - - {contentText} - - - - {children(actionButtonFunc => closeModalWithCallback(actionButtonFunc))} - - - ); -}; - -BaseModal.propTypes = { - title: PropTypes.string.isRequired, - contentText: PropTypes.string.isRequired, - open: PropTypes.bool.isRequired, - handleClose: PropTypes.func.isRequired, - children: PropTypes.func, -}; - -BaseModal.defaultProps = { - // eslint-disable-next-line @typescript-eslint/no-empty-function - children: () => {}, -}; - -export default BaseModal; diff --git a/src/components/modals/base/BaseModal.tsx b/src/components/modals/base/BaseModal.tsx new file mode 100644 index 00000000..6184b82f --- /dev/null +++ b/src/components/modals/base/BaseModal.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, +} from '@material-ui/core'; + +interface BaseModalProps { + title: string; + contentText: string; + open: boolean; + handleClose: () => void; + children: JSX.Element[]; +} + +export const BaseModal = ({ + title, + contentText, + open, + handleClose, + children, +}: BaseModalProps) => { + return ( + + {title} + + + {contentText} + + + {children} + + ); +}; diff --git a/src/components/modals/base/ButtonWithModal.js b/src/components/modals/base/ButtonWithModal.js deleted file mode 100644 index cfa39f1a..00000000 --- a/src/components/modals/base/ButtonWithModal.js +++ /dev/null @@ -1,43 +0,0 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; - -import Button from '../../buttons'; - -import BaseModal from './BaseModal'; - -const ButtonWithModal = props => { - const [open, setOpen] = useState(false); - - const { title, contentText, children, name, ...otherProps } = props; - - return ( - <> - - - setOpen(false)} - > - {children} - - - ); -}; - -ButtonWithModal.propTypes = { - title: PropTypes.string.isRequired, - contentText: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - children: PropTypes.func, -}; - -ButtonWithModal.defaultProps = { - /* eslint-disable-next-line @typescript-eslint/no-empty-function */ - children: () => {}, -}; - -export default ButtonWithModal; diff --git a/src/components/modals/base/ButtonWithModal.tsx b/src/components/modals/base/ButtonWithModal.tsx new file mode 100644 index 00000000..20b0f4fe --- /dev/null +++ b/src/components/modals/base/ButtonWithModal.tsx @@ -0,0 +1,74 @@ +import React, { useState } from 'react'; +import { useHistory } from 'react-router'; + +import { Button, ButtonProps } from '../../buttons/Button'; + +import { BaseModal } from './BaseModal'; + +// className for MUI can be put in styleProps, since it is of type ButtonProps +export interface ActionButton { + buttonName: string; + actionFunc?: (...params: any[]) => any; + styleProps?: ButtonProps; + urlToNavigate?: string; +} + +export interface ButtonWithModalProps { + modalTitle: string; + modalContentText: string; + name: string; + actionButtonList: ActionButton[]; + openButtonStyleProps?: ButtonProps; +} + +export const ButtonWithModal = ({ + modalTitle, + modalContentText, + name, + actionButtonList, + openButtonStyleProps = {}, +}: ButtonWithModalProps) => { + const [open, setOpen] = useState(false); + const history = useHistory(); + + return ( + <> + + + setOpen(false)} + > + {actionButtonList.map((actionButtonProps: ActionButton) => { + const { + buttonName, + actionFunc = () => { + return null; + }, + styleProps = {}, + urlToNavigate, + } = actionButtonProps; + + const onClickFunction = () => { + setOpen(false); + actionFunc(); + + if (urlToNavigate !== undefined) { + history.push(urlToNavigate); + } + }; + + return ( + + ); + })} + + + ); +}; diff --git a/src/components/modals/index.js b/src/components/modals/index.js deleted file mode 100644 index 43ae2bc7..00000000 --- a/src/components/modals/index.js +++ /dev/null @@ -1,119 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import Button from '../buttons'; - -import ButtonWithModal from './base/ButtonWithModal'; - -export const ButtonWithConfirmationModal = ({ - title, - contentText, - name, - confirmButtonProps, - cancelButtonProps, - ...otherProps -}) => { - const { name: cancelName, ...otherCancelProps } = cancelButtonProps; - const { - name: confirmName, - onClick: confirmOnClick, - ...otherConfirmProps - } = confirmButtonProps; - - return ( - - {onClickHOF => ( - <> - - - - - )} - - ); -}; - -ButtonWithConfirmationModal.propTypes = { - title: PropTypes.string.isRequired, - contentText: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - confirmButtonProps: PropTypes.shape({ - name: PropTypes.string.isRequired, - onClick: PropTypes.func, - otherConfirmProps: PropTypes.object, - }), - cancelButtonProps: PropTypes.shape({ - name: PropTypes.string.isRequired, - otherCancelProps: PropTypes.object, - }), -}; - -ButtonWithConfirmationModal.defaultProps = { - confirmButtonProps: { - /* eslint-disable-next-line @typescript-eslint/no-empty-function */ - onClick: () => {}, - otherConfirmProps: {}, - }, - cancelButtonProps: { - /* eslint-disable-next-line @typescript-eslint/no-empty-function */ - onClick: () => {}, - otherCancelProps: {}, - }, -}; - -export const ButtonWithAlertModal = ({ - title, - contentText, - name, - closeButtonProps, - ...otherProps -}) => { - const { name: closeName, ...closeOtherProps } = closeButtonProps; - - return ( - - {onClickHOF => ( - <> - - - )} - - ); -}; - -ButtonWithAlertModal.propTypes = { - title: PropTypes.string.isRequired, - contentText: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - closeButtonProps: PropTypes.shape({ - name: PropTypes.string.isRequired, - otherProps: PropTypes.object, - }), -}; - -ButtonWithAlertModal.defaultProps = { - closeButtonProps: { - /* eslint-disable-next-line @typescript-eslint/no-empty-function */ - onClick: () => {}, - otherProps: {}, - }, -}; diff --git a/src/components/modals/index.tsx b/src/components/modals/index.tsx new file mode 100644 index 00000000..5eaabfb2 --- /dev/null +++ b/src/components/modals/index.tsx @@ -0,0 +1,56 @@ +import React from 'react'; + +import { ButtonProps } from '../buttons/Button'; + +import { ButtonWithModal, ActionButton } from './base/ButtonWithModal'; + +export interface ButtonConfirmationModalProps { + modalTitle: string; + modalContentText: string; + name: string; + confirmButtonProps: ActionButton; + cancelButtonProps: ActionButton; + openButtonStyleProps?: ButtonProps; +} + +export const ButtonWithConfirmationModal = ({ + modalTitle, + modalContentText, + name, + confirmButtonProps, + cancelButtonProps, + openButtonStyleProps = {}, +}: ButtonConfirmationModalProps) => { + return ( + + ); +}; + +export interface ButtonAlertModalProps { + modalTitle: string; + modalContentText: string; + name: string; + closeButtonProps: ActionButton; +} + +export const ButtonWithAlertModal = ({ + modalTitle, + modalContentText, + name, + closeButtonProps, +}: ButtonAlertModalProps) => { + return ( + + ); +}; diff --git a/src/components/modals/modals.stories.tsx b/src/components/modals/modals.stories.tsx new file mode 100644 index 00000000..1ca30889 --- /dev/null +++ b/src/components/modals/modals.stories.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { Story, Meta } from '@storybook/react'; + +import { + ButtonWithModal, + ButtonWithModalProps, + ActionButton, +} from './base/ButtonWithModal'; + +export default { + title: 'Modals/Button With Modal', + component: ButtonWithModal, +} as Meta; + +const Template: Story = args => ( + +); + +const confirmButtonProps: ActionButton = { + buttonName: 'Confirm', + actionFunc: () => alert('You just clicked the confirm button!'), + styleProps: { + primary: true, + positive: true, + }, +}; + +const cancelButtonProps: ActionButton = { + buttonName: 'Cancel', + styleProps: { + primary: true, + negative: true, + }, +}; + +export const ButtonWithConfirmationModal = Template.bind({}); +ButtonWithConfirmationModal.args = { + modalTitle: 'Sample Button With Confirmation Modal', + modalContentText: 'Put any text you want here.', + name: 'Click on me!', + actionButtonList: [confirmButtonProps, cancelButtonProps], + openButtonStyleProps: { + primary: true, + positive: true, + }, +}; + +const closeButtonProps: ActionButton = { + buttonName: 'Close', +}; + +export const ButtonWithAlertModal = Template.bind({}); +ButtonWithAlertModal.args = { + modalTitle: 'Sample Button With Alert Modal', + modalContentText: 'Put any text you want here.', + name: 'Click on me!', + actionButtonList: [closeButtonProps], + openButtonStyleProps: { + primary: true, + negative: true, + }, +}; diff --git a/src/pages/EventDetailsPage/components/DeleteEditButtons/index.js b/src/pages/EventDetailsPage/components/DeleteEditButtons/index.js index ca32e897..a5779887 100644 --- a/src/pages/EventDetailsPage/components/DeleteEditButtons/index.js +++ b/src/pages/EventDetailsPage/components/DeleteEditButtons/index.js @@ -29,30 +29,36 @@ const DeleteEditButtons = props => { }; const confirmButtonProps = { - name: 'Yes', - onClick: handleConfirmDelete, - to: ROUTES.CALENDAR, - component: Link, - positive: true, + buttonName: 'Yes', + actionFunc: handleConfirmDelete, + styleProps: { + primary: true, + positive: true, + }, + urlToNavigate: ROUTES.CALENDAR, }; const cancelButtonProps = { - name: 'No', - positive: true, + buttonName: 'No', + styleProps: { + primary: true, + negative: true, + }, }; return (
} - primary - negative + openButtonStyleProps={{ + startIcon: , + primary: true, + negative: true, + }} /> + + + + ); +}; diff --git a/src/components/modals/ModalWithActionButtons.tsx b/src/components/modals/ModalWithActionButtons.tsx new file mode 100644 index 00000000..88a474af --- /dev/null +++ b/src/components/modals/ModalWithActionButtons.tsx @@ -0,0 +1,56 @@ +import React from 'react'; + +import { Button, ButtonProps } from '../buttons/Button'; + +import { BaseModal } from './BaseModal'; + +export interface ActionButton extends ButtonProps { + actionCallback?: (...params: any[]) => any; + closeModalOnClick?: boolean; +} + +export interface ModalProps { + title: string; + contentText: string; + open: boolean; + handleClose: () => void; +} + +interface ModalActionButtonProps { + modalProps: ModalProps; + actionButtonList: ActionButton[]; +} + +export const ModalWithActionButtons = ({ + modalProps, + actionButtonList, +}: ModalActionButtonProps) => { + return ( + + {actionButtonList.map((buttonProps: ActionButton) => { + const { + name, + closeModalOnClick = true, + actionCallback = () => { + return null; + }, + ...otherProps + } = buttonProps; + + const onClickFunction = () => { + if (closeModalOnClick) { + modalProps.handleClose(); + } + + actionCallback(); + }; + + return ( + + ); + })} + + ); +}; diff --git a/src/components/modals/base/ButtonWithModal.tsx b/src/components/modals/base/ButtonWithModal.tsx deleted file mode 100644 index 20b0f4fe..00000000 --- a/src/components/modals/base/ButtonWithModal.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React, { useState } from 'react'; -import { useHistory } from 'react-router'; - -import { Button, ButtonProps } from '../../buttons/Button'; - -import { BaseModal } from './BaseModal'; - -// className for MUI can be put in styleProps, since it is of type ButtonProps -export interface ActionButton { - buttonName: string; - actionFunc?: (...params: any[]) => any; - styleProps?: ButtonProps; - urlToNavigate?: string; -} - -export interface ButtonWithModalProps { - modalTitle: string; - modalContentText: string; - name: string; - actionButtonList: ActionButton[]; - openButtonStyleProps?: ButtonProps; -} - -export const ButtonWithModal = ({ - modalTitle, - modalContentText, - name, - actionButtonList, - openButtonStyleProps = {}, -}: ButtonWithModalProps) => { - const [open, setOpen] = useState(false); - const history = useHistory(); - - return ( - <> - - - setOpen(false)} - > - {actionButtonList.map((actionButtonProps: ActionButton) => { - const { - buttonName, - actionFunc = () => { - return null; - }, - styleProps = {}, - urlToNavigate, - } = actionButtonProps; - - const onClickFunction = () => { - setOpen(false); - actionFunc(); - - if (urlToNavigate !== undefined) { - history.push(urlToNavigate); - } - }; - - return ( - - ); - })} - - - ); -}; diff --git a/src/components/modals/index.tsx b/src/components/modals/index.tsx index 5eaabfb2..16e711f6 100644 --- a/src/components/modals/index.tsx +++ b/src/components/modals/index.tsx @@ -1,8 +1,7 @@ import React from 'react'; -import { ButtonProps } from '../buttons/Button'; - -import { ButtonWithModal, ActionButton } from './base/ButtonWithModal'; +import { ButtonWithModal } from './ButtonWithModal'; +import { ActionButton } from './ModalWithActionButtons'; export interface ButtonConfirmationModalProps { modalTitle: string; @@ -10,7 +9,6 @@ export interface ButtonConfirmationModalProps { name: string; confirmButtonProps: ActionButton; cancelButtonProps: ActionButton; - openButtonStyleProps?: ButtonProps; } export const ButtonWithConfirmationModal = ({ @@ -19,15 +17,15 @@ export const ButtonWithConfirmationModal = ({ name, confirmButtonProps, cancelButtonProps, - openButtonStyleProps = {}, + ...otherOpenButtonProps }: ButtonConfirmationModalProps) => { return ( ); }; @@ -44,6 +42,7 @@ export const ButtonWithAlertModal = ({ modalContentText, name, closeButtonProps, + ...otherOpenButtonProps }: ButtonAlertModalProps) => { return ( ); }; diff --git a/src/components/modals/modals.stories.tsx b/src/components/modals/modals.stories.tsx index 1ca30889..8fc1d29b 100644 --- a/src/components/modals/modals.stories.tsx +++ b/src/components/modals/modals.stories.tsx @@ -1,11 +1,8 @@ import React from 'react'; import { Story, Meta } from '@storybook/react'; -import { - ButtonWithModal, - ButtonWithModalProps, - ActionButton, -} from './base/ButtonWithModal'; +import { ButtonWithModal, ButtonWithModalProps } from './ButtonWithModal'; +import { ActionButton } from './ModalWithActionButtons'; export default { title: 'Modals/Button With Modal', @@ -17,20 +14,16 @@ const Template: Story = args => ( ); const confirmButtonProps: ActionButton = { - buttonName: 'Confirm', - actionFunc: () => alert('You just clicked the confirm button!'), - styleProps: { - primary: true, - positive: true, - }, + name: 'Confirm', + actionCallback: () => alert('You just clicked the confirm button!'), + primary: true, + positive: true, }; const cancelButtonProps: ActionButton = { - buttonName: 'Cancel', - styleProps: { - primary: true, - negative: true, - }, + name: 'Cancel', + primary: true, + negative: true, }; export const ButtonWithConfirmationModal = Template.bind({}); @@ -38,15 +31,13 @@ ButtonWithConfirmationModal.args = { modalTitle: 'Sample Button With Confirmation Modal', modalContentText: 'Put any text you want here.', name: 'Click on me!', - actionButtonList: [confirmButtonProps, cancelButtonProps], - openButtonStyleProps: { - primary: true, - positive: true, - }, + actionButtonList: [cancelButtonProps, confirmButtonProps], + primary: true, + positive: true, }; const closeButtonProps: ActionButton = { - buttonName: 'Close', + name: 'Close', }; export const ButtonWithAlertModal = Template.bind({}); @@ -55,8 +46,6 @@ ButtonWithAlertModal.args = { modalContentText: 'Put any text you want here.', name: 'Click on me!', actionButtonList: [closeButtonProps], - openButtonStyleProps: { - primary: true, - negative: true, - }, + primary: true, + negative: true, }; diff --git a/src/pages/EventDetailsPage/components/DeleteEditButtons/index.js b/src/pages/EventDetailsPage/components/DeleteEditButtons/index.js index a5779887..d76d3a1d 100644 --- a/src/pages/EventDetailsPage/components/DeleteEditButtons/index.js +++ b/src/pages/EventDetailsPage/components/DeleteEditButtons/index.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { useHistory } from 'react-router'; import { Link } from 'react-router-dom'; import { withStyles } from '@material-ui/core/styles'; import DeleteIcon from '@material-ui/icons/Delete'; @@ -13,6 +14,7 @@ import { deleteEvent } from '@Services/EventService'; const DeleteEditButtons = props => { const { classes, eventId } = props; + const history = useHistory(); const handleDeleteEvent = eventToDeleteId => { deleteEvent(eventToDeleteId) @@ -29,21 +31,19 @@ const DeleteEditButtons = props => { }; const confirmButtonProps = { - buttonName: 'Yes', - actionFunc: handleConfirmDelete, - styleProps: { - primary: true, - positive: true, + name: 'Yes', + actionCallback: () => { + handleConfirmDelete(); + history.push(ROUTES.CALENDAR); }, - urlToNavigate: ROUTES.CALENDAR, + primary: true, + positive: true, }; const cancelButtonProps = { - buttonName: 'No', - styleProps: { - primary: true, - negative: true, - }, + name: 'No', + primary: true, + positive: true, }; return ( @@ -51,14 +51,12 @@ const DeleteEditButtons = props => { , - primary: true, - negative: true, - }} + startIcon={} + primary + negative /> ); diff --git a/src/components/modals/ModalWithActionButtons.tsx b/src/components/modals/ModalWithActionButtons.tsx index 88a474af..fdb52aca 100644 --- a/src/components/modals/ModalWithActionButtons.tsx +++ b/src/components/modals/ModalWithActionButtons.tsx @@ -4,45 +4,35 @@ import { Button, ButtonProps } from '../buttons/Button'; import { BaseModal } from './BaseModal'; -export interface ActionButton extends ButtonProps { - actionCallback?: (...params: any[]) => any; - closeModalOnClick?: boolean; -} - export interface ModalProps { title: string; - contentText: string; + content: string; open: boolean; handleClose: () => void; } -interface ModalActionButtonProps { +interface ModalWithActionButtonProps { modalProps: ModalProps; - actionButtonList: ActionButton[]; + actionButtonPropsList: ButtonProps[]; } export const ModalWithActionButtons = ({ modalProps, - actionButtonList, -}: ModalActionButtonProps) => { + actionButtonPropsList, +}: ModalWithActionButtonProps) => { return ( - {actionButtonList.map((buttonProps: ActionButton) => { - const { - name, - closeModalOnClick = true, - actionCallback = () => { - return null; - }, - ...otherProps - } = buttonProps; - - const onClickFunction = () => { - if (closeModalOnClick) { - modalProps.handleClose(); + {actionButtonPropsList.map((buttonProps: ButtonProps) => { + const { name, onClick, ...otherProps } = buttonProps; + + const onClickFunction = ( + event: React.MouseEvent + ) => { + if (onClick !== undefined) { + onClick(event); } - actionCallback(); + modalProps.handleClose(); }; return ( diff --git a/src/components/modals/README.md b/src/components/modals/README.md new file mode 100644 index 00000000..3b4da6cf --- /dev/null +++ b/src/components/modals/README.md @@ -0,0 +1,8 @@ +The component abstraction level for for modals goes (from lowest to highest level): + +1. BaseModal +2. ModalWithActionButtons +3. ButtonWithModal +4. ButtonWithAlertModal, ButtonWithConfirmationModal + +The (i+1)-th component depends on the i-th component. diff --git a/src/components/modals/index.tsx b/src/components/modals/index.tsx index 16e711f6..6b1c6218 100644 --- a/src/components/modals/index.tsx +++ b/src/components/modals/index.tsx @@ -1,56 +1,2 @@ -import React from 'react'; - -import { ButtonWithModal } from './ButtonWithModal'; -import { ActionButton } from './ModalWithActionButtons'; - -export interface ButtonConfirmationModalProps { - modalTitle: string; - modalContentText: string; - name: string; - confirmButtonProps: ActionButton; - cancelButtonProps: ActionButton; -} - -export const ButtonWithConfirmationModal = ({ - modalTitle, - modalContentText, - name, - confirmButtonProps, - cancelButtonProps, - ...otherOpenButtonProps -}: ButtonConfirmationModalProps) => { - return ( - - ); -}; - -export interface ButtonAlertModalProps { - modalTitle: string; - modalContentText: string; - name: string; - closeButtonProps: ActionButton; -} - -export const ButtonWithAlertModal = ({ - modalTitle, - modalContentText, - name, - closeButtonProps, - ...otherOpenButtonProps -}: ButtonAlertModalProps) => { - return ( - - ); -}; +export { ButtonWithConfirmationModal } from './ButtonWithConfirmationModal'; +export { ButtonWithAlertModal } from './ButtonWithAlertModal'; diff --git a/src/components/modals/modals.stories.tsx b/src/components/modals/modals.stories.tsx index 8fc1d29b..4094df29 100644 --- a/src/components/modals/modals.stories.tsx +++ b/src/components/modals/modals.stories.tsx @@ -1,8 +1,9 @@ import React from 'react'; import { Story, Meta } from '@storybook/react'; +import { ButtonProps } from '../buttons/Button'; + import { ButtonWithModal, ButtonWithModalProps } from './ButtonWithModal'; -import { ActionButton } from './ModalWithActionButtons'; export default { title: 'Modals/Button With Modal', @@ -13,14 +14,14 @@ const Template: Story = args => ( ); -const confirmButtonProps: ActionButton = { +const confirmButtonProps: ButtonProps = { name: 'Confirm', - actionCallback: () => alert('You just clicked the confirm button!'), + onClick: () => alert('You just clicked the confirm button!'), primary: true, positive: true, }; -const cancelButtonProps: ActionButton = { +const cancelButtonProps: ButtonProps = { name: 'Cancel', primary: true, negative: true, @@ -28,24 +29,28 @@ const cancelButtonProps: ActionButton = { export const ButtonWithConfirmationModal = Template.bind({}); ButtonWithConfirmationModal.args = { - modalTitle: 'Sample Button With Confirmation Modal', - modalContentText: 'Put any text you want here.', + modalTitleContentProps: { + title: 'Sample Button With Confirmation Modal', + content: 'Put any text you want here.', + }, name: 'Click on me!', - actionButtonList: [cancelButtonProps, confirmButtonProps], + actionButtonPropsList: [cancelButtonProps, confirmButtonProps], primary: true, positive: true, }; -const closeButtonProps: ActionButton = { +const closeButtonProps: ButtonProps = { name: 'Close', }; export const ButtonWithAlertModal = Template.bind({}); ButtonWithAlertModal.args = { - modalTitle: 'Sample Button With Alert Modal', - modalContentText: 'Put any text you want here.', + modalTitleContentProps: { + title: 'Sample Button With Alert Modal', + content: 'Put any text you want here.', + }, name: 'Click on me!', - actionButtonList: [closeButtonProps], + actionButtonPropsList: [closeButtonProps], primary: true, negative: true, }; diff --git a/src/pages/EventDetailsPage/components/DeleteEditButtons/index.js b/src/pages/EventDetailsPage/components/DeleteEditButtons/index.js index d76d3a1d..0da06907 100644 --- a/src/pages/EventDetailsPage/components/DeleteEditButtons/index.js +++ b/src/pages/EventDetailsPage/components/DeleteEditButtons/index.js @@ -32,7 +32,7 @@ const DeleteEditButtons = props => { const confirmButtonProps = { name: 'Yes', - actionCallback: () => { + onClick: () => { handleConfirmDelete(); history.push(ROUTES.CALENDAR); }, @@ -49,11 +49,13 @@ const DeleteEditButtons = props => { return (
} primary negative