Added updating of display name to profile. Added employees table.

This commit is contained in:
Patrick Fic
2020-02-04 12:42:20 -08:00
parent 55cec20914
commit de6ca52fb8
33 changed files with 514 additions and 39 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.6.1"> <babeledit_project be_version="2.6.1" version="1.2">
<!-- <!--
BabelEdit project file BabelEdit project file
@@ -4438,6 +4438,32 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node>
<name>fields</name>
<children>
<concept_node>
<name>displayname</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>

View File

@@ -1,32 +1,34 @@
import React, { useState } from "react"; import { Layout } from "antd";
import ChatWindowComponent from "./chat-window.component"; import React from "react";
import { Button } from "antd";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectCurrentUser } from "../../redux/user/user.selectors"; import { toggleChatVisible } from "../../redux/messaging/messaging.actions";
import { selectChatVisible } from "../../redux/messaging/messaging.selectors";
import ChatWindowComponent from "./chat-window.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser chatVisible: selectChatVisible
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
// signOutStart: () => dispatch(signOutStart()) toggleChatVisible: () => dispatch(toggleChatVisible())
}); });
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(function ChatWindowContainer() { )(function ChatWindowContainer({ chatVisible, toggleChatVisible }) {
const [visible, setVisible] = useState(false);
return ( return (
<div> <Layout.Sider
<Button onClick={() => setVisible(!visible)}>Drawer!</Button> collapsible
<ChatWindowComponent defaultCollapsed
mask={false} theme="light"
maskClosable={false} collapsedWidth={0}
visible={visible} width={300}
zIndex={0} collapsed={!chatVisible}
/> onCollapse={(collapsed, type) => toggleChatVisible()}
</div> >
<ChatWindowComponent mask={false} maskClosable={false} zIndex={0} />
</Layout.Sider>
); );
}); });

View File

@@ -1,14 +1,15 @@
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import ProfileMyComponent from "../profile-my/profile-my.component";
export default function ProfileContent({ sidebarSelection }) { export default function ProfileContent({ sidebarSelection }) {
const { t } = useTranslation(); const { t } = useTranslation();
switch (sidebarSelection.key) { switch (sidebarSelection.key) {
case "profile": case "profile":
return <div>Profile stuff</div>; return <ProfileMyComponent />;
case "shop": case "shops":
return <div>Shop stuff</div>; return <div>Shop stuff</div>;
default: default:
return ( return (

View File

@@ -0,0 +1,67 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import { Form, Input, notification, Button } from "antd";
import { updateUserDetails } from "../../redux/user/user.actions";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser
});
const mapDispatchToProps = dispatch => ({
updateUserDetails: userDetails => dispatch(updateUserDetails(userDetails))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(
Form.create({ name: "ProfileMyComponentForm" })(function ProfileMyComponent({
currentUser,
form,
updateUserDetails
}) {
const { isFieldsTouched, resetFields, getFieldDecorator } = form;
const { t } = useTranslation();
const handleSubmit = e => {
e.preventDefault();
form.validateFieldsAndScroll((err, values) => {
if (err) {
notification["error"]({
message: t("jobs.errors.validationtitle"),
description: t("jobs.errors.validation")
});
}
if (!err) {
console.log("values", values);
updateUserDetails({ displayName: values.displayname });
}
});
};
return (
<div>
<AlertComponent message={"hi"} />
<Form onSubmit={handleSubmit} autoComplete={"no"}>
<Form.Item label={t("user.fields.displayname")}>
{getFieldDecorator("displayname", {
initialValue: currentUser.displayName,
rules: [{ required: true }]
})(<Input name="displayname" />)}
</Form.Item>
<Button
type="primary"
key="submit"
htmlType="submit"
onClick={handleSubmit}
>
Save
</Button>
</Form>
</div>
);
})
);

View File

@@ -34,6 +34,7 @@ export const createUserProfileDocument = async (userAuth, additionalData) => {
}; };
export const auth = firebase.auth(); export const auth = firebase.auth();
export const firestore = firebase.firestore(); export const firestore = firebase.firestore();
const provider = new firebase.auth.GoogleAuthProvider(); const provider = new firebase.auth.GoogleAuthProvider();
@@ -49,3 +50,15 @@ export const getCurrentUser = () => {
}, reject); }, reject);
}); });
}; };
export const updateCurrentUser = userDetails => {
return new Promise((resolve, reject) => {
const unsubscribe = auth.onAuthStateChanged(userAuth => {
console.log("userDetails", userDetails);
userAuth.updateProfile(userDetails).then(r => {
unsubscribe();
resolve(userAuth);
});
}, reject);
});
};

