본문 바로가기
💻 뚝딱뚝딱/북북클럽

[개발일지#029] 타임라인 등록 수정하기 (레이아웃 변경)

by 뚜루리 2024. 3. 22.
728x90
320x100
[참고]
김영한님 스프링 강의를 바탕으로 진행되는 토이프로젝트의 과정을 기록하는 글입니다. 
둥근 피드백은 언제나 환영입니다.
[오늘의 개발내용]
1. 타임라인 컨트롤러 수정하기
2. 로그인시 로그인 아이디 세션에 추가하기
3. 타임라인 등록 Html 수정하기

 

 

[서론]

회원가입 화면 구현한김에 이어서 타임라인 등록 부분 수정하기

수정할 부분이 많고 오래걸려서 이번 일지에는 레이아웃만 수정할 예정.

 

1. 타임라인 컨트롤러 수정하기

TimelineController.java

package toyproject.bookbookclub.web.timeline;

//////생략//////

@Controller
@RequiredArgsConstructor
@RequestMapping("/timeline")
public class TimelineController {

    private final TimelineValidator timelineValidator;
    private final TimeLineRepository timeLineRepository;
    private final MemberRepository memberRepository;
    private final FileStore fileStore;

	//////생략//////

    @GetMapping("/add")
    public String addForm(HttpServletRequest request, Model model) throws IOException {
        String loginId = (String) request.getSession().getAttribute("loginId");

        Member member = memberRepository.findById(loginId);
        UploadFile profileImage = member.getProfileImage();
        Timeline timeline = new Timeline();
        timeline.setMemberId(loginId);

        model.addAttribute("timeline", timeline);
        model.addAttribute("member", member);
        model.addAttribute("profileImage", profileImage);
        return "timeline/addForm";
    }

    //////생략//////
}
  • 타임라인 등록 폼을 띄울 때 멤버 아이디와 닉네임은 보일 수 있도록 세션에 저장된 로그인 아이디로 멤버 객체를 가져온다. (그러다보니 멤버 레파지토리도 주입해줌)
  • 프로필 이미지도 멤버 객체에 있는 프로필 이미지를 UpoladFIle 형태로 가져와서 일단 Model에 넣어주었다.  (이 부분을 어떻게 구현해야 할지 몰라서 지피티한테 많이 물어보고 삽질함...)
  • 근데 보면 request.getSession().getAttribute("loginId")를 가져오는 부분이 있는데 이 부분은 로그인 시에 세션에 추가하도록 했다.  (아래 소스 참조)

 

2. 로그인시 세션에 로그인 아이디 추가하기

LoginController.java

package toyproject.bookbookclub.web.login;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import toyproject.bookbookclub.domain.Members.Member;
import toyproject.bookbookclub.domain.Login.LoginForm;
import toyproject.bookbookclub.domain.Login.LoginService;
import toyproject.bookbookclub.web.SessionConst;

@Slf4j
@Controller
@RequiredArgsConstructor
public class LoginController {

    private final LoginService loginService;

    @GetMapping("/login")
    public String loginForm(@ModelAttribute("loginForm") LoginForm form) {
        return "login/loginForm";
    }

    @PostMapping("/login")
    public String login(@Valid @ModelAttribute LoginForm form, BindingResult bindingResult
    ,@RequestParam(defaultValue = "/") String redirectURL,
    HttpServletRequest request) {

        if (bindingResult.hasErrors()) {
            return "login/loginForm";
        }

        Member loginMember = loginService.login(form.getLoginId(),
                form.getPassword());
        log.info("login? {}", loginMember);

        if (loginMember == null) {
            bindingResult.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다.");
            return "login/loginForm";
        }
    //세션이 있으면 있는 세션을 반환, 없으면 신규 세션 생성
    HttpSession session = request.getSession();
    session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember); 
    session.setAttribute("loginId", form.getLoginId()); //로그인 아이디 세션에 추가!

    //redirectUrl 적용
    return "redirect:" + redirectURL;
    }
}

 

 

3. 타임라인 등록 Html 수정하기

