728x90
반응형

로그인 회원가입을 위한 페이지를 세팅해보자. 먼저 index.tsx 파일을 수정해준다.

 

index.tsx

import type { NextPage } from 'next'
import Head from 'next/head'
import Image from 'next/image'
import Link from 'next/link'
import styles from '../styles/Home.module.css'

const Home: NextPage = () => {
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>
          Welcome to my Site!
        </h1>

        <div className={styles.grid}>
          <Link href={{
            pathname: '/auth/login'
          }}>
            <a className={styles.card}>
              <p>
                Login
              </p>
            </a>
          </Link>
          <Link href={{
            pathname: '/auth/signup'
          }}>
            <a className={styles.card}>
              <p>
                Signup
              </p>
            </a>
          </Link>
        </div>
      </main>

      <footer className={styles.footer}>
        <p>
        copyright © Teepo
        </p>
      </footer>
    </div>
  )
}

export default Home

 

실행해본다.

 

간단하게 Login, Signup 버튼이 나오고, Link href 옵션에 '/auth/login' 과 '/auth/signup' 을 써주었다.

 

이제 우린 auth라는 파일을 만들고 SSR로 받아오는 데이터를 읽어 어떤 컴포넌트를 보여줄 것인지 정의할 것이다. 

 

 

경로는 /auth/[cate].tsx가 되고, [cate].tsx 부분에 무슨 문자열이 오는지에 따라 다른 컴포넌트를 보여줄 것이다.

 

지금 상태에서 웹을 확인해보면

 

이런 문구가 뜰 것이다. [cate].tsx 파일을 수정해보자.

 

/pages/auth/[cate].tsx

import { GetServerSideProps, GetStaticPaths, GetStaticProps } from 'next'
import type { NextPage } from 'next'

interface Props {
    CateData : string
}

const Auth:NextPage<Props> = ({CateData}) => {
    console.log(CateData)
    return (
        <div className='auth'>
            {(CateData === 'login') ? "Login" : ""}
            {(CateData === 'signup') ? "Signup" : ""}
        </div>
        )
}


export default Auth;


export const getServerSideProps:GetServerSideProps = async (context) => {
    const CateData = context.query.cate;
    return {
        props: {
            CateData
        }
    }
}

 

getServerSideProps로 받아온 데이터의 cate 값을 읽어 어떤 문자열이 들어오는지 읽고,

삼항연산자로 문자열에 따라 보여줄 내용을 정의했다. 나중에 css 추가를 위해 className에 'auth'를 추가해주었다.

 

 

물론 각각 다른 파일들로 만드는 방법도 있다. 하지만 나는 로그인, 회원가입에서 Header를 같이 쓰고싶었기 때문에 

이렇게 구조를 정의했다.

 

이번엔 Component들을 만들어보자. 루트경로에 src 폴더와 하위에 component 폴더를 만들고 아래 파일들을 작성한다.

 

LoginComponent.tsx

import type { NextPage } from 'next'

interface Props {

}

const Login:NextPage<Props> = () => {
    return (
        <div className='auth'>
            Login
        </div>
        )
}


export default Login

 

SignupComponente.tsx

import type { NextPage } from 'next'

interface Props {

}

const Signup:NextPage<Props> = () => {
    return (
        <div className='auth'>
            Signup
        </div>
        )
}


export default Signup

 

이제 [cate].tsx 파일에서 컴포넌트를 사용해보자.

 

[cate].tsx

import { GetServerSideProps, GetStaticPaths, GetStaticProps } from 'next'
import type { NextPage } from 'next'
import LoginComponent from '../../src/components/LoginComponent'
import SignupComponent from '../../src/components/SignupComponent'


interface Props {
    CateData : string
}

const Auth:NextPage<Props> = ({CateData}) => {
    return (
        <div className='auth'>
            {CateData} page
            {(CateData === 'login') ? <LoginComponent /> : ""}
            {(CateData === 'signup') ? <SignupComponent /> : ""}
        </div>
        )
}