View File

@@ -42,8 +42,9 @@ const errorLink = onError(
authorization: token ? `Bearer ${token}` : "" authorization: token ? `Bearer ${token}` : ""
} }
}); });
console.log("forward", forward);
return forward(operation); console.log("operation", operation);
return forward(operation).subscribe();
// return new Observable(observer => { // return new Observable(observer => {
// const subscriber = { // const subscriber = {

View File

@@ -2,7 +2,6 @@ import { BackTop, Layout } from "antd";
import React, { lazy, Suspense, useEffect } from "react"; import React, { lazy, Suspense, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Route } from "react-router"; import { Route } from "react-router";
import ChatWindowContainer from "../../components/chat-window/chat-window.container";
import ErrorBoundary from "../../components/error-boundary/error-boundary.component"; import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
import FooterComponent from "../../components/footer/footer.component"; import FooterComponent from "../../components/footer/footer.component";
//Component Imports //Component Imports
@@ -25,6 +24,9 @@ const JobsDocumentsPage = lazy(() =>
const JobsAvailablePage = lazy(() => const JobsAvailablePage = lazy(() =>
import("../jobs-available/jobs-available.page.container") import("../jobs-available/jobs-available.page.container")
); );
const ChatWindowContainer = lazy(() =>
import("../../components/chat-window/chat-window.container")
);
const { Header, Content, Footer, Sider } = Layout; const { Header, Content, Footer, Sider } = Layout;
//This page will handle all routing for the entire application. //This page will handle all routing for the entire application.
@@ -41,17 +43,9 @@ export default function Manage({ match }) {
<HeaderContainer /> <HeaderContainer />
</Header> </Header>
<Layout> <Layout>
<Sider <ChatWindowContainer />
collapsible
defaultCollapsed
theme="light"
collapsedWidth={0}
width={300}
>
<chatWindowContainer />
</Sider>
<Content className="content-container" style={{ margin: "50px" }}> <Content className="content-container" style={{ margin: "0px" }}>
<ErrorBoundary> <ErrorBoundary>
<Suspense <Suspense
fallback={ fallback={

View File

@@ -0,0 +1,7 @@
import MessagingActionTypes from './messaging.types'
export const toggleChatVisible = () => ({
type: MessagingActionTypes.TOGGLE_CHAT_VISIBLE,
//payload: user
});

View File

@@ -0,0 +1,24 @@
import MessagingActionTypes from "./messaging.types";
const INITIAL_STATE = {
visible: false
};
const messagingReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case MessagingActionTypes.TOGGLE_CHAT_VISIBLE:
return {
...state,
visible: !state.visible
};
case MessagingActionTypes.SET_CHAT_VISIBLE:
return {
...state,
visible: true
};
default:
return state;
}
};
export default messagingReducer;

View File

@@ -0,0 +1,90 @@
import {
all
// call, put, takeLatest
} from "redux-saga/effects";
//import { auth, getCurrentUser } from "../../firebase/firebase.utils";
// import { toggleChatVisible } from "./messaging.actions";
// import MessagingActionTypes from "./messaging.types";
// export function* getSnapshotFromUserAuth(userAuth) {
// try {
// const userRef = yield call(createUserProfileDocument, userAuth);
// //const userSnapshot = yield userRef.get();
// } catch (error) {
// yield put(signInFailure(error));
// }
// }
// export function* signInWithEmail({ payload: { email, password } }) {
// try {
// const { user } = yield auth.signInWithEmailAndPassword(email, password);
// yield put(
// signInSuccess({
// uid: user.uid,
// email: user.email,
// displayName: user.displayName,
// authorized: true
// })
// );
// } catch (error) {
// yield put(signInFailure(error));
// }
// }
// //This is the listener fo rthe call, and when it finds it, it triggers somethign else.
// export function* onEmailSignInStart() {
// yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail);
// }
// export function* isUserAuthenticated() {
// try {
// const user = yield getCurrentUser();
// if (!user) {
// yield put(unauthorizedUser());
// return;
// }
// let token = yield user.getIdToken();
// localStorage.setItem("token", token);
// window.sessionStorage.setItem(`lastTokenRefreshTime`, new Date());
// yield put(
// signInSuccess({
// uid: user.uid,
// email: user.email,
// displayName: user.displayName,
// authorized: true
// })
// );
// } catch (error) {
// yield put(signInFailure(error));
// }
// }
// export function* onCheckUserSession() {
// yield takeLatest(UserActionTypes.CHECK_USER_SESSION, isUserAuthenticated);
// }
// export function* signOutStart() {
// try {
// yield auth.signOut();
// yield put(signOutSuccess());
// localStorage.removeItem("token");
// } catch (error) {
// yield put(signOutFailure(error.message));
// }
// }
// export function* onSignOutStart() {
// yield takeLatest(UserActionTypes.SIGN_OUT_START, signOutStart);
// }
export function* messagingSagas() {
yield all([
// call(onGoogleSignInStart),
// call(onEmailSignInStart),
// call(onCheckUserSession),
// call(onSignOutStart)
// call(onEmailSignUpStart),
// call(onEmailSignUpSuccess)
]);
}

View File

@@ -0,0 +1,8 @@
import { createSelector } from "reselect";
const selectMessaging = state => state.messaging;
export const selectChatVisible = createSelector(
[selectMessaging],
messaging => messaging.visible
);

View File

@@ -0,0 +1,5 @@
const MessagingActionTypes = {
TOGGLE_CHAT_VISIBLE: "TOGGLE_CHAT_VISIBLE",
SET_CHAT_VISIBLE: "SET_CHAT_VISIBLE"
};
export default MessagingActionTypes;

View File

@@ -3,6 +3,7 @@ import { persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage"; import storage from "redux-persist/lib/storage";
import userReducer from "./user/user.reducer"; import userReducer from "./user/user.reducer";
import messagingReducer from "./messaging/messaging.reducer";
// import cartReducer from './cart/cart.reducer'; // import cartReducer from './cart/cart.reducer';
// import directoryReducer from './directory/directory.reducer'; // import directoryReducer from './directory/directory.reducer';
// import shopReducer from './shop/shop.reducer'; // import shopReducer from './shop/shop.reducer';
@@ -15,7 +16,8 @@ const persistConfig = {
}; };
const rootReducer = combineReducers({ const rootReducer = combineReducers({
user: userReducer user: userReducer,
messaging: messagingReducer
// cart: cartReducer, // cart: cartReducer,
// directory: directoryReducer, // directory: directoryReducer,
// shop: shopReducer // shop: shopReducer

View File

@@ -3,9 +3,10 @@ import { all, call } from "redux-saga/effects";
//List of all Sagas //List of all Sagas
// import { shopSagas } from "./shop/shop.sagas"; // import { shopSagas } from "./shop/shop.sagas";
import { userSagas } from "./user/user.sagas"; import { userSagas } from "./user/user.sagas";
import { messagingSagas } from "./messaging/messaging.sagas";
//import { cartSagas } from "./cart/cart.sagas"; //import { cartSagas } from "./cart/cart.sagas";
export default function* rootSaga() { export default function* rootSaga() {
//All starts all the Sagas concurrently. //All starts all the Sagas concurrently.
yield all([call(userSagas)]); yield all([call(userSagas), call(messagingSagas)]);
} }

View File

@@ -38,3 +38,13 @@ export const setUserLanguage = language => ({
type: UserActionTypes.SET_USER_LANGUAGE, type: UserActionTypes.SET_USER_LANGUAGE,
payload: language payload: language
}); });
export const updateUserDetails = userDetails => ({
type: UserActionTypes.UPDATE_USER_DETAILS,
payload: userDetails
});
export const updateUserDetailsSuccess = userDetails => ({
type: UserActionTypes.UPDATE_USER_DETAILS_SUCCESS,
payload: userDetails
});

View File

@@ -33,10 +33,17 @@ const userReducer = (state = INITIAL_STATE, action) => {
...state, ...state,
language: action.payload language: action.payload
}; };
case UserActionTypes.UPDATE_USER_DETAILS_SUCCESS:
return {
...state,
currentUser: {
...state.currentUser,
...action.payload //Spread current user details in.
}
};
case UserActionTypes.SIGN_IN_FAILURE: case UserActionTypes.SIGN_IN_FAILURE:
case UserActionTypes.SIGN_OUT_FAILURE: case UserActionTypes.SIGN_OUT_FAILURE:
case UserActionTypes.EMAIL_SIGN_UP_FAILURE: case UserActionTypes.EMAIL_SIGN_UP_FAILURE:
console.log("Reduced getting called.");
return { return {
...state, ...state,
error: action.payload error: action.payload

View File

@@ -1,6 +1,17 @@
import { all, call, put, takeLatest } from "redux-saga/effects"; import { all, call, put, takeLatest } from "redux-saga/effects";
import { auth, getCurrentUser } from "../../firebase/firebase.utils"; import {
import { signInFailure, signInSuccess, signOutFailure, signOutSuccess, unauthorizedUser } from "./user.actions"; auth,
getCurrentUser,
updateCurrentUser
} from "../../firebase/firebase.utils";
import {
signInFailure,
signInSuccess,
signOutFailure,
signOutSuccess,
unauthorizedUser,
updateUserDetailsSuccess
} from "./user.actions";
import UserActionTypes from "./user.types"; import UserActionTypes from "./user.types";
// export function* getSnapshotFromUserAuth(userAuth) { // export function* getSnapshotFromUserAuth(userAuth) {
@@ -74,6 +85,18 @@ export function* onSignOutStart() {
yield takeLatest(UserActionTypes.SIGN_OUT_START, signOutStart); yield takeLatest(UserActionTypes.SIGN_OUT_START, signOutStart);
} }
export function* onUpdateUserDetails() {
yield takeLatest(UserActionTypes.UPDATE_USER_DETAILS, updateUserDetails);
}
export function* updateUserDetails(userDetails) {
try {
yield updateCurrentUser(userDetails.payload);
yield put(updateUserDetailsSuccess(userDetails.payload));
} catch (error) {
//yield put(signOutFailure(error.message));
//TODO: error handling
}
}
export function* userSagas() { export function* userSagas() {
yield all([ yield all([
@@ -81,6 +104,7 @@ export function* userSagas() {
call(onEmailSignInStart), call(onEmailSignInStart),
call(onCheckUserSession), call(onCheckUserSession),
call(onSignOutStart), call(onSignOutStart),
call(onUpdateUserDetails)
// call(onEmailSignUpStart), // call(onEmailSignUpStart),
// call(onEmailSignUpSuccess) // call(onEmailSignUpSuccess)
]); ]);

View File

@@ -12,6 +12,8 @@ const UserActionTypes = {
EMAIL_SIGN_UP_SUCCESS: "EMAIL_SIGN_UP_SUCCESS", EMAIL_SIGN_UP_SUCCESS: "EMAIL_SIGN_UP_SUCCESS",
EMAIL_SIGN_UP_FAILURE: "EMAIL_SIGN_UP_FAILURE", EMAIL_SIGN_UP_FAILURE: "EMAIL_SIGN_UP_FAILURE",
UNAUTHORIZED_USER: "UNAUTHORIZED_USER", UNAUTHORIZED_USER: "UNAUTHORIZED_USER",
SET_USER_LANGUAGE: "SET_USER_LANGUAGE" SET_USER_LANGUAGE: "SET_USER_LANGUAGE",
UPDATE_USER_DETAILS: "UPDATE_USER_DETAILS",
UPDATE_USER_DETAILS_SUCCESS: "UPDATE_USER_DETAILS_SUCCESS"
}; };
export default UserActionTypes; export default UserActionTypes;

View File

@@ -272,6 +272,9 @@
"user": { "user": {
"actions": { "actions": {
"signout": "Sign Out" "signout": "Sign Out"
},
"fields": {
"displayname": "Display Name"
} }
}, },
"vehicles": { "vehicles": {

View File

@@ -272,6 +272,9 @@
"user": { "user": {
"actions": { "actions": {
"signout": "desconectar" "signout": "desconectar"
},
"fields": {
"displayname": "Nombre para mostrar"
} }
}, },
"vehicles": { "vehicles": {

View File

@@ -272,6 +272,9 @@
"user": { "user": {
"actions": { "actions": {
"signout": "Déconnexion" "signout": "Déconnexion"
},
"fields": {
"displayname": "Afficher un nom"
} }
}, },
"vehicles": { "vehicles": {

View File

@@ -0,0 +1,3 @@
- args:
sql: DROP TABLE "public"."employees"
type: run_sql

View File

@@ -0,0 +1,21 @@
- args:
sql: CREATE EXTENSION IF NOT EXISTS pgcrypto;
type: run_sql
- args:
sql: "CREATE TABLE \"public\".\"employees\"(\"id\" uuid NOT NULL DEFAULT gen_random_uuid(),
\"created_at\" timestamptz NOT NULL DEFAULT now(), \"updated_at\" timestamptz
NOT NULL DEFAULT now(), \"first_name\" text NOT NULL, \"last_name\" text NOT
NULL, \"employee_number\" text, \"shopid\" uuid NOT NULL, \"active\" boolean
NOT NULL DEFAULT true, PRIMARY KEY (\"id\") , FOREIGN KEY (\"shopid\") REFERENCES
\"public\".\"bodyshops\"(\"id\") ON UPDATE cascade ON DELETE cascade);\nCREATE
OR REPLACE FUNCTION \"public\".\"set_current_timestamp_updated_at\"()\nRETURNS
TRIGGER AS $$\nDECLARE\n _new record;\nBEGIN\n _new := NEW;\n _new.\"updated_at\"
= NOW();\n RETURN _new;\nEND;\n$$ LANGUAGE plpgsql;\nCREATE TRIGGER \"set_public_employees_updated_at\"\nBEFORE
UPDATE ON \"public\".\"employees\"\nFOR EACH ROW\nEXECUTE PROCEDURE \"public\".\"set_current_timestamp_updated_at\"();\nCOMMENT
ON TRIGGER \"set_public_employees_updated_at\" ON \"public\".\"employees\" \nIS
'trigger to set value of column \"updated_at\" to current timestamp on row update';\n"
type: run_sql
- args:
name: employees
schema: public
type: add_existing_table_or_view

View File

@@ -0,0 +1,12 @@
- args:
relationship: employees
table:
name: bodyshops
schema: public
type: drop_relationship
- args:
relationship: bodyshop
table:
name: employees
schema: public
type: drop_relationship

View File

@@ -0,0 +1,20 @@
- args:
name: employees
table:
name: bodyshops
schema: public
using:
foreign_key_constraint_on:
column: shopid
table:
name: employees
schema: public
type: create_array_relationship
- args:
name: bodyshop
table:
name: employees
schema: public
using:
foreign_key_constraint_on: shopid
type: create_object_relationship

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: employees
schema: public
type: drop_insert_permission

View File

@@ -0,0 +1,30 @@
- args:
permission:
allow_upsert: true
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- active
- employee_number
- first_name
- last_name
- created_at
- updated_at
- id
- shopid
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: employees
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: employees
schema: public
type: drop_select_permission

View File

@@ -0,0 +1,27 @@
- args:
permission:
allow_aggregations: false
columns:
- active
- employee_number
- first_name
- last_name
- created_at
- updated_at
- id
- shopid
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
limit: null
role: user
table:
name: employees
schema: public
type: create_select_permission

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: employees
schema: public
type: drop_update_permission

View File

@@ -0,0 +1,29 @@
- args:
permission:
columns:
- active
- employee_number
- first_name
- last_name
- created_at
- updated_at
- id
- shopid
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: employees
schema: public
type: create_update_permission

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: employees
schema: public
type: drop_delete_permission

View File

@@ -0,0 +1,16 @@
- args:
permission:
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: employees
schema: public
type: create_delete_permission