Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save nurmdrafi/5b7cb0f6cb33ea85a3757528fb6a49ad to your computer and use it in GitHub Desktop.
Save nurmdrafi/5b7cb0f6cb33ea85a3757528fb6a49ad to your computer and use it in GitHub Desktop.
Authentication with Redux, Next.js + TypeScript

store.ts

import { configureStore } from '@reduxjs/toolkit'
import type { TypedUseSelectorHook } from 'react-redux'
import { useDispatch, useSelector } from 'react-redux'

// Import Reducers
import authReducer from './reducers/authReducer'

const store = configureStore({
	reducer: {
		auth: authReducer,
	},
});

// Declare Typed Definitions
type RootState = ReturnType<typeof store.getState>
type AppDispatch = typeof store.dispatch

export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

export default store;

authActions.ts

import axios from 'axios'
import { createAsyncThunk } from '@reduxjs/toolkit'
import { AUTH } from '../../App.config'

// Import Reducers
import { setIsAuthenticated, setIsAuthenticating, setToken, setUser, setMessage } from '../reducers/authReducer'

// Login Action
export const login = createAsyncThunk('auth/login', async (user: any, { dispatch }) => {

	// Set Is Authenticating `true`
	dispatch( setIsAuthenticating(true) )
  
	try {
	  const res = await axios.post(AUTH.LOGIN, user)
  
	  // If Error or Token Doesn't Exist
	  if(!res?.data?.data) {
		throw new Error('Token Not Found')
	  }
	  
	  const token = res.data.data
  
	  // Validate User By Token
	  dispatch( validateUser(token) )
  
	} catch(err) {
	  // Dispatch `authReducer` Values to Redux Store
	  dispatch( setIsAuthenticated(false) )
	  dispatch( setToken(null) )
	  dispatch( setUser({}) )
	  dispatch( setMessage({type: "error", message: err?.response?.data?.message}) )
  
	  // Set Is Authenticating `false`
	  dispatch( setIsAuthenticating(false) )
	}
})

// Validate User By Token
export const validateUser = createAsyncThunk('auth/validateUser', async (token: any, { dispatch }) => {

	// Set Is Authenticating `true`
	dispatch( setIsAuthenticating(true) )
  
	try {
		
	  // If Token Doesn't Exist
	  if(!token) {
		throw new Error('User Not Found')
	  }
  
	  const res = await axios.get(AUTH.GET_USER, { headers: { Authorization: `Bearer ${ token }` } })

	  // If Error or User Doesn't Exist
	  if(!res?.data?.user) {
		throw new Error('User Not Found')
	  }
  
	  const user = res.data.user
  
	  // Save `token` & `user` to localStorage
	  localStorage.setItem('token', token)
	  localStorage.setItem('user', JSON.stringify(user))
  
	  // Dispatch `authReducer` Values to Redux Store
	  dispatch( setIsAuthenticated(true) )
	  dispatch( setToken(token) )
	  dispatch( setUser(user) )
  
	  // Set Is Authenticating `false`
	  dispatch( setIsAuthenticating(false) )
  
	} catch(err) {
	  console.error(err)
  
	  // Dispatch `authReducer` Values to Redux Store
	  dispatch( setIsAuthenticated(false) )
	  dispatch( setToken(null) )
	  dispatch( setUser({}) )
  
	  // Set Is Authenticating `false`
	  dispatch( setIsAuthenticating(false) )
	}
})

// Logout Action
export const logout = createAsyncThunk('auth/logout', async (e, { dispatch }) => {

	// Set Is Authenticating `true`
	dispatch( setIsAuthenticating(true) )

	// Clear localStorage
	localStorage.clear()

	// Dispatch `authReducer` Values to Redux Store
	dispatch( setIsAuthenticated(false) )
	dispatch( setToken(null) )
	dispatch( setUser({}) )

	// Set Is Authenticating `false`
	dispatch( setIsAuthenticating(false) )
})

authReducer.ts

import { createSlice } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'

interface Message {
  type: string,
  message: string
}

interface authState {
  isAuthenticated: boolean,
  isAuthenticating: boolean,
  token: null | string
  user: object,
  message: Message
}

const initialState: authState = {
  isAuthenticated: false,
  isAuthenticating: true,
  token: null,
  user: {},
  message:{
    type: "",
    message: ""
  },
}

const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    setIsAuthenticated: (state, action: PayloadAction<boolean>) => {
      	state.isAuthenticated = action.payload
    },
    setIsAuthenticating: (state, action: PayloadAction<boolean>) => {
      	state.isAuthenticating = action.payload
    },
    setToken: (state, action: PayloadAction<null | string>) => {
      	state.token = action.payload
    },
    setUser: (state, action: PayloadAction<object>) => {
      	state.user = action.payload
    },
    setMessage: (state, action: PayloadAction<Message>) => {
		state.message = action.payload;
	}
  }
})

export const { setIsAuthenticated, setIsAuthenticating, setToken, setUser, setMessage } = authSlice.actions
export default authSlice.reducer

login.tsx

import React, { useEffect } from 'react'
import { useRouter } from 'next/router'
import Image from 'next/image'
import Link from 'next/link'

// Import Components
import { Form, Input, Button, Row, Col, Spin, Checkbox, message } from 'antd'
import Meta from '../components/common/Meta'

// Import Action, Method & Reducers
import { useAppSelector, useAppDispatch } from '../redux/store'
import { login, validateUser } from '../redux/actions/authActions'
import { setMessage } from '../redux/reducers/authReducer'

