, September 29, 2022

0 kết quả được tìm thấy

Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) - Phần 6


  •   9 min reads
Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) - Phần 6

Trong phần trước, chúng ta đã hoàn thành trang Home (Guest Home Page), đồng thời cũng đã config và connect được với firebase. Trong phần phần này, chúng ta sẽ config React Context firebase, sau đó tạo Sign In Page và apply Authentication mà firebase cung cấp cho project của chúng ta nhé. Bắt đầu thôi 😉!

I. React Context for firebase.

Để các component có sử dụng các data do firebase trả về mà không cần truyền data theo kiểu nested từ cha → con, một kiểu truyền thống (phức tạp) thì chúng ta sẽ tạo một context có cho firebase như sau:

  • Trong folder src ta tạo folder và file như sau: /src/context/firebase.js
import { createContext } from "react";

export const FirebaseContext = createContext(null);
firebase.js
  • Trong file /src/index.js, ta edit lại như sau:
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { GlobalStyles } from "./global-styles";
import { firebase } from "./lib/firebase.prod";
import { FirebaseContext } from "./context/firebase"; //<----- here

ReactDOM.render(
	<React.StrictMode>
		<FirebaseContext.Provider value={{ firebase }}>  {/* <----- here */}
			<GlobalStyles />
			<App />
		</FirebaseContext.Provider>
	</React.StrictMode>,
	document.getElementById("root")
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: <https://bit.ly/CRA-vitals>
reportWebVitals();
src/index.js
React Context for firebase

Okie! Cơ bản như vậy đã xong, giờ chúng ta sang bước tiếp theo, tạo Sign In Page nhé 😉.

II. Tạo Sign In page.

Sign In page thì cũng có phần headerfooter như bên trang Home thôi.

Trong file /pages/signin.jsx, ta thêm đoạn code như sau:

import React from "react";
import { HeaderContainer, FooterContainer } from "../containers/index";

export default function SignIn() {
	return (
		<>
			<HeaderContainer>
			</HeaderContainer>
			<FooterContainer />
		</>
	);
}
signin.jsx

Ta sẽ được như hình ở dưới đây:

UI of Sign In page with Header and Footer

Okie, So sánh phần UI Sign In của Netflix realNetflix clone của chúng ta hiện tại thì sẽ có 2 thứ phải làm:

  • Đầu tiên là phần form Sign In,
  • Thứ 2 là phần Select boxbutton Sign In ở header, chúng ta sẽ ẩn nó đi ở Sign In page này.

Phần form để Sign In và cách để ẩn Select boxbutton Sign In, ta thực hiện như sau:

  • Trong folder components tạo thêm foder và file như sau:
Folder Form
  • Trong file form.jsx ta code như sau:
import styled from "styled-components/macro";
import { Link } from "react-router-dom";

export const Container = styled.div`
	display: flex;
	flex-direction: column;
	background-color: rgba(0, 0, 0, 0.75);
	border-radius: 4px;
	box-sizing: border-box;
	min-height: 515px;
	width: 100%;
	margin: auto;
	max-width: 450px;
	padding: 20px 0 30px;

	@media only screen and (min-width: 500px) {
		min-width: 380px;
	}

	@media only screen and (min-width: 740px) {
		min-height: 660px;
		padding: 60px 68px 40px;
		margin-bottom: 90px;
	}
`;

export const ErrorMessage = styled.div`
	background-color: #e87c03;
	border-radius: 4px;
	font-size: 14px;
	margin: 0 0 14px;
	color: #fff;
	padding: 15px 20px;
`;

export const Base = styled.form`
	display: flex;
	flex-direction: column;
	width: 100%;
	flex-grow: 0.5;
`;

export const Title = styled.h1`
	color: #fff;
	font-size: 32px;
	font-weight: 700;
	margin-bottom: 28px;
	padding: 0;
`;

export const Text = styled.p`
	color: #737373;
	font-size: 16px;
	font-weight: 500;
`;

export const SmallText = styled.span`
	margin-top: 10px;
	font-size: 13px;
	line-height: normal;
	color: #8c8c8c;
`;

export const RedirectLink = styled(Link)`
	color: #fff;
	text-decoration: none;

	&:hover {
		text-decoration: underline;
	}
`;

export const SmallLink = styled(RedirectLink)`
	color: #b3b3b3;
	cursor: pointer;
	font-size: 13px;
	font-weight: 500;
	padding-top: 2px;
`;

export const Input = styled.input`
	background: #333;
	border-radius: 4px;
	border: 0;
	color: #fff;
	height: 50px;
	line-height: 50px;
	padding: 5px 20px;
	width: 100%;
	box-shadow: none;
	box-sizing: border-box;
	font-size: 16px;
	display: block;
	appearance: none;
	margin-bottom: 16px;
`;

export const Button = styled.button`
	border: 0;
	border-radius: 4px;
	font-size: 16px;
	font-weight: 700;
	margin: 24px 0 12px;
	background: #e50914;
	position: relative;
	box-shadow: 0 1px 0 rgb(0 0 0 / 55%);
	color: #fff;
	cursor: pointer;
	letter-spacing: 0.1px;
	user-select: none;
	text-align: center;

	@media only screen and (min-width: 360px) {
		width: 100%;
		max-width: 100%;
	}

	@media only screen and (min-width: 500px) {
		padding: 16px;
		display: inline-block;
		min-width: 98px;
		min-height: 37px;
		line-height: 1em;
		&:disabled {
			opacity: 0.8;
		}
	}
`;

export const CheckBox = styled.input`
	width: 16px;
	height: 16px;
`;

export const Wrapper = styled.div`
	display: flex;
	align-items: center;
`;

export const FlexBox = styled.div`
	display: flex;
	align-items: center;
	justify-content: space-between;
`;

export const SocialButton = styled.button`
	margin-top: 16px;
	min-height: 0;
	width: auto;
	border: 0;
	background: 0 0;
	box-shadow: none;
	padding: 0;
	margin: 0;
`;

export const Image = styled.img`
	width: 20px;
	height: 20px;
	margin-right: 10px;
	vertical-align: middle;
`;
form.jsx
  • Trong file /form/index.jsx, ta code như sau:
import React from "react";
import {
	Container,
	Base,
	ErrorMessage,
	Title,
	Text,
	SmallText,
	RedirectLink,
	Input,
	Button,
	CheckBox,
	Wrapper,
	FlexBox,
	SmallLink,
	SocialButton,
	Image
} from "./styles/form";

export default function Form({ children, ...restProps }) {
	return <Container {...restProps}>{children}</Container>;
}

Form.Base = function FormBase({ children, ...restProps }) {
	return <Base {...restProps}>{children}</Base>;
};

Form.ErrorMessage = function FormErrorMessage({ children, ...restProps }) {
	return <ErrorMessage {...restProps}>{children}</ErrorMessage>;
};

Form.Title = function FormTitle({ children, ...restProps }) {
	return <Title {...restProps}>{children}</Title>;
};

Form.Text = function FormText({ children, ...restProps }) {
	return <Text {...restProps}>{children}</Text>;
};

Form.SmallText = function FormSmallText({ children, ...restProps }) {
	return <SmallText {...restProps}>{children}</SmallText>;
};

Form.RedirectLink = function FormRedirectLink({ children, ...restProps }) {
	return <RedirectLink {...restProps}>{children}</RedirectLink>;
};

Form.SmallLink = function FormSmallLink({ children, ...restProps }) {
	return <SmallLink {...restProps}>{children}</SmallLink>;
};

Form.Input = function FormInput({ children, ...restProps }) {
	return <Input {...restProps}>{children}</Input>;
};

Form.Button = function FormButton({ children, ...restProps }) {
	return <Button {...restProps}>{children}</Button>;
};

Form.SocialButton = function FormSocialButton({ children, ...restProps }) {
	return <SocialButton {...restProps}>{children}</SocialButton>;
};

Form.Image = function FormImage({ children, ...restProps }) {
	return <Image {...restProps}>{children}</Image>;
};

Form.CheckBox = function FormCheckBox({ children, ...restProps }) {
	return <CheckBox {...restProps}>{children}</CheckBox>;
};

Form.Wrapper = function FormWrapper({ children, ...restProps }) {
	return <Wrapper {...restProps}>{children}</Wrapper>;
};

Form.FlexBox = function FormFlexBox({ children, ...restProps }) {
	return <FlexBox {...restProps}>{children}</FlexBox>;
};
form/index.jsx
  • Tiếp đến là file /pages/signin.jsx, chúng ta code như sau:
import React, { useState, useContext } from "react";
import { useHistory } from "react-router-dom";
import { FirebaseContext } from "../context/firebase";
import { HeaderContainer, FooterContainer } from "../containers/index";
import { Form } from "../components/index";
import * as ROUTES from "../constants/routes";

export default function SignIn() {
	const [email, setEmail] = useState("");
	const [password, setPassword] = useState("");
	const [errorMessage, setErrorMessage] = useState("");

	const { firebase } = useContext(FirebaseContext);
	const history = useHistory();

	// Check form input elements are valid
	const isInvalid = password === "" || email === "";

	// Email & Password
	const handleSignIn = (event) => {
		event.preventDefault();
		//Apply authentication of firebase
		firebase
			.auth()
			.signInWithEmailAndPassword(email, password)
			.then(() => {
				// Redirect to browse page
				history.push(ROUTES.BROWSE);
			})
			.catch((error) => {
				setErrorMessage(error.code);
			});
	};

	return (
		<>
			<HeaderContainer status="hidden">
				<Form>
					<Form.Title>Sign In</Form.Title>
					{errorMessage && (
						<Form.ErrorMessage>{errorMessage}</Form.ErrorMessage>
					)}
					<Form.Base onSubmit={handleSignIn} method="POST">
						<Form.Input
							placeholder="Email or phone number"
							value={email}
							onChange={({ target }) => setEmail(target.value)}
						/>
						<Form.Input
							type="password"
							autoComplete="off"
							placeholder="Password"
							value={password}
							onChange={({ target }) => setPassword(target.value)}
						/>
						<Form.Button disabled={isInvalid} type="submit">
							Sign In
						</Form.Button>
						<Form.FlexBox>
							<Form.Wrapper>
								<Form.CheckBox
									type="checkbox"
									defaultChecked={true}
								/>
								<Form.SmallText style={{ marginTop: "0" }}>
									{" "}
									Remember me
								</Form.SmallText>
							</Form.Wrapper>
							<Form.SmallLink to="/">Need help?</Form.SmallLink>
						</Form.FlexBox>
					</Form.Base>
					<Form.Wrapper
						style={{
							flexDirection: "column",
							alignItems: "flex-start"
						}}
					>
						<Form.SocialButton>
							<Form.Image src="./images/icons/FB-Logo.png" />
							<Form.SmallLink to="/">
								Login with Facebook
							</Form.SmallLink>
						</Form.SocialButton>
						<Form.Text style={{ marginBottom: "0" }}>
							New to Netflix?{" "}
							<Form.RedirectLink to={ROUTES.SIGN_UP}>
								Sign up now
							</Form.RedirectLink>
							.
						</Form.Text>
						<Form.SmallText>
							This page is protected by Google reCAPTCHA to ensure
							you're not a bot.{" "}
							<Form.SmallLink to="/" style={{ color: "#0071eb" }}>
								Learn more
							</Form.SmallLink>
							.
						</Form.SmallText>
					</Form.Wrapper>
				</Form>
			</HeaderContainer>
			<FooterContainer />
		</>
	);
}
signin.jsx

Giải thích một tý cho code của file ở trên nhé:

  • State: Ta có 3 state gồm: email, passworderrorMessage.
  • isInvalid: Đơn giản, chỉ check khi user nhập đầy đủ emailpassword thì mới hiện rõ button Sign In cho người dùng nhấn.
  • handleSignIn(): Function handle việc Sign In, nó sẽ send request lên firebase gồm emailpassword để Sign In. Nếu thành công thì sẽ redirect đến Browse page, ngược lại sẽ show message error lên view.
  • status="signin": Ở phần HeaderContainer, mình có truyền propstatus vào header để check khi redirect sang Sign In page rồi thì ẩn phần select boxbutton Sign In đi.

Trong file headerContainer.jsx ta edit lại như sau:

import React from "react";
import { Header } from "../components/index";
import * as ROUTES from "../constants/routes";

export default function HeaderContainer({ children, status }) {
	return (
		<Header>
			<Header.Frame>
				<Header.Wrapper>
					<Header.Logo
						to={ROUTES.HOME}
						alt="Netflix-clone"
						src="/images/logo.svg"
					/>
					{/* Handle hidden or show is here */}
					{status === "signin" ? (
						""
					) : (
						<Header.WrapBlock>
							<Header.WrapSelect>
								<Header.Select>
									<Header.Option value="/vn-en/">
										English
									</Header.Option>
									<Header.Option value="/vn/">
										Vietnamese
									</Header.Option>
								</Header.Select>
							</Header.WrapSelect>
							<Header.ButtonLink to={ROUTES.SIGN_IN}>
								Sign In
							</Header.ButtonLink>
						</Header.WrapBlock>
					)}
				</Header.Wrapper>
			</Header.Frame>
			{children}
		</Header>
	);
}
headerContainer.jsx

Okie! Sau khi xong, chúng ta sẽ được UI như hình:

UI of the Sign In Page after it is done

Để có thể sử dụng được chức năng Sign In bằng email và password thì ta phải đăng ký với firebase trước, để đăng ký các bạn làm như sau:

  • Step 1.
Step 1
  • Step 2.
Step 2

Kết quả được như hình là đã thành công rồi.

Sign Up Success

Vì ban đầu chưa có Sign Up page để ta đăng ký account do đó ta chưa có thể Sign In lúc này. Tuy nhiên, để test thử việc Sign In bằng email và password với firebase có được hay không, chúng ta sẽ add trực tiếp 1 user vào Authentication của firebase như sau:

Step 1.

Step 1

Step 2: Thêm email và password (Tùy ý các bạn).

Step 2

Kết quả sẽ được như hình.

Successfully added new user

Chúng ta sẽ test thử 2 trường hợp là nhập đúng email, password và sai email hoặc password nhé.

Nhập đúng email và password:

Enter correct email and password
Results when entering correct email and password

Nhập sai email.

Enter incorrect email and password

III. Tổng kết.

Okie! Như vậy là đã xong phần thứ 6 của series này. Trong phần này, mình đã hướng dẫn các bạn setup React Context cho firebase, phân thích cũng như code nên Sign In page hoàn chỉnh, giống 99.99% so với hàng real 😁, setup authentication cho project của chúng ta và test chức năng Sign In. Trong phần tiếp theo, chúng ta sẽ làm Sign Up page, config lại phần router để đáp ứng được vấn đề Authentication và nhiều thứ khác. Cảm ơn các bạn đã đọc 😊, See u again~

Bài viết cùng seri

Bài viết liên quan

ReactJS Tutorial for Beginners Phần 2

Trong bài viết này chúng ta sẽ đi qua năm nội dung đầu tiên của tutorial...

ReactJS Tutorial for Beginners Phần 2
ReactJS vs React Native - Gà cùng một mẹ liệu có giống nhau?

Bạn có đang rối giữa ReactJS vs React Native? Nên chọn cái nào thì tốt hơn? Có thể tái sử dụng code của ReactJS cho React Native hay không? Hãy khám phá câu trả lời thông qua bài viết so sánh ReactJS vs React Native này bạn nhé!...

ReactJS vs React Native - Gà cùng một mẹ liệu có giống nhau?
Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) - Phần 7

Nối tiếp phần 6 của series, trong phần này, chúng ta sẽ bắt tay vào code Sign Up page,config lại phần router để đáp ứng được vấn đề Authentication kèm theo tạo custom hook để listening for Authentication. Không dài dòng nữa, Chúng ta bắt đầu thôi 😁....

Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) - Phần 7
Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) - Phần 5

Trong phần 5 này, chúng ta sẽ hoàn thiện Guest Home Page và config để connect với firebase, chuẩn bị cho các phần tiếp theo nhé 😉....

Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) - Phần 5
Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) - Phần 4

Trong phần 4 của series này, chúng ta sẽ tiếp tục hoàn thiện phần Guest Home Page . Lần này, chúng ta sẽ bắt tay vào code phần FAQs component, thực hiện config router để redirects qua lại các page nhé. Cùng bắt đầu thôi 😉....

Netflix Clone với ReactJS, Styled Components và Firebase (Firestore & Auth) - Phần 4
You've successfully subscribed to 200Lab Blog
Great! Next, complete checkout for full access to 200Lab Blog
Xin chào mừng bạn đã quay trở lại
OK! Tài khoản của bạn đã kích hoạt thành công.
Success! Your billing info is updated.
Billing info update failed.
Your link has expired.