Browse Source

사용자 상세 페이지 구현

Gayeon Park 3 years ago
parent
commit
f74a25531d

+ 2 - 2
realworld/src/main/java/com/dbs/realworld/controller/ArticleController.java

@@ -235,8 +235,8 @@ public class ArticleController {
         userDTO.setPassword("");
         data.put("user", userDTO);
 
-        // int viewer = (int) request.getSession().getAttribute("ssId");
-        int viewer = 1;
+        int viewer = (int) request.getSession().getAttribute("ssId");
+        // int viewer = 1;
         List<ArticleDTO> myArticles = this.articleService.findMyArticles(viewer, userId, articleId, feed);
         data.put("articles", myArticles);
         data.put("paging", this.articleService.calculatePagingInfo(myArticles, feed, userId));

+ 5 - 5
realworld/src/main/java/com/dbs/realworld/controller/UserController.java

@@ -189,13 +189,13 @@ public class UserController {
         userDTO.setPassword("");
         model.addAttribute("user", userDTO);
         
-        int viewer = (int) request.getSession().getAttribute("ssId");
+        // int viewer = (int) request.getSession().getAttribute("ssId");
         // int viewer = 9;          // delete
-        int articleId = -1;
+        // int articleId = -1;
         // int articleId = 1000052; // delete
-        List<ArticleDTO> myArticles = this.articleService.findMyArticles(viewer, userId, articleId, "your-feed");
-        model.addAttribute("myArticles", myArticles);
-        model.addAttribute("paging", this.articleService.calculatePagingInfo(myArticles, "your-feed", userId));
+        // List<ArticleDTO> myArticles = this.articleService.findMyArticles(viewer, userId, articleId, "your-feed");
+        // model.addAttribute("articles", myArticles);
+        // model.addAttribute("paging", this.articleService.calculatePagingInfo(myArticles, "your-feed", userId));
 
         return Views.USER_DETAIL;
     }

+ 2 - 2
realworld/src/main/resources/mybatis/mapper/user/ArticleMapper.xml

@@ -206,12 +206,12 @@
       ]]>
       <if test='userId != -1'>
         <![CDATA[
-          (
+          ,(
               SELECT COUNT(*) FROM article_like AL 
               WHERE AL.user_id     = #{userId}
               AND   AL.article_id  = AR.id
           )                   AS favorite
-          (
+          ,(
               SELECT COUNT(*) FROM user_follow UF
               WHERE  UF.from_user = #{userId}
               AND    UF.to_user   = AR.user_id

+ 8 - 8
realworld/src/main/webapp/WEB-INF/jsp/article/articleDetails.jsp

@@ -183,10 +183,10 @@
                             </div>
                             <div class="card-footer comment-footer">
                                 <div class="user-info">
-                                    <a href="userpage-others.html" class="comment-author">
+                                    <a href="/user/\${comment.writerId}" class="comment-author">
                                         <img class="comment-img" src="/resources/images/avatar.png" alt="">
                                     </a>
-                                    <a href="userpage-others.html" class="comment-author">\${comment.username}</a>
+                                    <a href="/user/\${comment.userId}" class="comment-author">\${comment.username}</a>
                                     <span class="date-posted"></span>
                                 </div>
                                 <i style="display: none" class="fas fa-trash-alt" onclick="deleteComment('\${comment.id}')"></i>
@@ -230,7 +230,7 @@
             value.textContent = count;
         })
 
-        // 좋아요 여부에 따른 버튼 표시
+        // 팔로우 여부에 따른 버튼 표시
         const followString = document.querySelectorAll('.follow-btn');
         followString.forEach(value => {
             if(value.classList.contains('active')) {
@@ -238,7 +238,7 @@
             }
         })
 
-        // 팔로우 여부에 따른 버튼 표시
+        // 좋아요 여부에 따른 버튼 표시
         const favoriteString = document.querySelectorAll('.favorite-btn');
         favoriteString.forEach(value => {
             if (value.classList.contains('active')) {
@@ -264,11 +264,11 @@
                 </h1>
                 <div class="article-meta">
                     <div class="metadata">
-                        <a href="userpage-my.html" class="profile-link">
+                        <a href="/user/${articleDetail.writerId}" class="profile-link">
                             <img src="/resources/images/avatar.png" alt="avatar">
                         </a>
                         <div class="article-info">
-                            <a href="userpage-my.html" class="name">
+                            <a href="/user/${articleDetail.writerId}" class="name">
                                 <c:out value="${articleDetail.writerName}"></c:out>
                             </a>
                             <span class="date">
@@ -327,11 +327,11 @@
             <div class="article-actions">
                 <div class="article-meta">
                     <div class="metadata">
-                        <a href="userpage-my.html" class="profile-link">
+                        <a href="/user/${articleDetail.writerId}" class="profile-link">
                             <img src="/resources/images/avatar.png" alt="avatar">
                         </a>
                         <div class="article-info">
-                            <a href="userpage-my.html" class="name">
+                            <a href="/user/${articleDetail.writerId}" class="name">
                                 <c:out value="${articleDetail.writerName}"></c:out>
                             </a>
                             <span class="date">

+ 1 - 1
realworld/src/main/webapp/WEB-INF/jsp/user/settings.jsp

@@ -51,7 +51,7 @@
         fetch("/user/settings/${user.id}", options)
             .then(response => {
                 if (response.status === 200) {
-                    location.href = '/user/settings/${user.id}'
+                    location.href = '/user/${user.id}'
                 }
             })
     }

+ 250 - 109
realworld/src/main/webapp/WEB-INF/jsp/user/userDetail.jsp

@@ -8,9 +8,38 @@
 <jsp:include page="/WEB-INF/jsp/include/head.jsp"></jsp:include>
 <script>
     let targetFeed = null;
-    const loadingArticles = document.querySelector('#article-loading');
-    const articleList = document.querySelector('#article-list');
+    let params = {
+        articleId: -1,
+        feed: 'your-feed'
+    };
+    let query = Object.keys(params)
+                .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params[key]))
+                .join('&');
+    let url = '/article/page/users/${user.id}?' + query;
+    let loadingArticles = null;
+    let articleList = null;
+    const tagSet = new Set();
+    const options = { method: 'GET' };
+    const follow = 'Follow ' + "${user.username}";
+    const unfollow = 'Unfollow ' + "${user.username}";
 
+    // feed를 바꾼 경우
+    const filterArticle = (option) => {
+        const { clickedFeed } = option;
+        const articles = document.querySelectorAll('.article-preview');
+
+        articles.forEach(article => {
+            let display = 'none';
+
+            // display 기본 값은 none이고 값이 block으로 변경되는 경우만 조건으로 건다.
+            if(clickedFeed === 'favorite-feed') {
+                display = 'block';
+            } else if(clickedFeed === 'your-feed') {
+                display = 'block';
+            }
+        });
+    }
+    // 게시글 목록 로딩
     const setLoading = (type) => {
         let loadingDisplay = null;
         let contentDisplay = null;
@@ -22,11 +51,22 @@
             loadingDisplay = 'none';
             contentDisplay = 'block';
         }
-
+        
         loadingArticles.style.display = loadingDisplay;
         articleList.style.display = contentDisplay;
     }
 
+    // 자신의 게시글이 없는 경우
+    const noContent = () => {
+        if(articleList.firstChild === null) {
+            const p = document.createElement('p');
+
+            p.classList.add('article-preview');
+            p.textContent = 'No articles are here... yet.';
+            articleList.appendChild(p);
+        }
+    }
+
     const focusFeed = () => {
         // 이미 포커싱되었으면 중단
         if(event.target.parentNode.classList.contains('active')) {
@@ -42,8 +82,128 @@
         // targetFeed 초기화
         targetFeed = currentFeed;
         setLoading('on');
+
+        // 게시글 초기화
+        while(articleList.firstChild) {
+            articleList.firstChild.remove();
+        }
+
+        params = {
+            articleId: -1,
+            feed: targetFeed.id
+        }
+        query = Object.keys(params)
+                .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params[key]))
+                .join('&');
+        url = '/article/page/users/${user.id}?' + query;
+
+        fetch(url, options)
+            .then(response => response.json())
+            .then(json => {
+                const { articles, paging } = json;
+                username = "${ssUsername}";
+
+                displayArticles(articles);
+                nextPageLoad(articles, paging);
+            })
+        filterArticle({ 'clickedFeed': currentFeed.id });
+        setTimeout(() => {
+            setLoading('off');
+            noContent();
+        }, 1000);
+    }
+
+    // 좋아요 버튼
+    const favoriteBtn = (indexNumber) => {
+        const favoriteNum = event.target.querySelector('.count').textContent;
+        const favorite = event.target.querySelector('.favorite-btn');
+        let counts = parseInt(favoriteNum);
+        let newCounts = counts + 1;
+
+        // 로그인한 경우에만 실행
+        if("${ssIsLogin}" === "true") {
+            if(event.target.classList.contains('active')) { // 좋아요 버튼 해지
+                event.target.querySelector('.count').textContent = counts - 1;
+                event.target.classList.remove('active');
+
+                fetch('/article/' + indexNumber + '/favorite', { method: 'DELETE' })
+                    .then(response => {
+                        if(response.status === 200) {
+                            return ;
+                        }
+                    });
+            } else { // 좋아요 버튼 활성
+                event.target.querySelector('.count').textContent = newCounts;
+                event.target.classList.add('active');
+
+                fetch('/article/' + indexNumber + '/favorite', {
+                    body: JSON.stringify({
+                        articleId: "${article.id}",
+                        userId: "${ssId}",
+                        created: new Date()
+                    }),
+                    method: 'POST',
+                    headers: {
+                        "Content-Type": "application/json"
+                    }
+                })
+                    .then(response => {
+                        if(response.status === 201) {
+                            return;
+                        }
+                    })
+            }
+        } else {
+            location.href = "/user/signin";
+        }
     }
 
+    // 사용자 팔로우
+    const userFollow = () => {
+        const followBtn = document.querySelector('.follow-btn');
+
+        if("${ssIsLogin}" === "true") { // 로그인한 경우에만 팔로우 기능 사용 가능
+            if(followBtn.classList.contains('active')) { // 팔로우 취소
+                followBtn.querySelector('.follow').textContent = follow;
+                fetch("/user/unfollow", {
+                    body: JSON.stringify({
+                        fromUser: "${ssId}",
+                        toUser: "${user.id}"
+                    }),
+                    method: 'DELETE',
+                    headers: {
+                        "Content-Type": "application/json"
+                    }
+                })
+                    .then(response => {
+                        if(response.status === 200) {
+                            return followBtn.classList.remove('active');
+                        }
+                    })
+            } else { // 팔로우하기
+                followBtn.querySelector('.follow').textContent = unfollow;
+                fetch("/user/follow", {
+                    body: JSON.stringify({
+                        fromUser: "${ssId}",
+                        toUser: "${user.id}"
+                    }),
+                    method: 'POST',
+                    headers: {
+                        "Content-Type": 'application/json'
+                    }
+                })
+                    .then(response => {
+                        if(response.status === 201) {
+                            return followBtn.classList.add('active');
+                        }
+                    })
+            }
+        } else { // 로그인하지 않은 경우 로그인화면으로 이동
+            location.href = "/user/signin"
+        }
+    }
+
+    // 게시글 목록 조회
     const displayArticles = (articles) => {
         articles.forEach(article =>  {
             const domParser = new DOMParser();
@@ -53,11 +213,11 @@
                 `
                 <div class="article-meta">
                     <div class="metadata">
-                        <a href="javascript.void(0);">
-                            <img src"/resources/images/avatar.png" alt="avatar">
+                        <a href="/user/\${article.writerId}">
+                            <img src="/resources/images/avatar.png" alt="avatar">
                         </a>
                         <div class="article-info">
-                            <a href="javascript.void(0);" class="name">\${article.writerName}</a>
+                            <a href="/user/\${article.writerId}" class="name">\${article.writerName}</a>
                             <span class="date"></span>
                         </div>
                     </div>
@@ -80,9 +240,9 @@
             // 게시글 내용 및 게시글태그
             const domStrPreviewLink =
                 `
-                <a href="/article/\${articleId}" class="preview-link">
-                    <h1 class="preview-title">\${articleTitle}</h1>
-                    <p>\${articleSubtitle}</p>
+                <a href="/article/\${article.id}" class="preview-link">
+                    <h1 class="preview-title">\${article.title}</h1>
+                    <p>\${article.subtitle}</p>
                     <div class="tag-data">
                         <span>Read more...</span>
                         <ul class="tag-list">
@@ -110,12 +270,79 @@
             const articlePreview = document.createElement('article');
             articlePreview.classList.add('article-preview');
             articlePreview.append(divArticleMeta, aPreviewLink);
+            articlePreview.style.display = 'block';
             articleList.appendChild(articlePreview);
         })
     }
 
+    const nextPageLoad = (articles, paging) => {
+        // 더 조회될 수 있는 게시글이 있는지 여부
+        if(paging.isNext) {
+            const moreButton = document.createElement('button');
+            const articleList = document.querySelector('#article-list');
+            const loading = document.createElement('div');
+
+            moreButton.textContent = '더보기';
+            moreButton.classList.add('more-button');
+            loading.setAttribute('id', 'loading');
+            articleList.appendChild(moreButton);
+
+            // more 버튼 클릭 시 다음페이지 게시물을 가져온다
+            moreButton.onclick = () => {
+                articleList.removeChild(moreButton);
+                articleList.appendChild(loading); // 로딩 생성
+
+                // 현재 페이지의 마지막 게시글 id
+                params['articleId'] = articles[articles.length - 1].id;
+                query = Object.keys(params)
+                        .map(key => encodeURIComponent(key) + '=' + encodeURIComponent(params[key]))
+                        .join('&');
+                url = '/article/page/users/${user.id}?' + query;
+
+                fetch(url, options)
+                    .then(response => response.json())
+                    .then(json => {
+                        const {
+                            articles,
+                            paging
+                        } = json;
+                        username = "${ssUsername}";
+                        setTimeout(() => {
+                            articleList.removeChild(loading);
+                            displayArticles(articles);
+                            nextPageLoad(articles, paging);
+                        }, 1000);
+                    })
+            }
+        }
+    }
     window.onload = () => {
         targetFeed = document.querySelector('.nav-item.active');
+        loadingArticles = window.document.querySelector('#article-loading');
+        articleList = document.querySelector('#article-list');
+       
+        setLoading('on');
+        // fetch api call
+        fetch(url, options)
+            .then(response => response.json())
+            .then(json => {
+                const { articles, paging } = json;
+                username = "${ssUsername}";
+
+                displayArticles(articles);
+                nextPageLoad(articles, paging);
+
+                // hide loadings
+                setTimeout(() => {
+                    setLoading('off');
+                    noContent();
+                }, 1000);
+            });
+
+        const followString = document.querySelector('.follow-btn');
+        if(followString.classList.contains('active')) {
+            followString.querySelector('.follow').textContent = unfollow;
+        }
 
     }
 
@@ -126,9 +353,8 @@
     <jsp:include page="/WEB-INF/jsp/include/header.jsp"></jsp:include>
     <!-- user-page content -->
 
-    ${myArticles}
-    ${paging}
     ${user}
+    
 
     <div class="user-page">
         <!-- User Info -->
@@ -136,19 +362,22 @@
             <div class="container">
                 <div class="row">
                     <div class="col-10">
-                        <img src="img/avatar.jpg" alt="" class="user-img">
-                        <h4 class="user-name">username</h4>
-                        <p class="profile-bio">short bio</p>
-                        <a href="settings.html" class="action-btn btn-sm">
+                        <img src="/resources/images/avatar.png" alt="" class="user-img">
+                        <h4 class="user-name"><c:out value="${user.username}"></c:out></h4>
+                        <p class="profile-bio"><c:out value="${user.shortBio}"></c:out></p>
                             <c:choose>
-                                <c:when test="{id eq ssId}">
-                                    <i class="fas fa-cog"></i>&nbsp;Edit Profile Settings
+                                <c:when test="${user.id eq ssId}">
+                                    <a href="/user/settings/${user.id}" class="action-btn btn-sm">
+                                        <i class="fas fa-cog"></i>&nbsp;Edit Profile Settings
+                                    </a>
                                 </c:when>
                                 <c:otherwise>
-                                    <i class="fas fa-plus"></i>&nbsp;Follow Gerome
+                                    <button class="action-btn btn-sm follow-btn" onclick="userFollow()">
+                                        <i class="fas fa-plus"></i>
+                                        <span class="follow">&nbsp;Follow <c:out value="${user.username}"/></span>
+                                    </button>
                                 </c:otherwise>
                             </c:choose>
-                        </a>
                     </div>
                 </div>
             </div>
@@ -163,7 +392,7 @@
                             <li id="your-feed" class="nav-item active">
                                 <a href="javascript:void(0);" onclick="focusFeed()">My Articles</a>
                             </li>
-                            <li id="favoreted-feed" class="nav-item">
+                            <li id="favorite-feed" class="nav-item">
                                 <a href="javascript:void(0);" onclick="focusFeed()">Favorited Articles</a>
                             </li>
                         </ul>
@@ -177,95 +406,7 @@
                         </article>
 
                         <!-- Article List -->
-                        <div id="article-list" style="display: none;">
-                            <!-- <article class="article-preview">
-                                <div class="article-meta">
-                                    <div class="metadata">
-                                        <a href="#" class="profile-link">
-                                            <img src="img/avatar.jpg" alt="">
-                                        </a>
-                                        <div class="article-info">
-                                            <a href="#" class="name">username</a>
-                                            <span class=date>November 24, 2021</span>
-                                        </div>
-                                    </div>
-                                    <div>
-                                        <button class="favorite-btn">
-                                            <i class="fas fa-heart"></i>
-                                            <span class="count">564</span>
-                                        </button>
-                                    </div>
-                                </div>
-                                <a href="article-my.html" class="preview-link">
-                                    <h1 class="preview-title">Create a new implementation</h1>
-                                    <p>join the community by creating a new implementation</p>
-                                    <div class="tag-data">
-                                        <span>Read more...</span>
-                                        <ul class="tag-list">
-                                            <li class="tag">implementations</li>
-                                            <li class="tag">implementations</li>
-                                        </ul>
-                                    </div>
-                                </a>
-                            </article>
-                            <article class="article-preview">
-                                <div class="article-meta">
-                                    <div class="metadata">
-                                        <a href="#" class="profile-link">
-                                            <img src="img/avatar.jpg" alt="">
-                                        </a>
-                                        <div class="article-info">
-                                            <a href="#" class="name">username</a>
-                                            <span class=date>November 24, 2021</span>
-                                        </div>
-                                    </div>
-                                    <div>
-                                        <button class="favorite-btn">
-                                            <i class="fas fa-heart"></i>
-                                            <span class="count">564</span>
-                                        </button>
-                                    </div>
-                                </div>
-                                <a href="article-my.html" class="preview-link">
-                                    <h1 class="preview-title">Create a new implementation</h1>
-                                    <p>join the community by creating a new implementation</p>
-                                    <div class="tag-data">
-                                        <span>Read more...</span>
-                                        <ul class="tag-list">
-                                            <li class="tag">implementations</li>
-                                        </ul>
-                                    </div>
-                                </a>
-                            </article>
-                            <article class="article-preview">
-                                <div class="article-meta">
-                                    <div class="metadata">
-                                        <a href="#" class="profile-link">
-                                            <img src="img/avatar.jpg" alt="">
-                                        </a>
-                                        <div class="article-info">
-                                            <a href="#" class="name">username</a>
-                                            <span class=date>November 24, 2021</span>
-                                        </div>
-                                    </div>
-                                    <div>
-                                        <button class="favorite-btn">
-                                            <i class="fas fa-heart"></i>
-                                            <span class="count">564</span>
-                                        </button>
-                                    </div>
-                                </div>
-                                <a href="article-my.html" class="preview-link">
-                                    <h1 class="preview-title">Create a new implementation</h1>
-                                    <p>join the community by creating a new implementation</p>
-                                    <div class="tag-data">
-                                        <span>Read more...</span>
-                                        <ul class="tag-list">
-                                            <li class="tag">implementations</li>
-                                        </ul>
-                                    </div>
-                                </a>
-                            </article> -->
+                        <div id="article-list" style="display: block;">
                         </div>
                     </div>
                 </div>