React: Creating different user profiles - database

I'm making an app in React and I want to users have the option to create an account using their email/password as auth factors. However, I want the option for two different user types. For example, a client and physician. So that when a user logs in they are taken to unique profiles (ie: clientProfile.js or physicianProfile.js) to that user type. Is this a db item? A firebase Auth token? Any advice would be great!

When a user registers they'll select their role from a select dropdown (or whatever UI element you choose): Client or Physician. You'll save this to the database.
When the user logs in you'll return their role along with any other relevant user data the front-end needs to handle the user.
When you render the profile you'll check their role and return the appropriate component: clientProfile.js or physicianProfile.js
Your component may look something like this:
class UserProfile extends Component {
constructor(props) {
super(props);
this.state = {
user: null
};
}
componentDidMount() {
/* fetch user from database */
this.setState({user: response.user});
}
render() {
const { user } = this.state;
if (user === null) {
return <Loading/>;
}
if (user.role === 'physician') {
return <PhysicianProfile user={user}/>;
}
return <ClientProfile user={user}/>;
}
}

Related

Updating data in firebase doesnt work inside async handleclick func

I am working on an app that has a payment tab, when i click on the payment tab it takes me to a page which only has just one button, when i click on that button, it takes me to the stripe checkout system (https://stripe.com/docs/payments/checkout/client) and once it handles the payment, it takes me back to the app. I have a database in firebase that tracks if the current user that is logged in is either paid or not. if the current user is unpaid, i want the membership field of my db for that user to be updated from 'false' to 'true' AFTER the payment has been completed successfully. The problem is that if i do update of the db right after the payment is done inside the try block, the db is not updated, but if i do the update inside componentDidMount() func, i do see changes made to the db. What am i doing wrong? is the fact that the func is async casuing the problem here? if i want to update the database after payment is successful, than how would i go about doing that?
import React from 'react';
import firebase from "firebase/app";
import 'firebase/firestore';
import "firebase/database";
import { auth, database } from '../../firebase';
import { UserAndDbObjConsumer, UserAndDbObjContext } from '../../dbAndUserObjContext';
import { loadStripe } from '#stripe/stripe-js';
// Make sure to call `loadStripe` outside of a component’s render to avoid
// recreating the `Stripe` object on every render.
const stripePromise = loadStripe('test key here');
export default class Payment extends React.Component {
static contextType = UserAndDbObjContext;
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
const {user, database} = this.context;
// When i update my data below, it updates the firebase db and i can
// see the changes taking effect
// database.collection("userCollection").doc(user.uid).update({
// membership: true
// })
}
handleClick = async (event) => {
const {user, database} = this.context;
console.log("i am inside handle but outside try")
try {
// When the customer clicks on the button, redirect them to Checkout.
const stripe = await stripePromise;
const { error } = await stripe.redirectToCheckout({
lineItems: [{
price: 'price_1IF4cPAt4f9zG3h2hTG64agQ', // Replace with the ID of your price
quantity: 1,
}],
mode: 'payment',
successUrl: 'https://app.guideanalytics.ca/',
cancelUrl: 'https://guideanalytics.ca/termsofuse.html',
});
// If `redirectToCheckout` fails due to a browser or network
// error, display the localized error message to your customer
// using `error.message`.
// When i update the firebase db here, than the changes on firebase dont take place
database.collection("userCollection").doc(user.uid).update({
membership: true
})
}
catch(e) {
console.log(e);
}
}
render() {
return (
<div>
<button role="link" onClick={(e) => this.handleClickTest()}>
Checkout
</button>
</div>
)
}
}
//Payment.contextType = Context;
stripe.redirectToCheckout will only error if there's a browser or network error, so in practically all cases your database.collection function call will never execute.
Instead you would need to call that function when the user hits the successUrl you defined in your Checkout Session. You might want to customize the successUrl to ensure that you know which customer to update. For instance:
successUrl: `https://app.guideanalytics.a/?userId=${user.uid}&sessionId={CHECKOUT_SESSION_ID}`,
The last part is a wildcard which Stripe will then automatically replace with the Session's ID.
Then on your success page you'd update the database based on the user ID and Checkout Session ID in the URL.

onAuthStateChanged firebase auth creating a user twice React Nextjs

