React creating a mock login service with async await - reactjs

I am creating a mock login button which will return a user object after 5 seconds of clicking the login button.
I have the following event handler for the login button:
import UserService from "../services/user";
export default class Login extends Component {
constructor(props) {
super(props);
this.state = {
field: {
username: "",
password: "",
},
};
}
login = async (event) => {
event.preventDefault();
const res = await UserService.login({
username: this.state.field.username,
password: this.state.field.password,
});
console.log("RESPONSE DATA", res);
// Set user variable to the response
};
And this is the user service:
let user = null;
const login = async (credentials) => {
await setTimeout(() => {
user = {
name: "test user",
username: credentials.username,
password: credentials.password,
token: "test token",
};
console.log("login service finished");
}, 5000);
return user;
};
Basically, I want the user object to appear on the console.log("RESPONSE DATA", res) part. However, what I'm getting instead is null. How do I go around on doing this properly?
==================================
EDIT:
Decided to rewrite into function components. I'm still getting the same results
const Login = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const userContext = useContext(UserContext);
const login = async (event) => {
event.preventDefault();
console.log("logging in:", username, password);
try {
const user = await UserService.login({
username: username,
password: password,
});
console.log(user);
// userContext[1](user);
// console.log(userContext);
} catch (exception) {
console.log(exception);
}
};

The main issue here is that setTimeout return value is basically
a positive integer value which identifies the timer created by the call to setTimeout(). (which can be passed to clearTimeout() to cancel the timeout)
So in order to achive this you need to transform your function so it returns a promise. You can find some options here. In your case you could edit it slightly the answers so do something like:
let user = null;
const login = async (credentials) => {
return new Promise(resolve => setTimeout(() => {
user = {
name: "test user",
username: credentials?.username,
password: credentials?.password,
token: "test token",
};
console.log("login service finished");
return resolve(user);
}, 5000))
};

Related

How to set a custom username in Firebase?

The first time a user logins with Google Auth provider a "username" field with an empty value is set in Users collection user.uid document. Now I want to first check if the username length is greater than 3 (which will be the minimum for a username). If greater than 3 usernames are already set, else a modal should open for the user to set a username.
The code below does not work and not sure if it's the correct approach I was trying. The code runs once the user logs in.
const [user] = useAuthState(auth);
const CheckUsername = async () => {
const docRef = doc(db, "UsersData", user.uid);
const docSnap = await getDoc(docRef);
if (!docSnap.exists() && docSnap.data().username.length > 3) {
//<Show SetUserName Modal - Recoil>
} else if (docSnap.exists() && docSnap.data().username.length > 3) {
//<Don't show SetUserName Modal>
}
};
useEffect(() => {
if (user) {
CheckUsername();
}
}, [user]);
SetUsername Modal:
const [user] = useAuthState(auth);
const [usernameValue, setUsernameValue] = useState("");
const SetUsername = async () => {
try {
const UserRef = collection(db, "UsersData")
const UsernameQuery = query(UserRef, where("username", "==", usernameValue))
const Username = await getDoc(UsernameQuery)
if(!Username.exists()) {
await updateDoc(doc(db, "UsersData", user.uid), {
username: usernameValue,
});
} else {
console.log("Username already exists, please try another one");
}
} catch (error) {
console.log("error in try catch")
}
}
return (
<div>
<input type="text" onChange={(e) => setUsernameValue(e.target.value)} />
<button onClick={SetUsername}>Set username</button>
</div>
);
Solution I came up with:
This is in layout:
const [user] = useAuthState(auth);
const [open, setOpen] = useRecoilState(setUsernameModal);
const [update, setUpdate] = useState(true);
const CheckUser = async () => {
try {
//Where Users are stored
const userDocRef = doc(db, "UsersData1", user.uid);
//Using Transaction for if something goes wrong mid process no action taken at all
await runTransaction(db, async (transaction) => {
const userDoc = await transaction.get(userDocRef);
//Read ELSE first
//If userDoc exists means they logged in before AND/OR never finished the registration process
if (userDoc.exists()) {
const User = await getDoc(userDocRef);
//if usernameSet = false means they never set the username before
if (User.data().usernameSet === false) {
//Opens a modal to set username - (for my case it's the last process for registration)
setOpen(true);
}
} else {
//If User doesn't exist in "UsersData" means it's the first time they are logging in
await setDoc(doc(db, "UsersData1", user.uid), {
//usernameSet to check if username is set or not.
usernameSet: false,
username: "",
//usernameValue for search if username is taken and should be in uppercase OR lowercase since Fx: John & john are not the same
usernameValue: "",
//Add's default Firebase info
user: JSON.parse(JSON.stringify(user)),
});
//Updates useEffect so the user falls in userDoc.exists
setUpdate(!update);
}
});
} catch (error) {}
};
useEffect(() => {
if (user) {
CheckUser();
}
}, [user, update]);
Then a modal to update: username: "" and usernameSet: to true and then use usernameValue to check if user already exists

How to add a field in Firestore collection using Next.js?

I want to add a field in the DB collection after the user has created that is a boolean, like isAdmin: false. Creation of user and adding doc to the DB is working but I'm not sure if I should add it while adding the doc or use updateDoc. I tried this but not working:
await updateDoc(collection(firestore, "Users", User.id), {
isAdmin: false
});
I'm using Next.js and react-firebase-hooks. Thanks in advance for any help.
My current code that is working with user creation and adding the user to Firebase db:
const [signUpForm, setSignUpForm] = useState({
email: "",
password: "",
confirmPassword: "",
});
const [error, setError] = useState(false);
const [createUserWithEmailAndPassword, user, loading, userError] =
useCreateUserWithEmailAndPassword(auth);
const onSubmit = async (event) => {
try {
event.preventDefault();
if (error) setError("");
if (signUpForm.password !== signUpForm.confirmPassword) {
return setError("Password do not match");
}
createUserWithEmailAndPassword(signUpForm.email, signUpForm.password);
} catch (error) {
console.log("Error", error.msg);
}
};
//Sets inputs value in signUpForm useState
const onChange = (event) => {
setSignUpForm((prev) => ({
...prev,
[event.target.name]: event.target.value,
}));
};
//Adds the user to Firebase db
const createUserDocument = async (User) => {
await addDoc(
collection(firestore, "Users"),
JSON.parse(JSON.stringify(User))
);
};
useEffect(() => {
if (user) {
createUserDocument(user.user);
console.log(user);
}
}, [user]);
Collections have no fields. Collections have documents. You have mistake in updateDoc() function.
Here is how you should update documents:
await updateDoc(doc(firestore, "Users", user.uid), {
isAdmin: false
});
In your arrow function, you have another mistake. You're creating a document with random ID. You should use setDoc() instead of addDoc() function.
const createUserDocument = async (user) => {
await setDoc(
doc(firestore, "Users", user.uid),
JSON.parse(JSON.stringify(User))
);
};
Edit:
Assuming your user object is a User not UserCredential it has .uid field not .id. If it is a UserCredential you need first take a user so: user.user.uid

How to add phone number to a logged in user(created with email and password) in firebase Auth in react?

I am using firebasev9 authentication for my react project. I have used email authentication for logging in/signing up a user. I want to add phone number too in the user but I am doing all the right steps but when I call updatePhoneNumber with user and phone crediential it throws an error and doesnt add phone number. I am updating the displayName too within the same function which works fine.
I have enabled phone signin in firebase dashboard
The error I am getting is this:
(https://i.postimg.cc/yY55Qzg2/Screenshot-2022-06-25-200735.jpg)
This is my signup function:
const signup = async (email, password, displayName, phoneNumber,phoneCrediential, userType) => {
setError(null);
setIsPending(true);
try {
const res = await createUserWithEmailAndPassword(
auth,
email,
password
);
console.log(res.user);
if (!res) {
throw new Error("Could not complete signUp");
}
debugger;
await updateProfile(res.user, { displayName });
**This updatePhonenumber function throws this error flow moves to catch block**
const resPhone = await updatePhoneNumber(auth.currentUser, phoneCrediential );
console.log(resPhone)
dispatch({ type: "LOGIN", payload: res.user });
console.log(res.user)
addDocument({
name: res.user.displayName,
email: res.user.email,
uid: res.user.uid,
type: userType,
});
if (!isCancelled) {
setError(null);
setIsPending(false);
}
} catch (err) {
if (!isCancelled) {
console.log(err.message);
setError(err.message);
setIsPending(false);
}
}
};
In my component, I take phone number, generate Otp, and take otp and pass phoneCredential to the signup function:
const [verificationIdState, setVerificationIdState] = useState(null);
const [phoneCredientialState, setPhoneCredientialState] = useState(null);
const handleRecaptcha = () => {
window.recaptchaVerifier = new RecaptchaVerifier(
"sign-in-button",
{
size: "invisible",
callback: (response) => {
// reCAPTCHA solved
},
},
auth
);
};
const handleGetOTP = () => {
handleRecaptcha();
const phoneNumber = "+91" + userPhoneNumber;
const applicationVerifier = window.recaptchaVerifier;
const provider = new PhoneAuthProvider(auth);
const verificationId = provider.verifyPhoneNumber(
phoneNumber,
applicationVerifier
);
if (verificationId) {
setVerificationIdState(verificationId);
}
};
const handleOTPSubmit = () => {
const phoneCredential = PhoneAuthProvider.credential(
verificationIdState,
userOTP
);
if (phoneCredential) {
setPhoneCredientialState(phoneCredential);
console.log(phoneCredential);
}
};
//Base Register
const handleRegisterSubmit = (e) => {
e.preventDefault();
signup(
userEmail,
userPassword,
userName,
userPhoneNumber,
phoneCredientialState,
userType
);
};

Next Auth: Redirect page before success login

I want make a custom login use next Auth.
What I want is, after check my email and password true, I want redirect to google auth page and after google auth true I want set the token to session and redirect to home. Is it possible to make it like that?
//This is the flow
Login -> google Auth -> Home
This is login page
//Front End
const handler = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
const res = await signIn("credentials", {
redirect: false,
data: emailRef.current?.value,
password: passwordRef.current?.value,
});
if (res?.error) {
toast.error(res?.error);
setLoading(false);
} else {
Router.replace("/");
Router.events.on("routeChangeComplete", success);
}
};
This is the API Page
//API
export default NextAuth({
session: {
jwt: true,
},
providers: [
Providers.Credentials({
async authorize(credentials: credentialsData, req: NextApiRequest) {
let client;
try {
client = await ConnectDatabase();
} catch (error) {
throw new Error("Failed connet to database.");
}
const checkEmail = await client
.db()
.collection("users")
.findOne({ email: credentials.data });
const checkPhone = await client
.db()
.collection("users")
.findOne({ phone: credentials.data });
let validData = {
password: "",
email: "",
};
if (!checkEmail && !checkPhone) {
client.close();
throw new Error("Email atau No HP tidak terdaftar.");
} else if (checkEmail) {
validData = checkEmail;
} else if (checkPhone) {
validData = checkPhone;
}
const checkPassword = await VertifyPassword(
credentials.password,
validData.password
);
if (!checkPassword) {
client.close();
throw new Error("Password Salah.");
}
client.close();
return validData;
},
}),
],
callbacks: {
async session(session) {
const data = await getSelectedUser(session.user!.email!);
session.user = data.userData;
return Promise.resolve(session);
},
},
});
In above Example I don't use google auth, so after login success it immediately redirects to home

Sending verification email with Firebase and React Native

I am trying to send the validation email upon the account registration, using firebase. The registration is being done successfully but whenever I try to code email verification it gives me an error. Probably because I don't know where to place it. All my firebase methods are on Fire.js, which are the following:
import firebaseKeys from './Config';
import firebase from 'firebase';
require("firebase/firestore");
class Fire {
constructor() {
if (!firebase.apps.length) {
firebase.initializeApp(firebaseKeys);
}
}
addPost = async ({ text, localUri }) => {
const remoteUri = await this.uploadPhotoAsync(localUri, 'photos/${this.uid}/${Date.now()}');
return new Promise((res, rej) => {
this.firestore.collection('posts').add({
text,
uid: this.uid,
timestamp: this.timestamp,
image: remoteUri
})
.then(ref => {
res(ref);
})
.catch(error => {
rej(error);
});
});
}
uploadPhotoAsync = async (uri, filename) => {
return new Promise(async (res, rej) => {
const response = await fetch(uri);
const file = await response.blob();
let upload = firebase
.storage()
.ref(filename)
.put(file);
upload.on(
"state_changed",
snapshot => {},
err => {
rej(err);
},
async () => {
const url = await upload.snapshot.ref.getDownloadURL();
res(url);
}
);
});
}
createUser = async user => {
let remoteUri = null
try {
await firebase.auth().createUserWithEmailAndPassword(user.email, user.password)
//I tried to code it here with user.sendEmailVerification();
let db = this.firestore.collection("users").doc(this.uid)
db.set({
name: user.name,
email: user.email,
avatar: null
})
if (user.avatar) {
remoteUri = await this.uploadPhotoAsync(user.avatar, 'avatars/${this.uid}')
db.set({avatar: remoteUri}, {merge: true});
}
} catch (error) {
alert("Error: ", error);
}
};
get firestore() {
return firebase.firestore();
}
get uid() {
return (firebase.auth().currentUser || {}).uid;
}
get timestamp() {
return Date.now();
}
}
Fire.shared = new Fire();
export default Fire;
The createUserWithEmailAndPassword() method returns a Promise which resolves with a UserCredential AND (as the the doc indicates) "on successful creation of the user account, this user will also be signed in to your application."
So you can easily get the signed in user by using the user property of the UserCredential, and call the sendEmailVerification() method, as follows:
try {
const userCredential = await firebase.auth().createUserWithEmailAndPassword(user.email, user.password);
await userCredential.user.sendEmailVerification();
//In the next line, you should most probably use userCredential.user.uid as the ID of the Firestore document (instead of this.uid)
cont db = this.firestore.collection("users").doc(this.uid);
//...
} catch (...)
Note that you may pass an ActionCodeSettings object to the sendEmailVerification() method, see the doc.

Resources