이번에 플젝을 next 를 쓰는 플젝을 진행했는데 다른건 그냥 만들면 됐는데 처음 가장 발목잡았던 next auth...

설정다하면 편하기는 편한데 설정이 복잡하다...

 

우선 next 디렉토리 구소를 api 형식이다. pages 라면 구글링해보면 다른 예제가 많아서 구글링을 추천한다.

 

https://next-auth.js.org/

 

NextAuth.js

Authentication for Next.js

next-auth.js.org

 

여기서 npm install 해주고 폴더구조를 우선

api/auth/[...nextauth]/route.js 를 정한다. ts 안쓰는 이유는 내가 안쓰는거까지도 타입다 지정해야하고 너무 불편하다

구글 애플/ 네이버 카카오 리턴이 다른데 콜백에서 접근할때 없는거니까 접근안된다고 에러까지 뜬다..

이런 에러 헨들링 가능하면 ts 를 추천한다.

 

 

 

import NextAuth, { AuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import AppleProvider from "next-auth/providers/apple";
import KakaoProvider from "next-auth/providers/kakao";
import NaverProvider from "next-auth/providers/naver";
import CredentialsProvider from "next-auth/providers/credentials";
import { BASE_URL, JWT_SECRET } from "@/util/path";
import jwt from 'jsonwebtoken';

import { SignJWT } from "jose";
import { createPrivateKey, KeyObject } from "crypto";

const authOptions = {

	session: {
		strategy: "jwt",
		maxAge: 60 * 60 * 24 * 7, // 31일
		updateAge: 60 * 60 * 24, // 1일
	},
	cookies: {
		pkceCodeVerifier: {
			name: "next-auth.pkce.code_verifier",
			options: {
				httpOnly: true,
				sameSite: "none",
				path: "/",
				secure: true,
			},
		},
	},
	pages: {
		signIn: "/next/login", // 로그인 페이지
		signOut: "/next/signout", // 로그아웃 페이지
		error: "/next/api/auth/error", // 오류 페이지
		verifyRequest: "/next/api/auth/verify-request", // 이메일 확인 페이지
	},
	providers: [
		CredentialsProvider({
			name: "Credentials",
			credentials: {
				username: { label: "Username", type: "text" },
				password: { label: "Password", type: "password" },
			},
			async authorize(credentials, req) {

				// 로그인 인증 처리 추가해야함
				
				const response_j = await response.json();

				let user = { id: null, name: null, email: null };

				if (response_j?.result) {
					user = { id: credentials.username, name: null, email: response_j._email };
				}

				if (user.id) {
					return user;
				} else {
					return null;
				}
			},
		}),

		GoogleProvider({
			clientId: process.env.GOOGLE_CLIENT_ID,
			clientSecret: process.env.GOOGLE_KEY,
		}),
		AppleProvider({
			clientId: process.env.APPLE_ID,
			clientSecret: await getAppleToken(),
		}),
		KakaoProvider({
			clientId: process.env.KAKAO_API_KEY,
			clientSecret: process.env.KAKAO_SCRIPT_KEY,
		}),
		NaverProvider({
			clientId: process.env.NAVER_API_KEY,
			clientSecret: process.env.NAVER_CLIENT_SECRET,
		}),


	],
	secret: process.env.NEXTAUTH_SECRET,
	callbacks: {
		async signIn({ user, account, profile, email, credentials, context }) {
			console.log("======signIn======");

			if (account.provider === "apple" || account.provider === "naver" || account.provider === "kakao" || account.provider === "google") {
				// 1. 가입 유무 체크  user.id
				
				const response_j = await response.json();

				if (response_j.result) {
					// 3. 가입 되어있으면 로그인 완료
					// console.log("regiCheck > response_j:: ", response_j)
				} else {

					// 2. 가입 안되면 간편가입으로 (필요 데이터 넘기기)
					// 전화번호, 아이디, 이메일, 이름, 닉네임, 소셜로그인,
					let nickname = null
					let phonenumber = null

					if (account.provider === "kakao") {
						nickname = profile.properties.nickname
						phonenumber = profile.kakao_account.phone_number
						if (phonenumber) {
							phonenumber = phonenumber.replace('+82', '0').replace(/[\s-]+/g, "").replace(/[^0-9]*/s, '');
						}
					} else if (account.provider === "naver") {
						nickname = profile.response.nickname
						phonenumber = profile.response.mobile
						if (phonenumber) {
							phonenumber = phonenumber.replace('+82', '0').replace(/[\s-]+/g, "").replace(/[^0-9]*/s, '');
						}
					}
					// 여기서 로그인 안되어있으니까 다른 페이지로 이동					

                }

			}

			if (account.provider === "credentials") {

			}

			return true;
		},
		async jwt({ token, trigger, session, user}) {
			console.log("======jwt======");

			let uToken = null;
			if (token?.uId !== null && token?.uId !== undefined) {
				uToken = jwt.sign({
					username: token.username,
				}, JWT_SECRET, { expiresIn: '5m' });
				token.uToken = uToken;
			}

			if (token?.sub) {
				console.log("======jwt setUserData======");
				

				const response_j = await response.json();

				// console.log("jwt > > response_j: ",response_j)
				if (response_j?.uData) {
					// 여기서 로그인 성공시 추가 테이터 지정 
                    token.username = response_j?.uData?.username;
				}
			}

			return token;
		},
		async session({ session, token, trigger, newSession }) {
			// console.log("======session======");

			// 토큰으로 지정한애들이 token. 안에 다 들어있어서 세션에 추가해야지 우리가 접근할수있다
			if (token.username !== undefined) {
				session.user.username = token.username;
			}

			return session;
		},

	},
};

// 이건 애플로그인 할때 필요한건데 그냥 넣자
async function getAppleToken() {
	const key = await process.env.APPLE_SECRET;

	const privateKey = createPrivateKey({
		key: key,
		format: "pem",
		type: "pkcs8",
	});

	const appleToken = await new SignJWT({})
		.setAudience("https://appleid.apple.com")
		.setIssuer(process.env.APPLE_TEAM_ID)
		.setIssuedAt(Math.floor(Date.now() / 1000))
		.setExpirationTime(Math.floor(Date.now() / 1000) + 3600 * 2)
		.setSubject(process.env.APPLE_ID)
		.setProtectedHeader({
			alg: "ES256",
			kid: process.env.APPLE_KEY_ID,
		})
		.sign(privateKey);

	return appleToken;
}


const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

 

 

이렇게 코드를 크게 짤수있다. 쿠키에 pkceCodeVerifier 이거는 애플로그인할때 필요하다. 중요 코드는 다빼서 알아서 추가해서 쓰자

pages 에 /next 를 다 붙이는 이유는 

 

next config 에 

    basePath: '/next',
    assetPrefix: '/next/',

이걸 추가해서 넣었다 

 

그리고 나처럼 로그인 다되고 user 를 뜯어고치고 싶으면 app/type/next-auth.d.ts 를 추가하자

 

import NextAuth, { DefaultSession } from "next-auth"

declare module "next-auth" {
  /**
   * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
   */
  interface Session {

    // 추가할 세션 항목
    user: {
      username: string | null,
    }
    // & DefaultSession["user"] // 기존 세션 항목 유지
  }
  interface User {

  }
}

 

이렇게 추가하면 된다

 

그리고 api 의 루트 layout 구조도 변경해야하는데

import React from "react";
import "./globals.css";

import { NextAuthProvider } from "./providers";

export default function RootLayout({ children,}: { children: React.ReactNode; }) {
  return (
    <html >
      <body>
        <div className=" bg-white">
          <NextAuthProvider>
            {children}
          </NextAuthProvider>
        </div>
      </body>
    </html>
  );
}


이렇게 수정하고 NextAuthProvider 는 

"use client";

import { SessionProvider } from "next-auth/react";
import React from "react";

type Props = {
  children?: React.ReactNode;
};

export const NextAuthProvider = ({ children }: Props) => {
  return <SessionProvider basePath="/next/api/auth">{children}</SessionProvider>;
};

이렇게 설정하자 

 

env 설정은 다른거는 다 구글링하면서 알아서 하겠지만

NEXTAUTH_SECRET

JWT_SECRET

NEXTAUTH_URL="만약에 나처럼 url 건들었음 https://도메인명/next/api/auth/ 이런식으로 지정해야한다 "  

APPLE_SECRET="-----BEGIN PRIVATE KEY-----\n여기에 키 내용 처음 열었을때 줄바꿈마다\n 을 붙여야함\n-----END PRIVATE KEY-----\n"

이거는 꼭 확인해서 적어주자 

그냥 폼검증말고 왜 틀렸는지 이런걸 알려주는 폼검증을 만들어보자!

js 라이브러리 등등 이런게 많지만 내입맛에 맞게 수정하기 편하기 위해서는 결국 직접 만드는것이 가장 좋겠다고 판단했다
https://codepen.io/trending

 

CodePen

An online code editor, learning environment, and community for front-end web development using HTML, CSS and JavaScript code snippets, projects, and web applications.

codepen.io

여기 사이트에서 적당한 예시를 찾아서 수정했는데 정확한 url을 다시 찾으려고하니 못찾겠다...

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
		<style>
			* {
				box-sizing: border-box;
			}

			body {
				background-color: blueviolet;
			}

			.title {
				margin-bottom: 2rem;
			}

			.hidden {
				display: none;
			}

			.icon {
				width: 24px;
				height: 24px;
				position: absolute;
				top: 32px;
				right: 5px;
				pointer-events: none;
				z-index: 2;

				&.icon-success {
					fill: green;
				}

				&.icon-error {
					fill: red;
				}
			}

			.container {
				max-width: 460px;
				margin: 3rem auto;
				padding: 3rem;
				border: 1px solid #ddd;
				border-radius: 0.25rem;
				background-color: white;
				box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
			}

			.label {
				font-weight: bold;
				display: block;
				color: #333;
				margin-bottom: 0.25rem;
				color: #2d3748;
			}

			.input {
				appearance: none;
				display: block;
				width: 100%;
				color: #2d3748;
				border: 1px solid #cbd5e0;
				line-height: 1.25;
				background-color: white;
				padding: 0.65rem 0.75rem;
				border-radius: 0.25rem;
				box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06);

				&::placeholder {
					color: #a0aec0;
				}

				&.input-error {
					border: 1px solid red;

					&:focus {
						border: 1px solid red;
					}
				}

				&:focus {
					outline: none;
					border: 1px solid #a0aec0;
					box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
					background-clip: padding-box;
				}
			}

			.input-group {
				margin-bottom: 2rem;
				position: relative;
			}

			.error-message {
				font-size: 0.85rem;
				color: red;
        display: block;
			}

			.button {
				background-color: blueviolet;
				padding: 1rem 2rem;
				border: none;
				border-radius: 0.25rem;
				color: white;
				font-weight: bold;
				display: block;
				width: 100%;
				text-align: center;
				cursor: pointer;

				&:hover {
					filter: brightness(110%);
				}
			}

			.promo {
				color: white;
				opacity: 0.75;
				margin: 1rem auto;
				max-width: 460px;
				background: rgba(255, 255, 255, 0.2);
				padding: 20px;
				border-radius: 0.25rem;

				a {
					color: white;
				}
			}
		</style>
	</head>
	<body>
		<div class="container">
			<h2 class="title">Create a new account</h2>
			<form action="#" class="form">
				<div class="input-group">
					<label for="username" class="label">이름</label>
					<input id="username" placeholder="webcrunch" type="text" class="input" name="username" formval_min="5" formval_max="15" />

					<span class="error-message"></span>
					<svg class="icon icon-success hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
					</svg>
					<svg class="icon icon-error hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
					</svg>
				</div>

				<div class="input-group">
					<label for="username" class="label">셀렉트 박스</label>
					<span class="error-message"></span>

					<select name="selectBox0" id="selectBox0">
						<option value="">선택x</option>
						<option value="1">선택1</option>
						<option value="2">선택2</option>
						<option value="3">선택3</option>
						<option value="4">선택4</option>
					</select>

					<svg class="icon icon-success hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
					</svg>
					<svg class="icon icon-error hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
					</svg>
				</div>

				<div class="input-group">
					<label class="label">체크박스</label>
					<span class="error-message"></span>

					<label for="checkbox1" class="label">체크박스선택1</label>
					<input id="checkbox1" name="checkbox00" type="checkbox" class="formValCheckBox" value="1" formval_min="1" formval_max="2" />
					<label for="checkbox2" class="label">체크박스선택2</label>
					<input id="checkbox2" name="checkbox00" type="checkbox" class="formValCheckBox" value="2" />
					<label for="checkbox2-1" class="label">체크박스선택2.1</label>
					<input id="checkbox2-1" name="checkbox00" type="checkbox" class="formValCheckBox" value="2.1" />

					<svg class="icon icon-success hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
					</svg>
					<svg class="icon icon-error hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
					</svg>
				</div>

				<div class="input-group">
					<label class="label">체크박스</label>
					<span class="error-message"></span>

					<div>
						<label for="checkbox3" class="label">체크박스선택3</label>
						<input id="checkbox3" name="checkbox01" type="checkbox" class="" value="3" />
					</div>
					<div>
						<label for="checkbox4" class="label">체크박스선택4</label>
						<input id="checkbox4" name="checkbox01" type="checkbox" class="" value="4" />
					</div>

					<svg class="icon icon-success hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
					</svg>
					<svg class="icon icon-error hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
					</svg>
				</div>

				<div class="input-group">
					<label for="email" class="label">Email</label>
					<input id="email" type="email" class="input" name="email" autocomplete placeholder="andy@web-crunch.com" />

					<span class="error-message"></span>
					<svg class="icon icon-success hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
					</svg>
					<svg class="icon icon-error hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
					</svg>
				</div>

				<div class="input-group">
					<label for="password" class="label">Password</label>
					<input id="password" type="password" class="input" name="password" />

					<span class="error-message"></span>
					<svg class="icon icon-success hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
					</svg>
					<svg class="icon icon-error hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
					</svg>
				</div>

				<div class="input-group">
					<label for="password_confirmation" class="label">Password Confirmation</label>
					<input id="password_confirmation" type="password" class="input" name="password_confirmation" />

					<span class="error-message"></span>
					<svg class="icon icon-success hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
					</svg>
					<svg class="icon icon-error hidden" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
						<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
					</svg>
				</div>

				<input type="submit" class="button" value="Create account" />
			</form>
		</div>

	</body>

	<script>
		class FormValidator {
			constructor(form, fields) {
				this.form = form;
				this.fields = fields;
			}

			initialize() {
				// this.validateOnEntry(); // 실시간 감지
				this.validateOnSubmit();
			}

			validateOnSubmit() {
				let self = this;

				this.form.addEventListener("submit", (e) => {
					e.preventDefault();
					self.fields.forEach((field) => {
						const input = document.getElementsByName(`${field}`);

						if (input[0].type == "checkbox") {
							self.validateFields_Checkbox(input[0], field);
						} else if (input[0].type == "select-one") {
							self.validateFields_Selectbox(input[0]);
						} else {
							self.validateFields(input);
						}
					});
				});
			}

			validateOnSubmit2() {
				// e.preventDefault();
				let self = this;
				self.fields.forEach((field) => {
					const input = document.getElementsByName(`${field}`);
					self.validateFields(input);
					if (input[0].type == "checkbox") {
						self.validateFields_Checkbox(input[0], field);
					} else if (input[0].type == "select-one") {
						self.validateFields_Selectbox(input[0]);
					} else {
						self.validateFields(input);
					}
				});
			}

			validateOnEntry() {
				let self = this;
				this.fields.forEach((field) => {
					const input = document.getElementsByName(`${field}`);

					input.addEventListener("input", (event) => {
						self.validateFields(input);
					});
				});
			}

			validateFields(fields) {
				let formval_min = fields[0].getAttribute("formval_min");
				let formval_max = fields[0].getAttribute("formval_max");

				fields.forEach((field) => {
					if (field.value.trim() === "") {
						this.setStatus(field, `${field.previousElementSibling.innerText} 을/를 입력해주세요`, "error");
					} else {
						if (field.value.trim() !== "") {
							this.setStatus(field, null, "success");
						}

						if (field.type === "text") {
							if (formval_max && formval_min) {
								if (formval_max < field.value.trim().length || formval_min > field.value.trim().length) {
									this.setStatus(field, formval_min + "자 이상 " + formval_max + "자 이하로 입력해주세요.", "error");
								}
							} else {
								if (formval_min) {
									if (formval_min > field.value.trim().length) {
										this.setStatus(field, formval_min + "자 이상 입력해주세요.", "error");
									}
								}
								if (formval_max) {
									if (formval_max < field.value.trim().length) {
										this.setStatus(field, formval_max + "자 이하로 입력해주세요.", "error");
									}
								}
							}
						}

						if (field.type === "email") {
							const re = /\S+@\S+\.\S+/;
							if (re.test(field.value)) {
								this.setStatus(field, null, "success");
							} else {
								this.setStatus(field, "이메일 형식이 맞지 않습니다.", "error");
							}
						}

						if (field.id === "password_confirmation") {
							const passwordField = this.form.querySelector("#password");

							if (field.value.trim() == "") {
								this.setStatus(field, "비밀번호를 입력해주세요.", "error");
							} else if (field.value != passwordField.value) {
								this.setStatus(field, "비밀번호 두개가 일치하지 않습니다.", "error");
							} else {
								this.setStatus(field, null, "success");
							}
						}
					}
				});
			}

			validateFields_Checkbox(field, checkboxName) {
				const selectedCheckboxLength = document.querySelectorAll(`input[name="${checkboxName}"]:checked`).length;
				let formval_min = field.getAttribute("formval_min");
				let formval_max = field.getAttribute("formval_max");

				if (selectedCheckboxLength == 0) {
					this.setStatus(field, `${field.parentElement.parentElement.querySelector("label").innerText} 을/를 선택해주세요`, "error");
				} else {
					this.setStatus(field, null, "success");
				}

				if (formval_max && formval_min) {
					if (formval_max < selectedCheckboxLength || formval_min > selectedCheckboxLength) {
						this.setStatus(field, formval_min + "개 이상 " + formval_max + "개 이하로 선택해주세요.", "error");
					}
				} else {
					if (formval_min) {
						if (formval_min > selectedCheckboxLength) {
							this.setStatus(field, formval_min + "개 이상 선택해주세요.", "error");
						}
					}
					if (formval_max) {
						if (formval_max < selectedCheckboxLength) {
							this.setStatus(field, formval_max + "개 이하로 선택해주세요.", "error");
						}
					}
				}
			}

			validateFields_Selectbox(field) {
				if (field.value.trim() === "") {
					this.setStatus(field, `${field.previousElementSibling.previousElementSibling.innerText} 을/를 선택해주세요`, "error");
				} else {
          this.setStatus(field, null, "success");
        }
			}

			setStatus(field, message, status) {
				const successIcon = field.parentElement.querySelector(".icon-success") ? field.parentElement.querySelector(".icon-success") : field.parentElement.parentElement.querySelector(".icon-success");
				const errorIcon = field.parentElement.querySelector(".icon-error") ? field.parentElement.querySelector(".icon-error") : field.parentElement.parentElement.querySelector(".icon-error");
				const errorMessage = field.parentElement.querySelector(".error-message")
					? field.parentElement.querySelector(".error-message")
					: field.parentElement.parentElement.querySelector(".error-message");

				if (status === "success") {
					if (errorIcon) {
						errorIcon.classList.add("hidden");
					}
					if (errorMessage) {
						errorMessage.innerText = "";
					}
					successIcon.classList.remove("hidden");
					field.classList.remove("input-error");
				}

				if (status === "error") {
					if (successIcon) {
						successIcon.classList.add("hidden");
					}
					errorMessage.innerText = message;
					errorIcon.classList.remove("hidden");
					field.classList.add("input-error");
				}
			}
		}

		const form = document.querySelector(".form");

		// name 명
		const fields = ["username", "email", "password", "password_confirmation", "checkbox00", "checkbox01", "selectBox0"];

		const validator = new FormValidator(form, fields);
		validator.initialize();
	</script>
