Facebook Pixel

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

05 Dec, 2021

Trong phần này, chúng ta sẽ config React Context firebase cho project của chúng ta, 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 😉!

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

Mục Lục

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
JS
import { createContext } from "react";

export const FirebaseContext = createContext(null);
firebase.js
  • Trong file /src/index.js, ta edit lại như sau:
JSX
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:

JSX
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:
JSX
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:
JSX
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:
JSX
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:

JSX
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 liên quan

Lập trình backend expressjs

xây dựng hệ thống microservices
  • Kiến trúc Hexagonal và ứng dụngal font-
  • TypeScript: OOP và nguyên lý SOLIDal font-
  • Event-Driven Architecture, Queue & PubSubal font-
  • Basic scalable System Designal font-

Đăng ký nhận thông báo

Đừng bỏ lỡ những bài viết thú vị từ 200Lab