TOP
본문 바로가기
[파이썬 Projects]/<파이썬 웹개발>

[파이썬] FastAPI - 메모 앱 프로젝트 6: 프론트엔드 페이지 개선

by 기록자_Recordian 2025. 5. 10.
728x90
반응형
이전 내용
 

[파이썬] FastAPI - 메모 앱 프로젝트 5: 웹페이지 개선

이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 4: 사용자별 메모 관리(feat. Almebic)이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 3: 사용자 인증이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 2: CRUD 구

puppy-foot-it.tistory.com


프론트엔드 페이지 개선

기존의 home.html 코드는 로그인 및 회원가입 성공 또는 실패 시 팝업창만 띄우므로 몇 가지 개선이 필요하다.

  • 로그인 성공 시: 자동으로 /memos 라우트로 넘어감
  • 로그인 또는 회원가입 실패 시: 팝업으로 에러 메시지 띄움
  • 화면 개선

[home.html 전체 코드]

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>마이 메모 앱</title>
    <style>
        body { 
            font-family: 'Noto Sans KR', sans-serif;
            background-color: #f8f9fa;
            margin: 0;
            padding: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            text-align: center;
        }
        .container {
            max-width: 400px;
            padding: 2rem;
            background-color: #fff;
            border-radius: 10px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            margin: 1rem;
            width: 100%;
        }
        h1 {
            font-size: 1.5rem;
            color: #007bff;
            margin-bottom: 2rem;
        }
        p {
            margin-bottom: 2rem;
            color: #666;
        }
        .form-group {
            margin-bottom: 1rem;
            width: 100%;
        }
        .form-group label {
            margin-bottom: .5rem;
            color: #888;
            text-align: left;
            display: block;
        }
        .form-group input {
            padding: 0.75rem;
            border: 1px solid #ced4da;
            border-radius: 5px;
            width: 100%;
            box-sizing: border-box;
        }
        .form-group input {
            padding: 0.75rem;
            border: 1px solid #ced4da;
            border-radius: 5px;
            width: 100%;
            box-sizing: border-box;
        }
        .form-group input:focus {
            border-color: #80bdff;
            box-shadow: 0 0 0 2px rgba(0,123,255,.25);
        }
        .buttons button {
            width: 100%
            padding: 0.75rem;
            border: none;
            border-radius: 5px;
            background-color: #007bff;
            color: white;
            margin-top: 0.5rem;
            font-size: 1rem;
            font-weight: 600;
            cursor: pointer;
            box-sizing: border-box;
        }
        .buttons button:hover {
            background-color: #0056b3;
        }
        @media (max-width: 768px) {
            .container {
                width: 90%;
                padding: 1.5rem;
            }
            h1 {
                font-size: 1.25rem;
            }
        }
    </style>
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700&display=swap" rel="stylesheet">
    <script>
        function submitLoginForm(event) {
            event.preventDefault();
            const formData = new FormData(event.target);
            const data = {
                username: formData.get('username'),
                password: formData.get('password')
            };
            fetch('/login', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(data)
            })
            .then(response => response.json().then(body => ({
                status: response.status, body: body})))
            .then(result => {
                if (result.status == 200) {
                    alert(result.body.message); // 로그인 성공 메시지를 팝업으로 표시
                    window.location.href = '/memos'; // 메모 페이지로 리다이렉트
                } else {
                    throw new Error(result.body.detail || '로그인이 실패하였습니다.'); // 로그인 실패 시 에러 발생 (서버 제공 에러 또는 기본 메시지)
                }
            })
            .catch((error) => {
                console.error('Error:', error);
                alert(error.message); // 에러 메시지를 팝업으로 표시
            });
        }

        function submitSignupForm(event) {
            event.preventDefault();
            const formData = new FormData(event.target);
            const data = {
                username: formData.get('username'),
                email: formData.get('email'),
                password: formData.get('password')
            };
            fetch('/signup', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify(data)
            })
            .then(response => response.json().then(body => ({
                status: response.status, body: body})))
            .then(result => {
                if (result.status == 200) {
                    alert(result.body.message); // 회원가입 성공 메시지를 팝업으로 표시
                    window.location.href = '/'; // 메인 페이지로 리다이렉트
                } else {
                    throw new Error(result.body.detail || '회원가입이 실패하였습니다.'); // 회원가입 실패 시 에러 발생 (서버 제공 에러 또는 기본 메시지)
                }
            })
            .catch((error) => {
                console.error('Error:', error);
                alert(error.message); // 에러 메시지를 팝업으로 표시
            });
        }
    </script>