</html>

이렇게 만들었는데 여기에서 입맛에 맞게 추가수정을 하면 될거같다


// 스크롤 멈춤 감지
$.fn.scrollStopped = function (callback) {
    let _this = this, $this = $(_this);
    $this.scroll(function(event) {
        clearTimeout($this.data('scrollTimeout'));
        $this.data('scrollTimeout', setTimeout(callback.bind(_this), 250, event));
    });
};
addEventListener("scroll", e => {
    $(window).scrollStopped(function (ev) {
        // console.log("멈춤")
    });
})

 

let lastScrollY = 0;
addEventListener("scroll", e => {
                const scrollY = window.scrollY;
                // 스크롤 올렸을때, 내렸을때
                if (scrollY < lastScrollY) {
       
                } else {
 
                }
                // 현재의 스크롤 값을 저장
                lastScrollY = scrollY;
})

 

이미 개발이 완료된 페이지를 모달로 불러와야하는 상황에서

iframe으로 불러온다면 어떨까?

 

라는 생각에 시작되었다.

 

구글링을 엄청해서 이렇게 해보라는 수많은 방식을 사용해봤는데 이게 하나도 안먹혔다...

그래도 구글링은 계속했고 여기에서 답을  찾았다

https://jsfiddle.net/zzznara/whL591q2/4/

 