export default Auth;


export const getServerSideProps:GetServerSideProps = async (context) => {
    const CateData = context.query.cate;
    return {
        props: {
            CateData
        }
    }
}

 

Header에 쓰일 부분에 임의로 어떤 페이지인지 나타내주는 문구를 작성했다.

컴포넌트들이 잘 나타나는지 확인해본다.

 

 

 

728x90
반응형
728x90
반응형

나중에 프론트 쪽에서 할 상태관리를 위해 front폴더에 세팅을 해준다.

자세한 내용은 아래 링크에 접속하면 확인 가능하다.

https://typo.tistory.com/entry/Nextjs-next-redux-wrapper

 

Next.js | next-redux-wrapper

NextJS에서 상태를 관리하기 수월하게 해주는 redux를 TypeScript와 함께 사용해보자. 이 포스트는 counter에 대한 state를 다루는 포스트이다. 사전 준비 create-next-app으로 프로젝트를 생성한다. $ npx create.

typo.tistory.com

 

로그인에 관한 기본적인 세팅만 해주겠다.

 

1. 먼저 필요한 npm 모듈들을 설치하자.

$ npm i react-redux redux next-redux-wrapper redux-thunk @types/redux-promise @types/redux-logger
$ npm i redux-devtools-extension redux-logger --dev-save

 

2. 루트 디렉토리에 store폴더를 생성하고 아래와 같이 구조를 만들어준다.

3. 그다음 아래 코드들을 입력해준다.

user.interfaces.ts

export interface UserState {
    userInfo : UserInfo;
    islogined : boolean;
    loginError : '';
    signupDone : boolean;
    signupError : '';
    idDuple : boolean;
    emailDuple : boolean;
    emailAuth : boolean;
    data : any;
}

export interface UserInfo {
    id : string;
    pw : string;
    email : string;
    address : string;
}

입력값으로 쓰일 User interface와 함수실행 후 반환값으로 받을 값들을 선언해준다.

마지막 data 속성은 차후에 payload로 데이터를 받기위한 저장공간이다.

UserStateUser를 포함한다.

 

 

userAct.interfaces.ts

export enum actionTypesUser {
    USER_INIT = "USER_INIT", // state 초기화
}

export type ActionsUser = UserInit 

export interface UserInit {
    type : actionTypesUser.USER_INIT;
    data : any;
}

아직은 UserState를 초기화하는 actionType 밖에 없지만 차근차근 늘려나갈 것이다.

 

 

 

/store/interfaces/index.ts

export * from './user/user.interfaces'
export * from './user/userAct.interfaces'

4. user 인터페이스와 액트를 export한다.

 

/store/interfaces/RootState.ts

import { UserState } from './index'

export interface RootStateInterface {
    user : UserState;
}

5. UserState를 가져와 RootState에 넣어준다. ( 이곳은 모든 State를 포함합니다. )

 

/store/reducer/index.ts

import { combineReducers, Reducer, AnyAction } from "redux";
import { RootStateInterface } from "../interfaces/RootState";
import user from "./user_reducer";

const rootReducer: Reducer<
	RootStateInterface,
	AnyAction
> = combineReducers<RootStateInterface>({
	user,
});

export default rootReducer;
export type RootState = ReturnType<typeof rootReducer>;

RootState 폴더에 선언한 State들을 가져와서 Reducer와 엮어준다.

 

 

/store/reducer/user_reducer.ts

import { HYDRATE } from "next-redux-wrapper";
import { getFontDefinitionFromManifest } from "next/dist/server/font-utils";
import {
    UserState,
    UserInfo,
    actionTypesUser,
    ActionsUser
} from "../interfaces";

export const initialState : UserState = {
    userInfo : <UserInfo>{
        id : "",
        pw : "",
        email : "",
        address : ""
    },
    islogined : false,
    loginError : '',
    signupDone : false,
    signupError : '',
    idDuple : false,
    emailDuple : false,
    emailAuth : false,
    data : {}
}

interface HydratePayload {
    user : UserState
}

const user = (
    state = initialState,
    action : ActionsUser | { type : typeof HYDRATE; payload : HydratePayload }
) : UserState => {
    switch (action.type) {
        case HYDRATE:
            return { ...state, ...action.payload.user};
        case actionTypesUser.USER_INIT:
            return {
                ...state,
                userInfo : initialState.userInfo
            }
        default:
            return state
    }
}

export default user;

userinitialState를 정의해주고 dispatch가 실행됐을 때의 로직을 정의한다.

나중엔 action이 추가됨에 따라 reducer의 case도 늘어날 것이다.

 

 

/store/index.ts

import { createStore, applyMiddleware, compose } from "redux";
import { createWrapper } from "next-redux-wrapper";
import promiseMiddleware from 'redux-promise';
import ReduxThunk from 'redux-thunk';
import rootReducer from "./reducer";

const createStoreWithMiddleware = applyMiddleware(promiseMiddleware, ReduxThunk)(createStore)

const configureStore = () => {
	const store = createStoreWithMiddleware (rootReducer);
	return store;
};

const wrapper = createWrapper(configureStore, { debug: true });

export default wrapper;

store를 생성하고 _app.tsx에 쓰일 wrapper를 만들어준다.

 

 

/pages/_app.tsx

import { AppProps } from "next/app";
import { NextPage } from "next";
import wrapper from "../store"; // store.ts 파일

const MyApp: NextPage<AppProps> = ({ Component, pageProps }: AppProps) => {
	return (
		<>
			<Component {...pageProps} />
		</>
	);
};

export default wrapper.withRedux(MyApp);

_app.tsx 파일의 MyApp을 만들었던 wrapper로 감싸주면 store 설정이 끝난다.

 

마지막으로 tsconfig.json에 store부분을 추가해준다.

 

tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve"
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx","store/**/*.ts"],
  "exclude": ["node_modules"]
}
728x90
반응형
728x90
반응형

1. 미들웨어는 이런식으로 사용이 된다.

// 로그인
router.get('/login', isLoggedIn, (req, res) => {
  res.render('login');
});

 - 라우터가 실행될 때, 그전에 먼저 실행되서 어떻게 할건지를 설정해준다. ( 예를들어 로그인이 안되어있을 경우 이런 페이지로 가겠다. ) 사실 라우터마다 설정들을 다 해줄 순 있지만, 굳이 반복되는 코드를 여러번 쓰는것보단 미들웨어로 한번에 처리하면 사용하기에도 편리하다.

 

먼저, middleware.js라는 파일을 만들고 exports 되는 함수들을 정의한 다음에

const { isLoggedIn, isNotLoggedIn, DataSet, agentDevide } = require('./middleware');

이런 식으로 미들웨어 사용이 필요한 라우터 마다  require로 정의를 해주었다.

 

 

 

2. 나는 이런식으로 로그인이 될 경우에 메인화면으로 오게 만들었고,

exports.isLoggedIn = (req, res, next) => {
  try {
    req.decoded = jwt.verify(req.cookies.token, secretObj.secret);
    res.redirect('/main');
  } catch(err) {
    next();//Token 만료시 다시 로그인 페이지로 넘어가게 설정
  }
};

 

3. 모든 페이지마다 로그인이 안되어있을 경우에 루트 페이지로 가도록 설정했다.

exports.isNotLoggedIn = (req, res, next) => {
  try {
    req.decoded = jwt.verify(req.cookies.token, secretObj.secret);
    next();   
  } catch(err) {
    res.redirect('/') ;//Token 만료시 다시 로그인 페이지로 넘어가게 설정
  }
};
728x90
반응형
728x90
반응형

화면 구성

※ 본사 지점은 무시하셔도 됩니다.

 

1. html 코드

 기본적으로 사업자번호(아이디)와 비밀번호를 입력하도록 설계하였다.

<form action="" method="post" id="login-form2" class="margin-bottom-0" onsubmit="login('login-form2','CNU2','ANA2','PW2');">
<!--사업자번호-->
  <div class="form-group m-b-20">
  <input id="CNU2" name = "CNU2" type="text" class="form-control form-control-lg inverse-mode" placeholder="{{__('business_number')}}" required />
  </div>
<!--비밀번호-->
  <div class="form-group m-b-20">
  <input id="PW2" name = 'PW2' type="password" class="form-control form-control-lg inverse-mode" placeholder="{{__('pw')}}" required />
  </div>
<!--버튼-->
  <div class="login-buttons">
  <button type="submit" class="btn btn-primary btn-block btn-lg">{{__('login')}}</button> 
  <li class="form-control-lg inverse-mode"><a href="/register" >{{__('signup')}}</a>   /  <a href="/find" > {{__('findpw')}}</a></li>
  </div>
</form>

 

2. script부분에서 input을 parsley로 형식을 검사한 후에 loginAjax를 실행하도록 하였다.

function login(form, companyNumber, agentInfo, password) {
    var CNU = document.getElementsByName(companyNumber)[0].value;
    var ANU = document.getElementsByName(agentInfo)[0].value.split("/")[0];
    var ANA = document.getElementsByName(agentInfo)[0].value.split("/")[1];
    var PW = document.getElementsByName(password)[0].value;
    var validateCNU = $('#'+form).parsley();

    if(validateCNU.isValid() == true) {
    	loginAjax(CNU,ANU,ANA,PW);
    }
    event.preventDefault();
  }

 

3. loginAjax() 함수로 백엔드에 데이터를 보낸 후 성공할 경우 어떻게 할건지를 정의한다.

// 로그인 기능
function loginAjax(companyNumber, agentNumber, agentName, password) {
	$.ajax({
		type: 'POST',
		url: 'auth/login',
		dataType: 'json',
		data: {
			CNU: companyNumber,
			ANU: agentNumber,
			ANA: agentName,
			PW: password
		}
	}).done(function(data) {
		if(data.result == 'success') {
			location = '/main';
		}
		else if(data.result == 'fail') {
			alert(i18nconvert('login_error'));
		}
		else {
			alert(i18nconvert('login_fail'));
		}
	});
}

 

3. 백엔드쪽에서 올바른 데이터가 들어왔을 경우 로그인 정보를 쿠키에 담아준다.

//login 진행 할경우 토큰 만들어서 cookie에 넣음
router.post("/login", async(req, res, next) => {
  const {CNU, ANU, ANA, PW} = req.body;
  try {
    const company = await modelQuery(QUERY.Findone,COLLECTION_NAME.Company,{ "CNU" : CNU+ANU, "ANA" : ANA, "ANU" : ANU },{});// CNU에 맞는 데이터 찾아오기
    if(company) {
      //bcrypt 암호화된 PW와 입력 PW 비교
      if(bcrypt.compareSync(PW, company.PW) ){
        const token = jwt.sign({ CNU : company.CNU, ANU : company.ANU, ANA : company.ANA, CNA : company.CNA, CID : company._id, AH : company.AH },// 토큰의 내용(payload)
          secretObj.secret,   // 비밀 키
          { expiresIn: '1440m' });  // 유효 시간은 1440분 하루 설정
        res.cookie("token", token); // 쿠키에 token 등록
        
        return res.send({ result : 'success' });
      }
      else{
        return res.send({ result : "fail" });
      }
    }
    else {
      return res.send({ result : "fail" });
    }
  } catch(err){
    res.send({ result : "fail" });
    console.error(err);
    next(err);
  }
});

 

 

페이지마다 미들웨어를 설정해서 로그인된 정보가 쿠키에 없을 경우에 로그인 화면이나 원하는 화면으로 돌아가도록

설정을 해주면 좋다!

728x90
반응형

+ Recent posts