</head>
<body>
    <div class="container">
        <h1>마이 메모 앱에 오신 것을 환영합니다!</h1>
        <p>간단한 메모를 작성하고 관리할 수 있는 앱입니다.</p>

        <form id="loginForm" onsubmit="submitLoginForm(event)">
            <div class="form-group">
                <label for="username">사용자 이름:</label>
                <input type="text" id="username" name="username" required>
            </div>
            <div class="form-group">
                <label for="password">비밀번호:</label>
                <input type="password" id="password" name="password" required>
            </div>
            <div class="buttons">
                <button type="submit">로그인</button>
            </div>
        </form>

        <form id="signupForm" onsubmit="submitSignupForm(event)">
            <div class="form-group">
                <label for="signup_username">사용자 이름:</label>
                <input type="text" id="signup_username" name="username" required>
            </div>
            <div class="form-group">
                <label for="signup_email">이메일:</label>
                <input type="email" id="signup_email" name="email" required>
            </div>
            <div class="form-group">
                <label for="signup_password">비밀번호:</label>
                <input type="password" id="signup_password" name="password" required>
            </div>
            <div class="buttons">
                <button type="submit">회원가입</button>
            </div>
        </form>
    </div>
</body>
</html>

 

 [전체 구조 요약]

구분 설명
HTML 로그인 및 회원가입 입력 폼 구조
CSS 디자인(폰트, 배경색, 입력창 스타일 등)
JavaScript 로그인/회원가입 버튼을 눌렀을 때 서버로 데이터를 보내는 기능

home.html 분석
1. HTML (화면에 보여지는 부분)

HTML: 웹 페이지에 어떤 요소를 보여줄지를 결정하는 "뼈대" 역할.

각 <form>은 onsubmit 속성을 통해 JavaScript 함수 (submitLoginForm, submitSignupForm)와 연결.

태그 역할
<h1> 큰 제목. 앱 이름을 보여줌
<p> 짧은 소개 문장
<form> 입력 폼. 사용자 정보를 서버로 보낼 준비
<input> 사용자 이름, 비밀번호 등을 입력하는 칸
<label> 입력창에 대한 설명 (ex. "사용자 이름")
<button> 제출 버튼. 클릭하면 서버로 전송
onsubmit="함수명(event)" 자바스크립트 함수 호출 (기본 제출 방지 포함)

 

<head>

<meta charset="UTF-8">  
<title>마이 메모 앱</title>
  • 페이지의 문자 인코딩은 UTF-8 (한글 지원)
  • 브라우저 탭 제목은 "마이 메모 앱"

<link> 폰트 추가

<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700&display=swap" rel="stylesheet">
  •  웹 폰트인 'Noto Sans KR'을 불러오는 코드

<body>

<div class="container">
    <h1>...</h1>
    <p>...</p>

    <!-- 로그인 폼 -->
    <form id="loginForm" onsubmit="submitLoginForm(event)">...</form>

    <!-- 회원가입 폼 -->
    <form id="signupForm" onsubmit="submitSignupForm(event)">...</form>
</div>
  • 하나의 박스(.container) 안에 로그인과 회원가입 폼이 함께 들어 있음.
  • 폼을 제출할 때 각각의 자바스크립트 함수가 실행됨

로그인 폼

<form id="loginForm" onsubmit="submitLoginForm(event)">
    <div class="form-group">
        <label for="username">사용자 이름:</label>
        <input type="text" id="username" name="username" required>
    </div>
    <div class="form-group">
        <label for="password">비밀번호:</label>
        <input type="password" id="password" name="password" required>
    </div>
    <div class="buttons">
        <button type="submit">로그인</button>
    </div>
</form>
  • required 속성은 필수 입력 사항으로 입력 하지 않고 제출하면 브라우저가 경고창 띄움.

home.html 분석
2. CSS (화면 스타일 설정)

CSS는 웹페이지에 어떻게 보일지(모양, 색, 배치)를 정하는 부분

선택자 설명
body 화면 전체를 중앙 정렬, 배경색, 글꼴 지정
.container 폼이 담긴 박스. 최대 400px, 배경 흰색, 그림자
.form-group 각 입력창을 감싸는 박스
.form-group input 텍스트 입력창 스타일
.buttons button 파란색 버튼 스타일. 마우스 올리면 색 진해짐

 

<주요 스타일>

body {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
}
속성  설명
display: flex 중앙 정렬을 위해 Flexbox 사용
justify-content: center 가로축 중앙 정렬
align-items: center 세로축 중앙 정렬
height: 100vh 브라우저 화면 전체 높이를 사용 (vh = viewport height)

 

<.container 박스 스타일>