[자바스크립트] javascript로 iframe에 동적으로 소스를 추가하거나 변경하는 방법 - JSFiddle - Code Playgr

 

jsfiddle.net

 

여기에서 보여주는 예시처럼

    // iframe을 담는 변수
    let iframe = document.getElementById('iframe_id');

    // 브라우저 버전에 따라 iframe의 document를 가져온다.
    let iframedoc = iframe.document;
    if (iframe.contentDocument) {
        iframedoc = iframe.contentDocument;
    } else if (iframe.contentWindow) {
        iframedoc = iframe.contentWindow.document;
    }

    let mainPageHeader = iframedoc.querySelector('#mainPageHeader')
    mainPageHeader.style.display = "none";
    let header_area = iframedoc.querySelector('.header-area')
    // header_area.style.display = "none";
    let htmlTag = `
        <header class="">
            <h1 class="" style="padding-bottom:0;">모달이름</h1>
            <a class="modal-close">×</a>
        </header>`
    $(header_area).append(htmlTag);
 
이런식으로 해서 구현했다 

 

 

세션 스토리지를 리스트 유지에 많이 쓰이고는 하는데

세션스토리지에 리스트를 그리는용 데이터를 많이 던져두고는 하는데 여기서 최소한의 보안을 신경쓴 코드다

 

// 세션 스토리지 배열가져오기
function getArrSessionStorage(storageName) {
    if (JSON.parse(sessionStorage.getItem(storageName))) {
        let ArrData = JSON.parse(sessionStorage.getItem(storageName))
        ArrData = CryptoJS.AES.decrypt(ArrData, storageName);
        ArrData = JSON.parse(ArrData.toString(CryptoJS.enc.Utf8));
        return ArrData
    } else {
        return false;
    }
}
 
// 세션 스토리지 배열 저장
function setArrSessionStorage(storageName, ArrData) {
    ArrData = CryptoJS.AES.encrypt(JSON.stringify(ArrData), storageName).toString();
    sessionStorage.setItem(storageName, JSON.stringify(ArrData));
}
 

해당함수를 사용하기 위해서는 필수적으로 crypto-js가 설치 되어야한다

https://www.npmjs.com/package/crypto-js

 

crypto-js

JavaScript library of crypto standards.. Latest version: 4.1.1, last published: a year ago. Start using crypto-js in your project by running `npm i crypto-js`. There are 8744 other projects in the npm registry using crypto-js.

www.npmjs.com

 

안드로이드 스튜디오를 실행했느데 도큐먼트 파일명이 너무 작아서 안보여서 키우는 방법을 찾아보았다

 

file > settings > Appearance&Behacior >  Appearance 에 보면  

 

use costom font 에서 조절하면 된다

 

 

 

 

+ Recent posts