Search by

    Terakhir diperbaharui: Jan 1, 2021

    Implementasi sistem autentikasi - Logout

    Penambahan fitur logout ini dilakukan di sisi client dan bukan server.

    Setelah logout item 'user' di dalam localStorage akan dihapus dan state isLoggedIn diubah ke false.

    Step by step

    Membuat menu Logout

    Kita ingin tampilan dari aplikasi DinoTes dibuat se-simple mungkin baik ketika diakses dari desktop, laptop ataupun smartphone.

    Hanya ada 3 menu yang ditampilkan pada halaman utama yaitu manambah note baru, search bar & sorting.

    Lalu dimana kita tempatkan menu logout?

    Menu logout akan muncul ketika user klik logo DinoTes, seperti ini:

    Bagi kamu yang sudah belajar UI/UX pendekatan ini mungkin adalah pendekatan yang tidak popular karena menyalahi Jakob's Law.

    Tapi tujuan dari menempatkan menu dibalik logo adalah agar aplikasi tetap terlihat simple.

    Buat sebuah file baru bernama Modal.js di dalam folder components/ui, kemudian salin code berikut ini:

    src/components/ui/Modal.js

    1import React from 'react';
    2import tw from 'twin.macro';
    3import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
    4import { faTimes } from '@fortawesome/free-solid-svg-icons';
    5import logo from '../../assets/images/header-logo.png';
    6
    7const Container = tw.div`fixed z-10 inset-0 overflow-y-auto`;
    8const ModalWrapper = tw.div`flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0`;
    9const BackgroundOverlayWrapper = tw.div`fixed inset-0 transition-opacity`;
    10const BackgroundOverlay = tw.div`absolute inset-0 bg-gray-500 opacity-75`;
    11const Span = tw.span`hidden sm:inline-block sm:align-middle sm:h-screen`;
    12const Content = tw.div`inline-block bg-white rounded-lg overflow-hidden shadow-xl transform transition-all sm:my-8 sm:max-w-lg sm:w-full`;
    13const Body = tw.div`inline-block bg-white p-4 m-6 text-center sm:p-6`;
    14const CloseButtonWrapper = tw.div`flex justify-end`;
    15const CloseButton = tw.button`bg-white m-4`;
    16const ImgWrapper = tw.div`flex items-center justify-center`;
    17const Img = tw.img`py-4`;
    18const BodyText = tw.p`text-lg text-gray-900`;
    19const Footer = tw.div`bg-gray-50 p-4 items-center justify-center sm:p-6 sm:flex sm:flex-row`;
    20const FooterText = tw.button`text-red-700 font-bold`;
    21
    22const Modal = (props) => {
    23 const username = useSelector((state) => state.user.user.username);
    24 const { close } = props;
    25
    26 const handleClick = () => {
    27 close();
    28 };
    29
    30 return (
    31 <Container>
    32 <ModalWrapper>
    33 <BackgroundOverlayWrapper>
    34 <BackgroundOverlay />
    35 </BackgroundOverlayWrapper>
    36 <Span aria-hidden="true" />
    37 <Content>
    38 <CloseButtonWrapper>
    39 <CloseButton onClick={handleClick}>
    40 <FontAwesomeIcon icon={faTimes} />
    41 </CloseButton>
    42 </CloseButtonWrapper>
    43 <Body>
    44 <ImgWrapper>
    45 <Img src={logo} alt="logo" />
    46 </ImgWrapper>
    47
    48 <BodyText>Hi {username}</BodyText>
    49 </Body>
    50 <Footer>
    51 <FooterText>Logout</FooterText>
    52 </Footer>
    53 </Content>
    54 </ModalWrapper>
    55 </Container>
    56 );
    57};
    58
    59export default Modal;

    Kemudian update file Header.js karena logo terletak di dalam Header.

    src/components/shared/Header.js

    1import React, { useState } from 'react';
    2import tw from 'twin.macro';
    3import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
    4import { faFile } from '@fortawesome/free-solid-svg-icons';
    5import { Link } from 'react-router-dom';
    6import Button from '../ui/Button';
    7import Modal from '../ui/Modal';
    8import logo from '../../assets/images/header-logo.png';
    9
    10const Navigation = tw.div`flex justify-between items-center border-b-2 border-gray-100 py-6 md:justify-start md:space-x-3`;
    11const Img = tw.img`h-14 w-auto sm:h-16`;
    12const Heading = tw.h2`invisible text-xl font-bold text-gray-900 md:visible`;
    13const Menu = tw.div`md:flex items-center justify-end md:flex-1 lg:w-0`;
    14
    15const Header = () => {
    16 const [visible, setVisible] = useState(false);
    17
    18 const handleClick = () => {
    19 setVisible(!visible);
    20 };
    21
    22 return (
    23 <>
    24 {visible && <Modal close={handleClick} />}
    25 <Navigation>
    26 <Img src={logo} alt="logo" onClick={handleClick} />
    27 <Heading>DinoTes</Heading>
    28 <Menu>
    29 <Link to="/add">
    30 <Button>
    31 <FontAwesomeIcon icon={faFile} />
    32 &nbsp;&nbsp; New Note
    33 </Button>
    34 </Link>
    35 </Menu>
    36 </Navigation>
    37 </>
    38 );
    39};
    40
    41export default Header;

    Function Logout

    Buat function untuk logout di file userSlice.js:

    1...
    2const userSlice = createSlice({
    3 name: 'user',
    4 initialState,
    5 reducers: {
    6 statusReset(state, action) {
    7 state.status = 'idle';
    8 },
    9 logout(state, action) {
    10 localStorage.removeItem('user');
    11 state.isLoggedIn = false;
    12 state.user = null;
    13 }
    14 },
    15 extraReducers: {
    16 ...
    17 }
    18});
    19
    20export const { statusReset, logout } = userSlice.actions;
    21
    22export default userSlice.reducer;

    Kemudian hubungkan dengan component Modal.js:

    1...
    2import { useSelector, useDispatch } from 'react-redux';
    3import { logout } from '../../features/user/userSlice';
    4
    5...
    6
    7const Modal = (props) => {
    8 const dispatch = useDispatch();
    9 const username = useSelector((state) => state.user.user.username);
    10 const { close } = props;
    11
    12 const handleClick = () => {
    13 close();
    14 };
    15
    16 const handleLogout = () => {
    17 dispatch(logout());
    18 window.location.reload();
    19 };
    20
    21 return (
    22 <Container>
    23 <ModalWrapper>
    24 <BackgroundOverlayWrapper>
    25 <BackgroundOverlay />
    26 </BackgroundOverlayWrapper>
    27 <Span aria-hidden="true" />
    28 <Content>
    29 <CloseButtonWrapper>
    30 <CloseButton onClick={handleClick}>
    31 <FontAwesomeIcon icon={faTimes} />
    32 </CloseButton>
    33 </CloseButtonWrapper>
    34 <Body>
    35 <ImgWrapper>
    36 <Img src={logo} alt="logo" />
    37 </ImgWrapper>
    38
    39 <BodyText>Hi {username}</BodyText>
    40 </Body>
    41 <Footer>
    42 <FooterText onClick={handleLogout}>Logout</FooterText>
    43 </Footer>
    44 </Content>
    45 </ModalWrapper>
    46 </Container>
    47 );
    48};
    49
    50export default Modal;

    Hasil Akhir



    Data owner

    Satu langkah tambahan yang tidak kalah penting adalah menambah satu field untuk menyimpan data pemilik note.

    Hal ini dikarenakan tidak mungkin note bersifat shared yang berarti semua user dapat melihat semua note yang ditambahkan oleh user lain.

    Data note harus bersifat private, hanya bisa diakses oleh user yang menambah note tersebut.

    Kita update file handler.js pada handler addNewNote dan getAllNotes.

    handler.js

    1...
    2exports.addNote = async (req, res, next) => {
    3 try {
    4 const db = getDb();
    5 const { title } = req.body;
    6
    7 if (!title) {
    8 logger.error(`${req.originalUrl} - ${req.ip} - title is missing `);
    9 throw new Error('title is missing');
    10 }
    11
    12 const data = {
    13 ...req.body,
    14 createdAt: new Date(Date.now()).toISOString(),
    15 updatedAt: new Date(Date.now()).toISOString(),
    16 username: req.user.username
    17 };
    18
    19 // Insert data to collection
    20 const result = await db.collection('notes').insertOne(data);
    21
    22 const objResult = JSON.parse(result);
    23
    24 logger.info(`${req.originalUrl} - ${req.ip} - Data successfully saved`);
    25
    26 res.status(200).json({ message: 'Data successfully saved', _id: objResult.insertedId });
    27 } catch (error) {
    28 logger.error(`${req.originalUrl} - ${req.ip} - ${error} `);
    29 next(error);
    30 }
    31};
    32
    33exports.getAllNotes = async (req, res, next) => {
    34 try {
    35 const db = getDb();
    36 // find all Notes
    37 const result = await db.collection('notes').find({ username: req.user.username }).sort({ _id: -1 }).toArray();
    38
    39 logger.info(`${req.originalUrl} - ${req.ip} - All notes retrieved`);
    40
    41 res.status(200).json(result);
    42 } catch (error) {
    43 logger.error(`${req.originalUrl} - ${req.ip} - ${error} `);
    44 next(error);
    45 }
    46};
    47...