I have an app that I do not need a scenario where there is no user. Once the user lands on the main page a user is created for them anonymously. If they are a user with credentials and they are signed in when they sign out a user anonymous should be created again till they sign in again.
I took a HOC component that was working until I was ready to go into production then I noticed a bug in code where a user is created twice when they land on the index page.
This is my HOC that I wrap my index page and every other page with
const AuthHoc = (Component) => {
return class extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log("mounted") /////this logs twice for some reason I dont know why
auth.onAuthStateChanged((user) => {
if (user) {
console.log(user.uid);
} else {
console.log("no user");
var user = auth.currentUser;
if (user) {
// User is signed in.
console.log("there is already a suer")
} else {
// No user is signed in.
auth.signInAnonymously().then(async (cred) => {
uid=cred.user.uid
console.log("user created with id", cred.user.uid);
});
}
}
});
render() {
return <Component {...this.props} />;
}
};
};
export default AuthHoc;
and wrapping my index page as
export default connect(mapStateToProps)(AuthHoc(Index));
Every log in this code is called twice. The component mounts... twice (I don't know why) it finds no user, as it should, creates a user and logs the new user id. The onAuthStateChanged callback fires and logs the uid in the if user block of code. Then the problem comes where it reruns the whole process a second time, it creates a user again ignoring the conditionals.
How best can I improve this code. I need to reuse it across the entire app

Adding reset password functionality to react / redux login functionality

I used the following login with react/redux tutorial to build a signup / signin functionality into my React app, however I did not realize until recently that I now also need a reset-password / forgot-password functionality.
This feature is not a part of the tutorial at all, and I am simply wondering if anybody has any suggestions as to how I can go about this?
Let me know if I can share any info about my app that will help with this, or if there's a better place to post this type of question. I'm holding off on sharing more on the app as I think it's redundant given the info in the tutorial is nearly exactly how my signup / signin is setup.
Thanks!
After the user enters the proper credentials that you state (usually username, email, or both)
Make an api call to your backend that creates a password reset token. Store it in the database and, in one form or another, associate it with the user (usually it's the same database entry).
Send an email to the user with a link that has the password reset token embedded into it. Have a route in your react-router routes that will handle the url you link to.
Have the route mount a component that has a componentDidMount, which takes the token and makes an api to the backend to validate the token.
Once validated, open a ui element in the react component that allows the user to set a new password
Take the new password, password confirmation, and reset token and make an api call to the backend to change the password.
Delete the reset token in the backend after successful password change
// in your routes file
<Route path="/password_reset/:token" component={PasswordResetValidator}/>
//
class PasswordResetValidator extends React.Component {
state = {password: '', passwordReset: '', isValidated: false}
async componentDidMount() {
const response = await validatePasswordResetToken(this.props.token)
if (response.ok) {
this.setState({ isValidated: true })
} else {
// some error
}
}
handleSubmit = () => {
const { token } = this.props
const { password, passwordReset } = this.state
sendPasswordResetData({password, passwordReset, token})
// probably want some confirmation feedback to the user after successful or failed attempt
}
render() {
if(this.state.isValidated) {
return (
<div>
<input />
<input />
<button onClick={this.handleSubmit}>Set new password</button>
<div>
)
}
return // something while token is being validated
}
}
Obviously you need to make your own text input handlers. You should also have error handling, and good ui feedback to the user. But ultimately, that's all at your discretion.
Best of luck

How can I get role before render()

I want to do the following:
send a request to the server, verify JSON web-token and get user details (name, email and role);
then the page appears with the specific menu items (if the user is admin it shows him Main, Admin and Logout items; if not - Main and Logout).
I thought about just getting the token from localStorage, then decoding it and taking a role from it. But what should I do if I change the role in database (for example from admin to user)? Decoded token on the client-side will contain the "admin" role. So this user will be able to see the admin page.
You can use a variable in store called isFetchingUserDetails and set it to true when you make that async call to the server to get the details.
Till isFetchingUserDetails is set true, you can have an if statement in the render() to return a spinner or other component which shows the user that the page is loading.
Once you get the response from the server, isFetchingUserDetails will be set to false and the rest of the render() will be executed.
Without Store
import React, { Component } from "react";
import ReactDOM from "react-dom";
class App extends Component {
constructor() {
super();
this.state = { isFetchingUserDetails: true };
}
componentDidMount() {
fetch(`https://api.makethecall.com/users/1`)
.then(res => res.json())
.then(() => this.setState({ isFetchingUserDetails: false }));
}
render() {
if (this.state.isFetchingUserDetails) {
return <Spinner />
}
return (
<Home />
);
}
}

Redux&React handle data the right way

Assume the following situation: As a logged-in user, you're accessing your own profile page. The header menu will show your avatar as a logged-in user, and the rest of the page will show your user public information.
You got a list of users from the api, and you need to handle data from 2 of these users: the logged-in user and the user which profile page is being accessed (in the situation above, it's actually the same user).
Duplicating the data in store.users, store.currentUser and store.myUser would be a very bad idea (need to sync these 3 occurrences of the data everytime it gets modified). But I do not want my components using the current or logged in user data to be re-rendered everytime some lambda user data gets modified in the list of users.
How would you proceed in order to avoid data duplication, speed up data handling time (avoid costly list operations like filter/find), and avoid unnecessary re-rendering ?
After some thinking, I'm thinking of implementing it this way,
Data are stored as follow:
store = {
users: {
1: {
name: 'John',
},
},
currentUser: 1, // user which profile page is being accessed
myUser: 1, // logged in user
};
Now, the UserDetails component can be connected as follow:
#connect(state => {user: state[state.currentUser]})
export default class UserDetails extends Component {
render() {
return <div>User profile: {this.props.user.name}</div>;
}
}
and the page header using the logged in user data can be connected as follow:
#connect(state => {user: state[state.myUser]})
export default class HeaderMenu extends Component {
render() {
return <div>Logged-in user: {this.props.user.name}</div>;
}
}
But I'm not convinced that this is an ideal solution. Will have to test whether the re-rendering is truly avoided or not.
Your thoughts are very welcome!
I would store the details of the logged in user in the Redux store. Whatever data necessary to manage the user's session and render the persistent header. For other user profiles I'd load the data on-the-fly from the backend.
For example, if you had a route /profile/:id and this was the URL foo.com/profile/99
class Profile extends Component {
constructor(props) {
super(props);
this.state = {
user: null,
loading: true
};
}
componentDidMount() {
api.get('/getUser', {userId: this.props.match.params.id}, response => {
this.setState({
user: response.user,
loading: false
});
});
}
render() {
return (
...
);
}
}

Resources