.container {
    max-width: 400px;
    width: 100%;
    background-color: #fff;
    border-radius: 10px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
  • max-width: 400px: 너무 커지지 않도록 최대 너비 제한
  • width: 100%: 모바일에서 가로폭 채우기
  • border-radius: 모서리를 둥글게
  • box-shadow: 그림자 효과로 박스가 살짝 떠보이게

<입력창 스타일>

.form-group input {
    padding: 0.75rem;
    border: 1px solid #ced4da;
    border-radius: 5px;
    width: 100%;
}
속성 설명
padding 입력창 안쪽 여백
border 테두리
border-radius 입력창도 살짝 둥글게
width: 100% 폼 너비만큼 꽉 채움
.form-group input:focus {
            border-color: #80bdff;
            box-shadow: 0 0 0 2px rgba(0,123,255,.25);
        }
  • 입력창에 포커스(클릭)했을 때는 파란색 테두리로 바뀜.

<버튼 스타일>

.buttons button {
    width: 100%;
    padding: 0.75rem;
    background-color: #007bff;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}
  • 파란색 배경의 버튼
  • cursor: pointer: 마우스 올리면 손가락 모양

자바스크립트 기초

JavaScript: 사용자의 행동(버튼 클릭 등)에 따라 데이터 전송, 알림, 화면 이동 등을 처리하는 기능

 

 [자바스크립트 기본 문법]

키워드 설명
let 바뀔 수 있는 값 저장
const 바뀌지 않는 값 저장 (상수)
= 값을 변수에 저장하는 기호

 

[함수 만들기]

function sayHello() {
    alert("안녕하세요!");
}
  • function: “이름이 있는 동작”을 만들 때 사용
  • sayHello(): 나중에 실행시키는 이름
  • { ... } 안에 실행할 코드를 넣음
  • alert(): 경고창 띄우는 함수

[이벤트 처리]

<button onclick="sayHello()">인사하기</button>
  • 이 버튼을 누르면 sayHello()라는 함수가 실행된다.

[조건문]

if (age > 18) {
    alert("성인입니다.");
} else {
    alert("미성년자입니다.");
}
  • if: 조건이 참일 때 실행
  • else: 그렇지 않을 때 실행

[fetch() – 서버와 통신]

fetch('/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(data)
})
  • fetch()는 서버와 데이터를 주고받을 때 사용
  • POST: 데이터를 보낼 때 사용하는 방식
  • JSON.stringify(data): 데이터를 JSON 형식 문자열로 바꿈

home.html 분석
3. JavaScript (동작 기능 처리)

 

<로그인 처리 함수>

function submitLoginForm(event) {
    event.preventDefault(); // 기본 동작(페이지 새로고침) 막기

    const formData = new FormData(event.target);
    const data = {
        username: formData.get('username'),
        password: formData.get('password')
    };

    fetch('/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
    })
    .then(response => response.json().then(body => ({ status: response.status, body: body })))
    .then(result => {
        if (result.status == 200) {
            alert(result.body.message);  // 로그인 성공 시 팝업
            window.location.href = '/memos'; // 메모 페이지로 이동
        } else {
            throw new Error(result.body.detail || '로그인이 실패하였습니다.');
        }
    })
    .catch((error) => {
        alert(error.message); // 오류 메시지 표시
    });
}

 

[코드 설명]

function submitLoginForm(event) {
    event.preventDefault();
  • function submitLoginForm() → 함수 만들기
  • event.preventDefault() → 폼이 기본 동작(새로고침) 못하게 막음
const formData = new FormData(event.target);
  • FormData() → 입력한 값들을 쉽게 모아주는 도구
  • event.target → 사용자가 입력한 <form> 요소 전체
const data = {
    username: formData.get('username'),
    password: formData.get('password')
};
  • 객체({})는 여러 개의 값을 하나로 묶는 구조
  • formData.get('username'): 입력한 사용자 이름을 꺼냄
fetch('/login', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
})
  • 서버 주소 /login으로 data를 보냄
  • POST 방식으로 보내고
  • 데이터를 JSON 문자열로 바꿈
.then(response => response.json().then(body => ({ status: response.status, body: body })))
  • .then() → 서버 응답을 받은 후 실행할 일
  • response.status → 응답 코드 (예: 200이면 성공)
  • response.json() → 응답 데이터를 JSON으로 해석
.then(result => {
    if (result.status == 200) {
        alert(result.body.message);
        window.location.href = '/memos';
    } else {
        throw new Error(result.body.detail || '로그인이 실패하였습니다.');
    }
})
  • 결과 상태가 200이면 성공 메시지 + 페이지 이동
  • 실패하면 에러 메시지를 띄움
.catch((error) => {
    alert(error.message);
});
}
  • .catch()는 오류가 발생했을 때 실행됨
  • alert()로 오류 메시지를 사용자에게 보여줌

 

<회원가입 처리 함수>

const data = {
    username: formData.get('username'),
    email: formData.get('email'),
    password: formData.get('password')
};

회원가입 함수도 단지 email 입력 항목이 하나 추가됐을 뿐 로그인 함수와 비슷하다.


변경된 코드 실행

main.py를 실행하면 디자인적으로 개선된 홈페이지가 뜨며, 로그인을 하면 /memos 로 잘 이동한다.


[참고]

가장 빠른 풀스택을 위한 플라스크 & FastAPI


다음 내용

 

[파이썬] FastAPI - 메모 앱 프로젝트 7: 보완(회원가입 등)

이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 6: 프론트엔드 페이지 개선이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 5: 웹페이지 개선이전 내용 [파이썬] FastAPI - 메모 앱 프로젝트 4: 사용자별 메

puppy-foot-it.tistory.com

728x90
반응형