addForm.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <link href="../css/bootstrap.min.css"
          th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
    <script th:src="@{/js/timeline/addForm.js}"></script>
    <style>
        .container {
            max-width: 560px;
        }
        .field-error {
            border-color: #dc3545;
            color: #dc3545;
        }
        #profileImagePreview {
            border-radius: 70%;
            overflow: hidden;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="py-5 text-center">
        <h2 th:text="#{page.addTimeline}">타임라인 등록</h2>
    </div>

    <div th:if="${profileImage}" style="margin-bottom: 10px;">
        <img id="profileImagePreview" th:src="|/basic/members/images/${profileImage.getStoreFileName()}|" width="16" height="16"/>
        <span class="h5" th:text="${member.nickName}"></span>
        <span class="h5" style="color: gray;" th:text="'@' + ${member.id}"></span>
    </div>

    <form action="timeline.html" id="addForm" th:action th:object="${timeline}" method="post">
        <div th:unless="${profileImage}">
            <svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="currentColor" class="bi bi-person-circle" viewBox="0 0 16 16" style="color: gray;">
                <path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0"/>
                <path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1"/>
            </svg>
            <span class="h6" style="color: gray;" th:text="'@' + ${member.id}"></span>
        </div>

        <div style="margin-bottom: 10px;">
            <span class="h6">내가 읽은 책 / </span>
            <span class="h6"  th:if="*{bookId}" th:text="*{bookId}"></span>
            <span class="h6" style="color: red;" th:unless="*{bookId}">읽은 책을 검색해주세요.</span>
            <button type="button" class="btn btn-outline-info btn-sm">읽은 책 찾기</button>
        </div>

        <div style="width: 100%; height: 150px;">

            <div style="float: left; width: 20%; text-align: center;">
                <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" fill="currentColor" class="bi bi-book" viewBox="0 0 16 16" style="color: gray; padding-right: 10px;">
                    <path d="M1 2.828c.885-.37 2.154-.769 3.388-.893 1.33-.134 2.458.063 3.112.752v9.746c-.935-.53-2.12-.603-3.213-.493-1.18.12-2.37.461-3.287.811zm7.5-.141c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783"/>
                </svg>
                <input type="text" id="bookImg" th:field="*{bookImg}" class="form-control" placeholder="이미지를 입력하세요" style="display: none;">
            </div>

            <div style="float: right; width: 80%;">
                <div class="form-floating">
                    <textarea class="form-control" id="content" th:field="*{content}" th:errorclass="is-invalid"
                              placeholder="느낀점을 공유해주세요." style="height: 120px; resize: none;"></textarea>
                    <label for="content">소개글</label>
                    <div id="contentCounter" style="font-size: 0.87em; color: gray; text-align: right;">0/120</div>
                    <div class="is-invalid" th:errors="*{content}">내용 오류</div>
                    <div class="is-invalid" id="contentError" style="display: none;">글자수 제한</div>
                </div>
            </div>
        </div>
        <div style="width: 100%;">
            <div class="row">
                <div class="col">
                    <button class="w-100 btn btn-primary btn-lg" type="submit" id="saveBtn"
                            th:text="#{button.timeline.save}">타임라인 등록</button>
                </div>
                <div class="col">
                    <button class="w-100 btn btn-secondary btn-lg" onclick="location.href='allTimeline.html'"
                            th:onclick="|location.href='@{/timeline}'|" type="button" th:text="#{button.cancel}">취소</button>
                </div>
            </div>
        </div>

    </form>
</div> <!-- /container -->
</body>
</html>

전체적인 레이아웃이 싹 바뀌었는데

  1. 로그인 후에만 타임라인 등록이 가능하니까 프로필 이미지와 프로필 아이디를 띄우도록 했고
  2. 내가 읽은책은 '읽은 책 찾기'를 통해서 책 이름, 책 아이디, 책 이미지를 불러올껀데 그건 나중에 구현 예정. 
  3. 그리고 글 내용을 입력하면 회원 소개글과 같이 120자가 초과되면 입력되지 않고 경고문이 뜬다.

 

그 밖에 수정할 부분이 많은데 너무 늘어지는 것 같아서 끊어서 커밋하려고 함. 

 

 


(+) 로그인 화면 수정

<div class="col">
    <button class="w-100 btn btn-secondary btn-lg" type="button"
            th:onclick="|location.href='@{/basic/members/join}'|">
        회원 가입
    </button>
</div>

  • 이건 번외인데 로그인 할 때 회원가입, 취소 보다는 회원가입 / 로그인이 나을 것 같아서 회원가입 버튼을 추가했다. 
728x90
320x100