// Import Images
import leftCover from '../public/images/auth/login/login-left-cover.svg'
import line from '../public/images/auth/line.svg'

// Import Icons
import emailIcon from '../public/icons/email.svg'

const Login: React.FC = () => {

    // States
    const router = useRouter()
    const dispatch = useAppDispatch()

    // Message States
    const [messageApi, contextHolder] = message.useMessage()

    // Get Data from Redux Store
    const isAuthenticated = useAppSelector(state => state?.auth?.isAuthenticated ?? false)
    const isAuthenticating = useAppSelector(state => state?.auth?.isAuthenticating ?? true)
    const actionMessage = useAppSelector(state => state?.auth?.message ?? {type: null, message: null})

    // Message
    const _message = (type: any, content: string) => {
        messageApi.open({
			type,
			content,
        })
    }  

    // On Load Validate User by Token
    useEffect(() => {
        const token = localStorage.getItem('token')
        dispatch( validateUser(token) )
    }, [])

    // On Load Redirect User if Authenticated
    useEffect(() => {
        if(isAuthenticated && !isAuthenticating) {
          router.push('/dashboard')
        }
    }, [ isAuthenticated, isAuthenticating ])

    // Loading State
    if(isAuthenticated || isAuthenticating) {
        return (
          <div style={ spinContainerStyles as React.CSSProperties }>
            <Spin size='large' />
          </div>
        )
    }

    // On Submit
    const _onSubmit = (values: any) => {
        dispatch(login(values))

        setTimeout(() => {
            if(actionMessage?.type === "error"){
                console.log("error message")
              _message("error", actionMessage?.message)
              setMessage({type: "", message: ""})
            }
        }, 500);
    }

    return (
        <>  
            {contextHolder}
            <Meta title="***" description="***" keywords=""/>
            <div className='login' style={ containerStyles as React.CSSProperties }>
                <Row justify='space-between' align='middle' style={ rowStyles as React.CSSProperties }>
                    {/* Image */}
                    <Col xs={0} sm={0} md={12} lg={14}>
                        <Image style={{width: '100%', maxHeight: '600px'}} src={ leftCover} alt='login-cover'/>
                    </Col>
                    {/* Form */}
                    <Col xs={24} sm={24} md={12} lg={10}>
                        <Form
                            layout='vertical'
                            style={ formStyles as React.CSSProperties }
                            onFinish={ _onSubmit }
                        >
                            <Row gutter={ [16, 16] }>
                                {/* Title */}
                                <h2 style={{ textAlign: 'center' }}>Sign in to ***</h2>

                                {/* Email */}
                                <Col span={24}>
                                    <Form.Item 
                                        name='email'
                                        label='Email'
                                        rules={[
                                            {
                                                required: true,
                                                message: 'This field is required'
                                            }
                                        ]}
                                    >
                                        <Input suffix={ <Image src={ emailIcon } alt='email'/> }/>
                                    </Form.Item>
                                </Col>

                                {/* Password */}
                                <Col span={24}>
                                    <Form.Item
                                        name='password'
                                        label='Password'
                                        rules={[
                                            {   
                                                required: true,
                                                message: 'This field is required'
                                            }
                                        ]}
                                        >
                                        <Input.Password/>
                                    </Form.Item>
                                </Col>

                                {/* Remember Me */}
                                <Col span={24}>
                                    <Form.Item >
                                        <div style={{ display: 'flex', justifyContent: 'space-between' }}>
                                            <Checkbox className='remember'>Remember me</Checkbox>
                                            <Link className='forgot' href='' >Forgot Password</Link>
                                        </div>
                                    </Form.Item>
                                </Col>

                                {/* Submit Button */}
                                <Col span={24}>
                                    <Form.Item>
                                        <Button className='submit' htmlType='submit'>SIGN IN</Button>
                                    </Form.Item>
                                </Col>

                                {/* Divider */}
                                <Col span={24}>
                                <Image style={{ width: '100%' }} src={ line } alt='divider'/>
                                </Col>
                                
                                {/* Signup Link */}
                                <Col span={24} style={ signUpWrapperStyles as React.CSSProperties }>
                                    <p className='text'>{`Don't have an account yet?`}</p> <Link className='signup' href='signup'>Sign Up</Link>
                                </Col>
                            </Row>
                        </Form>
                    </Col>
                </Row>
            </div>
        </>
    )
}

// Styles
const containerStyles = {
    width: '100vw',
    height: '100vh',
    display: 'flex',
    flexDirection: 'column',
    justifyContent: 'center',
    alignItems: 'center',
    overflow: 'auto',
}

const rowStyles = {
    width: '100%', 
    height: '100%', 
    padding: '1rem'
}

const formStyles = {
    display: 'flex',
    flexDirection: 'column',
    justifyContent: 'center',
    alignItems: 'center',
    maxHeight: '600px',
    maxWidth: '450px',
    borderRadius: '20px',
    boxShadow: '0px 4px 24px -1px rgba(117, 117, 117, 0.25)',
    backdropFilter: 'blur(22.5px)',
    padding: '20px',
    margin: '0 auto'
}

const spinContainerStyles = {
    width: '100%',
    height: '100%',
    display: 'flex',
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center'
}

const signUpWrapperStyles = {
    display: 'flex', 
    justifyContent: 'center', 
    alignItems: 'center', 
    gap: '10px'
}

export default Login
@Santhosh-jackmeow2009
Copy link

';LJHGTFREWQ

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment