728x90
반응형

Password

비밀번호와 비밀번호 확인부터 해보자.

 

비밀번호 확인란 텍스트 state를 만들어준다.

 

    const [pwdCertText,setPwdCertText] = useState<string>('');

 

비밀번호 확인에 관한 state는 이 페이지 안에서만 사용될 것이다.

 

그다음 다음과 같이 코드를 작성해준다.

 

(...)
  <div>
      <input 
          style={{ borderColor : (userState.userInfo.pw.length < 4) ? 'red' : 'lightgreen' }}
          name="pw"
          type="password"
          placeholder="Enter PW"
          onChange={userChangeHandler} />
      <p style={{color : 'red', fontSize: 5}}>
          {(userState.userInfo.pw.length < 4) ? '4글자 이상 입력해주세요' : ''}
      </p>
  </div>
  <div>
      <input 
          style={{ borderColor : (userState.userInfo.pw !== pwdCertText) ? 'red' : 'lightgreen' }}
          name="pw2"
          type="password"
          placeholder="Verify password"
          value={pwdCertText}
          onChange={(event) => { setPwdCertText(event.target.value)}}/>
      <p style={{color : 'red', fontSize: 5}}>
          {(userState.userInfo.pw !== pwdCertText) ? '비밀번호가 다릅니다' : ''}
      </p>
  </div>
(...)

 

비밀번호 입력칸은 userState.userInfo.pw 안에 들어갈 것이고 비밀번호 확인 입력칸은 pwdCertText 안에 들어갈 것이다.

  • 각각 input의 border를 검증이 완료되지 않으면 빨간색으로 지정한다.
  • userState.userInfo.pw의 값이 4보다 작을 땐 '4글자 이상 입력해주세요' 문구를 나타내준다.
  • pwdCertText의 값이 userState.userInfo.pw 의 값과 다르면 '비밀번호가 다릅니다' 문구를 나타내준다.
  • 모든 조건이 성립하면 border의 색깔을 lightgreen으로 바꾼다.

화면구성을 보자.

 

 

4글자 입력은 했지만 비밀번호 확인 텍스트와 다를 경우 :

 

 

두가지 모두 성립할 경우 :

 

 

이 밖에도 숫자, 영문, 특수문자 조합 등 다양한 조건으로 validation 을 설정할 수 있다.

 

Email

이메일 전송 방식은 

  1. 이메일을 입력하고 보내기 버튼을 누른다.
  2. 만약 이메일이 중복되면 아무것도 실행되지 않는다.
  3. NestJS에서 입력한 주소로 이메일을 보내준다.
  4. 보내기 버튼이 재전송 버튼으로 바뀌게되고 남은 시간이 보여진다.
  5. 재전송 버튼을 눌렀을 경우 남은 시간이 reset 된다.
  6. 인증번호 입력칸과 확인버튼이 생성된다.
  7. 확인 버튼을 눌러서 인증번호가 확인되면 확인버튼도 비활성화된다.
  8. 남은 시간이 만료되면 인증번호 입력칸과 확인버튼이 사라지고 보내기버튼이 활성화된다.

 

이메일 전송에 필요한 state를 선언해준다.

    const [emailsendOK,setEmailSendOK] = useState<boolean>(false); // email send ( 인증번호 입력칸과  확인버튼 활성화 )

 

 

이메일 보내기 버튼에 onClick을 추가하고 input과 button의 disabled 옵션에 useState.emailAuth를 넣어서 인증이 완료되면 비활성화 되게 한다.

 

<div>
    <input 
        name="email"
        type="email"
        placeholder="Enter Email"
        onChange={userChangeHandler}
        disabled={userState.emailAuth}/>
        
    <button
    	type="button"
    	onClick={emailSend}
        disabled={userState.emailAuth}>
    보내기    
    </button>
</div>

 

emailSend 함수를 만들어준다.

const emailSend = () => {
}

아이디 중복할 때와 비슷하게 이메일 중복확인, 이메일 보내기를 act, type, action, reducer를 만들고 실행해줄 것이다.

 

act와 type을 만들어준다.

userAct.interfaces.ts

export enum actionTypesUser {
    USER_INIT = "USER_INIT", // state 초기화
    USER_STATE = "USER_STATE", // userState 변경
    USER_INFO = "USER_INFO",  // userInfo 변경 
    USER_ID_DUPLICATE = "USER_ID_DUPLICATE", // ID 중복 검사
    USER_EMAIL_DUPLICATE = "USER_EMAIL_DUPLICATE", // Email 중복검사
    USER_EMAIL_SEND = "USER_EMAIL_SEND" // Email 보내기
}

export type ActionsUser = UserInit | UserState | UserInfo | UserIdDuplicate
                         | UserEmailDuplicate | UserEmailSend

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

export interface UserState {
    type : actionTypesUser.USER_STATE;
    data : any;
}

export interface UserInfo {
    type : actionTypesUser.USER_INFO;
    data : any;
}

export interface UserIdDuplicate {
    type : actionTypesUser.USER_ID_DUPLICATE;
    payload : any;
}

export interface UserEmailDuplicate {
    type : actionTypesUser.USER_EMAIL_DUPLICATE;
    payload : any;
}

export interface UserEmailSend {
    type : actionTypesUser.USER_EMAIL_SEND;
    payload : any;
}

 

리듀서에 추가해준다.

user_reducer.ts

        case actionTypesUser.USER_EMAIL_DUPLICATE:
            return {
                ...state, data : action.payload
            }
        case actionTypesUser.USER_EMAIL_SEND:
            return {
                ...state, data : action.payload
            }

 

action을 만들어준다.

user_action.ts

export async function userEmailDuplicate (email : string) {
    const request = Axios.get('/api/email/'+email)
    .then(response => response.data)
    
    return {
        type : actionTypesUser.USER_ID_DUPLICATE,
        payload: request
    }
}

export async function userEmailSend (email : string) {
    const request = Axios.post('/api/email/send',{email : email})
    .then(response => response.data)

    return {
        type : actionTypesUser.USER_EMAIL_SEND,
        payload: request
    }
}

axios post 메소드로 백엔드에 데이터를 보냈다.

 

다시  SignupComponent.tsx 파일로 돌아와 emailSend 함수를 수정한다.

 

SignupComponent.tsx

    const emailSend = () => {
        if(userState.userInfo.email !=='' && userState.userInfo.email.indexOf('@') !== -1) { // 입력된 값이 존재하고 email 형식에 맞을경우
            dispatch(userEmailDuplicate(userState.userInfo.email)).then((req : any) => {
                if(!req.payload.result) { // 이메일이 이미 존재할 때
                    alert('해당 이메일이 이미 존재합니다.');
                }else {
                        dispatch(userEmailSend(userState.userInfo.email)).then((req : any) => {
                            if(req.payload.result) { // 이메일 보내기 성공했을 때
                                setEmailSendOK(true)
                                // 성공했을 경우 로직
                            }else {
                                alert('이메일 전송이 실패됐습니다.')
                            }
                        });
                }
            })
        }
    }

이메일이 존재하는지 확인하고, 중복되지 않을 경우에 

  1. 인증번호에 대한 쿠키를 시간을 설정해서 bcrypt로 암호화한 후 생성해준다.
  2. 보내기 버튼을 재전송 버튼으로 바꾼다.
  3. 재전송 버튼을 눌렀을 때 남은시간은 리셋된다.
  4. 하단에 인증번호 입력칸과 확인버튼을 만든다.
  5. 남은시간이 0이되면 인증번호 입력칸과 확인버튼이 사라지고 재전송이 보내기로 바뀐다.

 

이메일 중복 여부부터 다음 포스터에서 다루겠다.

 

 

 

 

 

 

728x90
반응형
728x90
반응형

전 포스트에서 NextJS 프론트에서 데이터를 요청하는것 까지 해보았다. 이번엔 백엔드를 설계해보자.

먼저 해당 포스트로 접속해 mongoose 까지 설치해준다.

 

https://typo.tistory.com/entry/Nestjs-MongoDB-Schema?category=895068 

 

Nest.js | MongoDB | Schema

이 글은 NestJS document 사이트 기반으로 작성했습니다. https://docs.nestjs.kr/techniques/mongodb 네스트JS 한국어 매뉴얼 사이트 네스트JS 한국, 네스트JS Korea 한국어 매뉴얼 docs.nestjs.kr 몽고DB설치하..

typo.tistory.com

 

- mongoose 설치

 

$ npm install --save @nestjs/mongoose mongoose

 

혹시 기존에 생성했던 apple 리소스가 있다면 삭제해준다.

app.module.ts 를 다음과 같이 수정한다.

 

app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [MongooseModule.forRoot('mongodb://test:test1234@localhost:27017/admin')],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

 

user 리소스를 생성해준다.

$ nest g res apple

 

app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MongooseModule } from '@nestjs/mongoose';
import { UserModule } from './user/user.module';

@Module({
  imports: [MongooseModule.forRoot('mongodb://test:test1234@localhost:27017/admin'), UserModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

그럼 이렇게 파일들과 자동으로 import되는것을 볼 수 있다.

 

schemas 폴더를만들고 user.schema.ts 파일을 작성해준다.

 

/src/user/schemas/user.schemas.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type UserDocument = User & Document;

@Schema()
export class User {
  @Prop()
  id: string;
  
  @Prop()
  pw: string;
  
  @Prop()
  email: string;

  @Prop()
  address: string;
}

export const UserSchema = SchemaFactory.createForClass(User);

 

user.module.tsuserSchema에 관한 내용을 import해준다. 여기서 id부분은 자동으로 1씩 더해서 추가해주기위해 임의로 정의한 것이다.

 

user.module.ts

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { User, UserSchema } from './schemas/user.schema';

@Module({
  imports: [MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])],
  controllers: [UserController],
  providers: [UserService]
})
export class UserModule {}

 

우리는 일단 아이디 중복 확인을 위해 다음 @Get(':id') 데코레이터만 쓸 것이다. 우리는 사용자 id를 string으로 받을 것이기 때문에 수정해주자.

 

user.controller.ts

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.userService.findOne(id);
  }

 

service 부분에 mongoose schema를 추가한다.

 

user.service.ts

import { Injectable } from '@nestjs/common';
//mongoose
import { Model } from 'mongoose';
import { InjectModel } from '@nestjs/mongoose';
import { User, UserDocument } from './schemas/user.schema';
//dto
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Injectable()
export class UserService {
  constructor(@InjectModel(User.name) private userModel: Model<UserDocument>) {}

  findOne(id: string) {
    return `This action returns a #${id} user`;
  }

}

 

 

findOne 함수를 async 함수로 바꿔주고( mongoose Query은 Promise 함수입니다. ) 지금은 중복확인으로 result 값만 필요하지만 나중에 로그인 할 때 이 함수를 써서 유저의 정보를 받아와야하기 때문에 id에 맞는 유저의 data가 존재할 때 user도 result와 함께 반환해준다.

 

user.service.ts

import { Injectable } from '@nestjs/common';
//mongoose
import { Model } from 'mongoose';
import { InjectModel } from '@nestjs/mongoose';
import { User, UserDocument } from './schemas/user.schema';
//dto
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Injectable()
export class UserService {
  constructor(@InjectModel(User.name) private userModel: Model<UserDocument>) {}

  async findOne(id: string) {
    const userOne = await this.userModel.findOne({id});
    if(userOne)
      return {result : false, user : userOne}
    else
      return {result : true}
  }

}

 

 

이제 npm start dev로 백엔드를 실행하고 중복확인 버튼을 눌러보자.

 

 

 

정상적으로 중복확인 버튼이 disabled가 되었다.

728x90
반응형
728x90
반응형

 

SignupComponent 부분에 더 입력해야 될 값들을 추가해서 화면구성을 하자.

 

SignupComponent.tsx

import type { NextPage } from 'next'
import { UserState } from "../../store/interfaces/"
import { useDispatch, useSelector } from "react-redux";
import { actionTypesUser } from "../../store/interfaces/user/userAct.interfaces"
import { RootStateInterface } from "../../store/interfaces/RootState";

interface Props {
    userChangeHandler : (event : any) => void;
    userState : UserState;
}

const Signup:NextPage<Props> = ({userChangeHandler,userState}) => {
    const dispatch = useDispatch();
    const submitHandler = (event : any) => {
        event.preventDefault()
    }
    return (
        <div className="signup">
            <form onSubmit={submitHandler}>
                <div>
                    <input 
                        name="id"
                        type="text"
                        placeholder="Enter ID"
                        onChange={userChangeHandler} />
                    <button>
                    중복확인     
                    </button>
                </div>
                <div>
                    <input 
                        name="pw"
                        type="password"
                        placeholder="Enter PW"
                        onChange={userChangeHandler} />
                </div>
                <div>
                    <input 
                        name="pw2"
                        type="password"
                        placeholder="Verify password"/>
                </div>
                <div>
                    <input 
                        name="email"
                        type="email"
                        placeholder="Enter Email"
                        onChange={userChangeHandler} />
                    <button>
                    보내기    
                    </button>
                </div>
                <div>
                    <input 
                        name="address"
                        type="text"
                        placeholder="Enter Address"
                        onChange={userChangeHandler} />
                    <button>
                    주소검색    
                    </button>
                </div>
                <div className="signbutton">
                    <button
                        type="submit">
                    Signup       
                    </button>
                </div>
            </form>
        </div>
        )
}

export default Signup

 

 나중에 중복확인이 완료되고 id 입력칸을 비활성화로 만들어주기 위해 state를 만들어준다.

    //idDuple
    const [idDupleOK,setIdDupleOK] = useState<boolean>(false)

 

 

화면 부분도 바꿔준다.

<div>
    <input 
        name="id"
        type="text"
        placeholder="Enter ID"
        onChange={userChangeHandler}
        disabled={idDupleOK} />
    <button
    	type="button"
        onClick={IdDuplicate}
        disabled={userState.idDuple}
        >
    중복확인
    </button>
</div>

 

 

 화면으로 확인해보자.

 

 

참고로 기능위주로 구현하고 CSS는 나중에 다룰것이다. ( div에 className을 하나씩 추가한 이유 )

 

로그를 확인해보면 userState 값이 정상적으로 바뀌는것을 확인할 수 있다.

axios 모듈이 없으면 npm i axios 로 설치하면 된다.

id를 파라미터로 받아 백엔드에 GET 메소드로 데이터를 요청해 반환값을 전달하는 userIdDuplicate 함수를 작성했다.

 

이제 userAct.interfaceuser_reducer부분에 ID중복확인 부분을 추가해준다.

 

user.interface.ts

export enum actionTypesUser {
    USER_INIT = "USER_INIT", // state 초기화
    USER_STATE = "USER_STATE", // userState 변경
    USER_INFO = "USER_INFO",  // userInfo 변경 
    USER_ID_DUPLICATE = "USER_ID_DUPLICATE" // ID 중복 검사
}

export type ActionsUser = UserInit | UserState | UserInfo | UserIdDuplicate

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

export interface UserState {
    type : actionTypesUser.USER_STATE;
    data : any;
}

export interface UserInfo {
    type : actionTypesUser.USER_INFO;
    data : any;
}

export interface UserIdDuplicate {
    type : actionTypesUser.USER_ID_DUPLICATE;
    payload: any;
}

 

user_reducer.ts

        case actionTypesUser.USER_ID_DUPLICATE:
            return {
                ...state, data : action.payload
            }

 

ID중복확인을 위한 함수를 작성하기 전에 우리가 USER_STATE, USER_INFO 액트를 쓸 땐 백엔드(NestJS)와의 통신이 없어도 상관없었지만 ID 중복확인을 위한 과정에는 백엔드(NestJS)와의 통신이 필요하다.

 

때문에 dispatch에 사용될 Action을 정의할 것이다.

store 밑에 user_action.ts 파일을 생성한다.

/store/user_action.ts

import Axios from 'axios'
import { actionTypesUser } from '../interfaces'

export async function userIdDuplicate (id : string) {
    const request = Axios.get('/api/user/'+id)
    .then(response => response.data)
    
    return {
        type : actionTypesUser.USER_ID_DUPLICATE,
        payload: request
    }
}

 

이제 컴포넌트에서 dispatch로 axios의 결과값에 따른 로직을 만들어준다.

SignupComponent.tsx

import {userIdDuplicate} from '../../store/actions/user_action'

    (...)
    
    //idDuple
    const [idDupleOK,setIdDupleOK] = useState<boolean>(false)
    }
    const IdDuplicate = () => {
        if(userState.userInfo.id !=='') { // 입력된 값이 존재할 경우
            dispatch(userIdDuplicate(userState.userInfo.id)).then((req : any)=> {
                if(!req.payload.result) { // 아이디가 이미 존재할 때
                    alert('해당 아이디가 이미 존재합니다.');
                }else {
                    alert('중복된 아이디가 없습니다.');
                    dispatch({type : actionTypesUser.USER_STATE, data: ['idDuple',true]});
                    setIdDupleOK(true);
                }
            })
        }
    }
    
    (...)
    
    <input 
      name="id"
      type="text"
      placeholder="Enter ID"
      onChange={userChangeHandler}
      disabled={idDupleOK} />
    <button
      type="button"
      onClick={IdDuplicate}
      disabled={userState.idDuple}
    >
    중복확인     
    </button>
    
    (...)

 

  1. 만들어두었던 action을 가져온다.
  2. 버튼을 클릭할 경우 동작될 함수를 선언한다.
  3. 함수에선 dispatchdata에 입력한 ID를 전달한다.
  4. 반환값에 따라 조건문을 써준다. 만약 중복된 아이디가 없을 경우 UserStateidDuple값을 바꿔준다.

 

여기까지 중복확인 버튼을 눌렀을 때 dispatch로 백엔드에 데이터를 요청해서 원하는 값을 받아오고,

받아온 값에 따라 어떻게 처리할지 조건문으로 써주었다. 

 

다음 포스트에서는 백엔드(NestJS)를 구성해보겠다.

 

 

728x90
반응형
728x90
반응형

먼저 회원가입부터 구현하도록 하자.

 

먼저 user.interfaces.ts와 user 부분에 store의 state를 바꿔주는 부분을 추가하고 다음과같이 액션들의

interface부분을 바꿔준다.

 

userAct.interfaces.ts

export enum actionTypesUser {
    USER_INIT = "USER_INIT", // state 초기화
    USER_STATE = "USER_STATE", // userState 변경
    USER_INFO = "USER_INFO",  // userInfo 변경 
}

export type ActionsUser = UserInit | UserState | UserInfo 

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

export interface UserState {
    type : actionTypesUser.USER_STATE;
    data : any;
}

export interface UserInfo {
    type : actionTypesUser.USER_INFO;
    data : any;
}

 

user_reducer.ts

(...)
        case actionTypesUser.USER_STATE:
            return {
                ...state,
                [action.data[0]] : action.data[1]
            }
        case actionTypesUser.USER_INFO:
                return {
                    ...state,
                    userInfo : {
                        ...state.userInfo,
                        [action.data[0]] : action.data[1]
                    }
                }
(...)

State 안의 값이 변경될 경우와 userInfo 변경만 필요할 경우를 나눠서 선언해준다.

 

그다음 Input에서 쓰일 userChangeHandler를 선언해주고, SignupComponentuserState와 함께 props를 전달한다.

[cate].tsx

import { GetServerSideProps, GetStaticPaths, GetStaticProps } from 'next'
import type { NextPage } from 'next'
//Redux
import { useDispatch, useSelector } from "react-redux";
import { RootStateInterface } from "../../store/interfaces/RootState";
import { UserState } from "../../store/interfaces/"
//React
import { useState } from 'react';
//Components
import LoginComponent from '../../src/components/LoginComponent'
import SignupComponent from '../../src/components/SignupComponent'

interface Props {
    CateData : string
}


const Auth:NextPage<Props> = ({CateData}) => {
    const dispatch = useDispatch();
    let userState = useSelector( // store에서 가져온 userState
        (state: RootStateInterface) : UserState => state.user
    )
    const userChangeHandler = (event : any):void => { // Input Handler
        dispatch({type : actionTypesUser.USER_INFO, data : [event.target.name,event.target.value]})
    }
    console.log(userState)

    return (

        <div className='auth'>
            {CateData} page
            {(CateData === 'login') ? <LoginComponent /> : ""}
            {(CateData === 'signup') ? 
            <SignupComponent
                userChangeHandler={userChangeHandler} // 회원가입에 props로 Handler함수를 보낸다.
            	userState={userState}
            /> : ""}
        </div>

        )
}


export default Auth;


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

 

컴포넌트에서 State가 잘 바뀌는지 확인하기위해 console.log를 추가하였다.

아직 SignupComponentProps type을 지정해주지 않았기 때문에 에러가 뜬다.

Props type을 설정해주고 log로 확인해보자.

 

SignupComponent.tsx

import type { NextPage } from 'next'
import { UserState } from "../../store/interfaces/"
import { useDispatch, useSelector } from "react-redux";
import { actionTypesUser } from "../../store/interfaces/user/userAct.interfaces"
import { RootStateInterface } from "../../store/interfaces/RootState";

interface Props {
    userChangeHandler : (event : any) => void;
    userState : UserState;
}

const Signup:NextPage<Props> = ({userChangeHandler,userState}) => {
    console.log(userChangeHandler)
    return (
        <div className='signup'>
        </div>
        )
}

export default Signup

 

 

정상적으로 떴으니 이제 form에 필요한 화면과 함수까지 구현해보자.

 

SignupComponent.tsx

import type { NextPage } from 'next'
import { UserState } from "../../store/interfaces/"
import { useDispatch, useSelector } from "react-redux";
import { actionTypesUser } from "../../store/interfaces/user/userAct.interfaces"
import { RootStateInterface } from "../../store/interfaces/RootState";

interface Props {
    userChangeHandler : (event : any) => void;
    userState : UserState;
}

const Signup:NextPage<Props> = ({userChangeHandler,userState}) => {
    const dispatch = useDispatch();
    const submitHandler = (event : any) => {
        event.preventDefault()
    }
    return (
        <div className='signup'>
            <form onSubmit={submitHandler}>
                <input 
                    name="id"
                    type="text"
                    placeholder="Enter ID"
                    onChange={userChangeHandler} />
                <button
                    type="submit">
                회원가입        
                </button>
            </form>
        </div>
        )
}

export default Signup

 

input onChange 부분에 props로 전달받았던 userChangeHandler를 주입했다.

 

아까 수정했던 [cate].tsx 파일에 작성했던 console.log로 확인해보자. userInfo가 정상적으로 바뀌는지 확인해보자.

 

SignupComponentuserChangeHandler 함수가 잘 넘어갔고, [cate].tsx 파일에 선언했던 userState가 정상적으로 바뀌는것을 확인했다.

 

728x90
반응형
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
반응형

nest-next npm 카테고리에서 우리는 프론트엔드와 백엔드를 어느정도 통합시킨 프로젝트를 진행했었다.

서버를 개발하다보면 규모가 커지고, 보안상의 이유로 프론트엔드와 백엔드를 나눠야 할 때가 있다.

 

React를 쓸 때는 명확하게 client와 server로 나눠서 개발을 진행했었다.

이번에는 NextJS와 NestJS로 분리된 서버를 만들어보겠다.

 

Pakage 설치

1. 먼저 필요한 npm, node, npx들을 설치한다.

$ sudo apt-get install npm
$ sudo npm i -g n 
$ sudo n 14.18.1
$ sudo npm i npx -g

 

2. create-next-app( Next.js 프로젝트를 생성하고 기본 패키지를 설치해주는 프로그램)을 설치한다.

$ sudo npm i create-next-app -g

 

3. @nestjs/cli(NestJS 프레임워크)를 설치한다.

$ sudo npm i -g @nestjs/cli

 

Front-End 구축

 

1. front라는 이름의 NextJS 프로젝트를 생성한다.

$ npx create-next-app front --typescript

--typescript 옵션을 추가하면 NextJS 프로젝트가 typescript 버전으로 설치된다.

front 디렉토리로 이동해서 개발모드로 서버를 실행해보자.

 

eslint 설치

$ npx eslint --init

npx eslint --init
✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · react
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser
✔ How would you like to define a style for your project? · guide
✔ Which style guide do you want to follow? · airbnb
✔ What format do you want your config file to be in? · JavaScript
Checking peerDependencies of eslint-config-airbnb@latest
The config that you've selected requires the following dependencies:

eslint-plugin-react@^7.21.5 @typescript-eslint/eslint-plugin@latest eslint-config-airbnb@latest eslint@^5.16.0 || ^6.8.0 || ^7.2.0 eslint-plugin-import@^2.22.1 eslint-plugin-jsx-a11y@^6.4.1 eslint-plugin-react-hooks@^4 || ^3 || ^2.3.0 || ^1.7.0 @typescript-eslint/parser@latest
✔ Would you like to install them now with npm? · No / Yes

 

 

$ npm run dev

2. locallhost:3000으로 접속해서 웹페이지가 정상적으로 뜨는지 확인한다.

 

 

 

Back-End 구축

1. back이라는 이름의 NestJS 프로젝트를 생성한다.

$ nest new back

 

2. back 폴더로 들어가 개발모드로 서버를 실행해보자. 아직 포트를 둘다 3000번을 쓰고있기 때문에 Next 서버를 먼저 종료하고 실행해보자.

$ npm run start:dev

 

 

Front-Back 연동

1. Back-End에 새로운 리소스 apple을 추가해보자. 먼저 nest에 apple이라는 resource를 하나 만들어준다.

$ nest g res apple

 

2. localhost의 3000번 포트로 확인해본다 

 

 

 

Back-End 포트 변경

1. back/src/main.ts 의 파일을 열어 기존 3000번 포트를 3001번으로 바꾼다.

 

Front-End 프록시 설정

Front에서 Back의 API를 쓰기위해 프록시를 설정해주어야 한다.

1. 먼저 .env 파일을 생성하고 다음과 같이 작성한다.

.env

SOURCE_PATH = '/api/:path*'
DESTINATION_URL = 'http://localhost:3001/:path*'

 

2. 그다음 next.config.js 파일을 수정해준다.

 

next.config.js

module.exports = {
  reactStrictMode: true,
  async rewrites() {
    console.log(process.env.NODE_ENV)
    if (process.env.NODE_ENV !== 'production') {
      return [
        {
          source: process.env.SOURCE_PATH,
          destination: process.env.DESTINATION_URL,
        },
      ];
    }
    else {
      return [
        {
          source: process.env.SOURCE_PATH,
          destination: process.env.DESTINATION_URL,
        },
      ];
    }
  },
}

 

3. 프록시 설정이 끝났다. 이제 front 서버와 back 서버를 실행하고 localhost:3000/api/apple에 접속해보자.

 

 

NextJS가 열린 3000번 포트에서 /api 경로를타니 3001번 포트에 열린 NestJS의 자원을 쓸 수 있는것을 확인할 수 있다.

 

 

728x90
반응형

+ Recent posts