- html, css, javascript , node express 를 이용하여 프론트 작업
- 서버연결은 라우터를 활용해서 연결
- 프론트앤드 작업 (me)
- 백앤드 개발자와 협업 , 서버부분은 백앤드 개발자 1명과 함께
instagram & facebook과 같은 SNSt사이트를 구현한다. 로그인 페이지 , 홈페이지 가입, 프로필 수정, 게시글 올리기, 댓글구현 댓글수정, 사진 삭제, 좋아요 를 구현하여 소통할수 있는 사이트를 만든다
프론트앤드 개발 :) 기여도 100%
- 이메일 주소, 비밀번호 입력
$(document).ready(function(){
$("#loginbtn").on("click", function (e) {
e.preventDefault();
var email = $("#email").val();
var password = $("#password").val()
if(confirm("로그인하시겠습니까?")){
$.ajax ({
type: "POST",
url:"/login",
data:{
EMAIL: email,
PASSWORD: password
},
success:function(res){
if(res ==="true"){
alert("로그인 성공");
window.location.assign("/mainpage")
} else if ( res === "false"){ alert("로그인 실패")}
else {}
}
})
}
})
})
- 서버에 저장된 session을 통해서 로그인 유지
request.post(url, {
json: data
}, (error, response, body) => {
if (!error && response.statusCode === 200) {
let loginUser= response.body.login
req.session.IDX = loginUser.IDX;
req.session.EMAIL = loginUser.EMAIL;
req.session.NAME = loginUser.NAME;
req.session.INTRODUCE = loginUser.INTRODUCE;
req.session.PATH= loginUser.PATH;
res.send("true")
} else {
res.send("false")
}
});
- 세션을 통해서 로그인 유지 / 세션이 없다면 메인페이지 이동
if(!req.session.IDX){
res.redirect('/');
return;
}
-
닉네임
- 빈칸일 경우
if (!name) { alert("닉네임을 확인해 주세요") $("#name").focus(); return; }
- 이메일 입력
- 이메일 정규식 사용
var regExpEm = /^[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_\.]?[0-9a-zA-Z])*\.[a-zA-Z]{2,3}$/i;
- 이메일 형식확인
if(email && !regExpEm.test(email)){ alert("이메일이 형식에 맞지 않습니다") $(this).val(''); $("#email").val(''); $("#email").focus(); return; }
- 이메일 중복
$.ajax({ type:"GET", url:"http://13.125.149.206/api/user/duplicate/"+email, success:function(res){ if( res.duplicateUser ){ alert("이메일 중복") $("#email").focus(); } else { $(".secondJoin").show(); $(".firstJoin").hide(); } }, })
- 빈칸일 경우
if (!email) { alert("이메일을 확인해 주세요") $("#email").focus(); return; }
-
비밀번호
- 최대 8글자 이상입력
if(password && password.length<8) { alert("비밀번호 숫자,영문자 최소 8자리 이상") $(this).val(''); $("#password").val(''); $("#password").focus(); }
- 비밀번호 확인창과 일치 하지 않을 경우
$("#repassword").on("blur",function(e){ let password = $("#password").val(); let repassword = $("#repassword").val(); if (password !== repassword){ alert("비밀번호가 불일치") $(this).val(''); $("#password").val(''); $("#password").focus(); return; } })
- 빈칸일 경우
if (!password) { alert("패스워드를 확인해 주세요") $("#password").focus(); return; }
- 최대 8글자 이상입력
-
NEXT 버튼을 이용해 프로필사진/ 한줄자기소개 show
$(".firstJoin").hide();
$(".secondJoin").show();
-
프로필 사진
- 필수 입력사항 아님
//프로필 사진 미리보기 function readURL(input) { if (input.files && input.files[0]) { //파일이 있으면 var reader = new FileReader(); //파일을 읽는 객체를 생성 reader.onload = function(e) { //파일 읽는것에 성공을 하면 e벤트 함수 실행 $('#preImg').attr('src', e.target.result); } //id가 preImg인 div의 src에 타겟이벤트 결과값을 반환해 src에 넣어줘라 reader.readAsDataURL(input.files[0]);//file내용을 읽어 dataURL형식의 문자열로 저장해라 } } //서버에 저장하는 방식이 아니라 input type =file 의 파일 업로드 형식을 미리보기로 보여준다 //서버에 저장하는 방식은 s3에 저장함 axios 호출사용 if(document.getElementById("joinprofile").files.length >= 1){ selectedFile = document.getElementById("joinprofile").files[0]; const extensionUrl = "http://13.125.149.206/api/common/getExtension?type="+selectedFile.type axios.get(extensionUrl) //처음 데이터의 extension을 가져오는 호출을 실행 .then(res => { var data = res.data const getUploadUrl = "http://13.125.149.206/api/common/fileUploadUrl?mimetype="+selectedFile.type+"&extension="+data.extension axios.get(getUploadUrl)//서버로 올릴 주소를 받아옴 .then(s3Res => { let signatureUrl = s3Res.data.url axios.put(signatureUrl, selectedFile, { }) //서버에 올릴 주소와 함께 data를 함께 put함 .then(function (s3res,data) { var URL =s3res.request.responseURL SRC.push(URL) }); }); }); }
-
수정 버튼
// img클릭시 반환값이 없을때
if(!src ){
src=proimg;
}else if( $("#proImg").attr('src')=='/img/not.png'){
src='';
}else {
imgSrc=src.split("?");
src=imgSrc[0];
}
if(confirm("수정하시겠습니까?")){
$.ajax({
type:"PUT",
url:"/edit",
data: {
NAME: name,
PASSWORD:password,
INTRODUCE:introduce,
PATH:src,
},
success:function(res){
if(res==="true"){
alert("수정완료")
window.location.assign("/mainpage")
}else if( res === "false"){
alert("수정실패")
}
else{}
}
})
}
})
- 탈퇴 버튼
//탈퇴 ajax
$("#deletebtn").on("click",function(e){
if(confirm("정말로 탈퇴하시겠습니까?")){
$.ajax({
type:"DELETE",
url:"/deleteEdit",
success:function(res){
if(res==="true"){
alert("탈퇴되셨습니다")
window.location.assign("/")
}else if( res === "false"){
alert("에러발생")
}
else{}
}
})
}
})
- 수정과 탈퇴 url을 라우터로 보내서 세션값을 이용한다
//회원정보 수정
router.put('/edit',(req,res)=>{
var url ="http://13.125.149.206/api/user";
var email= req.session.EMAIL;
var idx = req.session.IDX;
var name = req.body.NAME;
var introduce = req.body.INTRODUCE;
var password= req.body.PASSWORD;
var path= req.body.PATH;
var data ={
IDX : idx,
EMAIL: email,
NAME: name,
INTRODUCE : introduce,
PASSWORD: password,
PATH:path
}
request.put(url,{
json:data
},(error,response,body)=>{
if(!error &&response.statusCode===200){
// console.log(response.body)
req.session.NAME =req.body.NAME;
req.session.INTRODUCE = req.body.INTRODUCE;
req.session.PATH = req.body.PATH;
// console.log(req.session)
res.send("true")
} else{
res.send("false")
}
})
})
//탈퇴
router.delete('/deleteEdit',(req,res)=>{
var url ="http://13.125.149.206/api/user";
var idx = req.session.IDX;
var data ={
IDX : idx,
}
request.delete(url,{
json:data
},(error,response,body)=>{
if(!error &&response.statusCode===200){
res.send("true")
} else{
res.send("false")
}
})
})
- 한줄 자기소개
- Introduce
- 필수 입력 사항 아님
- 사이트 저장된 모든 피드 21개씩 paging
let pageNum=1;
getUserFeed(pageNum);
function getUserFeed(pageNum){
$(".box").empty();
var limit = 21 ;
$.ajax({
type:"GET",
url:"http://13.125.149.206/api/feed",
success:function(res){
var pageCnt = Math.ceil(res.result.length/limit);
for (let i = (res.result.length-1)-(pageNum-1)*limit; i>(res.result.length-1)-(pageNum*limit); --i){
var html = "<div class = 'feedbox'><a href='/mainpage/feed?IDX="+res.result[i].IDX+"'><img id=myfeed src ="+res.result[i].PATH+"></a></div>"
if(res.result[i].PATH==null) { //피드이미지가 없을경우 대체이미지를 보여준다
var html="<div class='feedbox'><img id=myfeed src='/img/nothing.png'></div>"
}
$('.box').append(html)
}
getPagingBtn(pageCnt);
}
})
}
//피드 페이징 버튼 생성
function getPagingBtn (pageCnt) {
$('.paging').empty();
for (let i =0; i < pageCnt; i++) {
let cnt = i + 1;
let prev = '<div class="direction prev">이전</div>'
let pBtn = '<div class="pBtn ">'+cnt+'</div>'
let next = '<div class="direction next">다음</div>'
if (i == 0) {
$('.paging').append(prev);
$('.paging').append(pBtn);
$('.pBtn').addClass("strong");
} else if (i == pageCnt - 1) {
$('.paging').append(pBtn);
$('.paging').append(next);
} else {
$('.paging').append(pBtn);
}
}
$('.prev').on('click', function(e) {
// alert(pageNum)
if (pageNum == 1) {
alert('이전 페이지가 없습니다')
} else {
pageNum = pageNum - 1;
}
getUserFeed(pageNum)
})
$('.pBtn').on('click', function(e) {
var num = $(this).text()
// console.log(num)
pageNum = num;
$('.pBtn').removeClass('strong')
$(this).addClass('strong')
getUserFeed(pageNum)
})
$('.next').on('click', function(e) {
if (pageNum == pageCnt) {
alert('다음 페이지가 없습니다')
} else {
pageNum = pageNum*1 + 1;
$('.pBtn').removeClass('strong') ;
$('.pBtn').next('.pBtn').addClass('strong')
getUserFeed(pageNum)
}
})
}
- 텍스트 애니메이션 효과
- feed click 시 feed 상세 페이지로 이동
- write button 클릭시 feedwrite modal 창을 보여준다
* 현재 로그인 된 사용자 사진과 닉네임을 보여준다 ("(사용자닉네임)님 당신의 이야기를 보여주세요")
* 사진 입력 - 사진 미리보기 가능
* textarea를 통해서 feedcontent 입력
* submit으로 ajax를 이용해 서버에 POST후 /profile 이동.
* X 버튼을 누르면 feedwrite 창을 hidden 시킨다
// 글쓰기 모달창 버튼 클릭 ajax
$('#feedupbtn').on("click", function (e) {
let content = $("#feedcontent").val();
let feedimg=$("#feedUpImg").attr("src");
var src = SRC.slice(-1)[0];
// 제일마지막 배열값을 불러와 사진을 저장할수 있도록 url을 가져온다
if (feedimg==="/img/feedup.png" ) {
alert("사진을 빼먹었어요!!")
$("#content").focus();
return;
}
// console.log()
if (confirm("피드를 등록할까요?")) {
var imgSrc = src.split("?");
src = imgSrc[0]
e.preventDefault();
$.ajax({
type: "POST",
url: "/mainpage/feedwrite",
data: {
CONTENT: content,
PATH: src
},
success:function(res){
// console.log("data____",res)
if(res==="true"){
alert("피드등록 성공")
window.location.assign("/mainpage")
} else if( res === "false"){
alert("피드등록 실패")
}
}
})
}
})
- hambuger menu 버튼
* input type = button 과 label을 이용해서 버튼 생성 , 클릭시 nav메뉴 show
* 현재 로그인된 사용자 이름을 보여준다
* HOME button - /mainpage 이동
* 회원정보 button - /editinfo 이동
* 이용약관 button - /mainpage/service 이동
* 개인정보 취급방침 button - /mainpage/sns 이동
* 로그아웃 button - session 삭제, index로 이동
- 현재 로그인된 사용자 프로필 사진
* click 시 /profile 이동
* 내가 작성한 feed sort
-
로고 클릭시 /mainpge 이동
-
session이 없을 경우 index페이지로 이동
router.get('/logout', function(req, res, next) {
req.session.destroy();
res.redirect('/')
});
- header 부분 프로필 클릭시 /feedprofile 페이지 이동
- 클릭한 프로필 user가 작성한 feed를 모아서 볼수 있다.
//피드모아서 화면에 보여주기 - 각각의 유저의 피드 모아보기
$.ajax({
type:"GET",
url:"http://13.125.149.206/api/feed?userIdx="+user_Idx,
success:function(res){
// console.log(res)
for(i=res.result.length-1; i>-1; --i){
var html ="<div class = 'feedbox'><a href='/mainpage/feed?IDX="+res.result[i].IDX+"'><img id=myfeed src ="+res.result[i].PATH+"></a></div>"
$(".feed_box").append(html)
if(res.result[i].PATH==null) {
var html="<div class='feedbox'><img id=myfeed src='/img/nothing.png'></div>"
// return;
}
}
}
})
-
프로필 편집 button 클릭시 /editinfo 페이지 이동
- IDX가 session.IDX와 일치시만 버튼생성
var loginUser_Idx= $(".feedidx").html(); //프로필클릭한 유저의 idx var feedUser_Idx= $(".loginidx").html(); //현재 로그인유저의 session.IDX if(loginUser_Idx !== feedUser_Idx){ $(".feedPlusbox").css("display","none"); $(".useredit").css("display","none"); }
-
사용자의 프로필 ( 사진, 닉네임, e-maill, introduce ) 나타냄
- 회원정보 수정하면 반영되어 나타남
//쿼리값에 user idx값을 부여 , 그값을 가져오는 함수
openparam=getUrlParams();
user_Idx=openparam.userIdx;
$.ajax({
10000
span>
type:"GET",
url:"http://13.125.149.206/api/user/"+user_Idx,
async: false,
success:function(res){
var result = res.result[0];
var feedUserEmail = result.EMAIL;
var feedUserName = result.NAME;
var feedUserIntroduce = result.INTRODUCE;
var feedUserPhoto=result.PATH;
var feedUserIdx= result.IDX;
$('dt').html(feedUserName);
$("#feeduserImg").attr('src',feedUserPhoto);
$(".feedintroduce").html(feedUserIntroduce);
$(".feedemail").html(feedUserEmail);
$("#preImg").attr('src',feedUserPhoto);
$(".feedidx").html(feedUserIdx);
if(!feedUserPhoto){
$("#feeduserImg").attr("src","/img/not.png")
$("#preImg").attr("src","/img/not.png")
}
}
})
- 프로필 사진 클릭시 프로필 사진 확대
- feed click 시 피드 상세페이지 이동
- '+ button' click 시 feedwrite modal창 show
* 프로필 클릭 IDX = session.IDX 가 일치할때만 버튼 생성
var loginUser_Idx= $(".feedidx").html();
var feedUser_Idx= $(".loginidx").html();
// console.log(loginUser_Idx);
// console.log(feedUser_Idx);
if(loginUser_Idx !== feedUser_Idx){
$(".feedPlusbox").css("display","none");
$(".useredit").css("display","none");
}
- 피드사진 , 피드작성자의 name, 피드작성자의 photo , feed content , 댓글
- 쿼리스트링을 이용해 feed IDX값을 받아와 api를 호출, result를 화면에 보여준다
//feed?IDX=(idx) 피드idx값을 부여해 가져오는 function getUrlParams() { var params = {}; window.location.search.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (str, key, value) { params[key] = value; }); return params; }
* 댓글 입력창에 로그인된 user의 photo를 보여준다.
* 댓글 input창을 통해 value값을 입력받아 ajax POST를 보내 댓글을 서버로 보내준다.
$("#replySubmit").on("click",function(){
var reply = $("textarea").val();
var idx = $("#sessionIdx").html();
$.ajax({
type:"POST",
url:"http://13.125.149.206/api/feedReply/"+user_Idx,
data:{
USER_IDX:idx,
CONTENT: reply
},
success:function(res){
alert("댓글등록완료")
window.location.href=window.location.href
},
})
})
* 좋아요 버튼/ 좋아요 갯수
* 피드가 현재 로그인한 유저와 같으면 피드를 수정할수 있는 아이콘 생성
//좋아요버튼 눌렀을때 ajax호출
$("#dislike").on("click", function () {
$.ajax({
type: "GET",
url: "http://13.125.149.206/api/feed?idx=" + user_Idx,
success: function (res) {
var result = res.result[0];
var feedlike = result.FEED_LIKE;
$.ajax({
type: "POST",
url: "http://13.125.149.206/api/feedLike/" + result.IDX + "/" + idx,
success: function (res) {
$(".feedLikeCount").text(feedlike + 1);
$("#dislike").css("display", "none");
$("#like").css("display", "block");
}
});
}
});
});
//싫어요버튼 눌렀을때 ajax 호출
$("#like").on("click", function () {
$.ajax({
type: "GET",
url: "http://13.125.149.206/api/feed?idx=" + user_Idx,
success: function (res) {
var result = res.result[0];
var feedlike = result.FEED_LIKE;
$.ajax({
type: "DELETE",
url: "http://13.125.149.206/api/feedLike/" + result.IDX + "/" + idx,
success: function (res) {
$(".feedLikeCount").text(feedlike - 1);
$("#like").css("display", "none");
$("#dislike").css("display", "block");
}
});
}
});
});
- 댓글 수정 및 삭제
* 내가쓴 댓글은 수정.삭제 버튼 생성
* 수정버튼을 누르면 input창 생성
- 페이지 접속시 회원정보입력을 통해 받았던 닉네임, introduce, 사진값을 미리 보여준다
- 내용을 수정을 하면 수정된 값을 EDIT button을 통해 서버로 보내준다
- 수정시 비밀번호는 필수로 값을 입력 받는다
- 카메라 버튼 클릭시, 사진 입력 가능
- 휴지통 버튼 클릭시, 이미지 삭제 가능
//프로필 사진 삭제버튼
$("#delete").on("click",function(e){
$("#proImg").attr('src','')
})
- 탈퇴 button 클릭시 - 회원의 모든 정보 삭제
//탈퇴 ajax
$("#deletebtn").on("click",function(e){
if(confirm("정말로 탈퇴하시겠습니까?")){
$.ajax({
type:"DELETE",
url:"/deleteEdit",
success:function(res){
if(res==="true"){
alert("탈퇴되셨습니다")
window.location.assign("/")
}else if( res === "false"){
alert("에러발생")
}
else{}
}
})
}
})
- 내가쓴 피드는 피드수정버튼
- 피드의 사진값, 유저의 프로필을 뿌려준다.
- textarea로 글씨 수정할수 있는 수정페이지를 바꿔준다.
- 수정버튼 / 삭제 버튼 활성화 시킨다
- ajax값을 먼저 호출한후 값을 div에 넣어 보여준뒤 그 val값을 replace한다 . val값이 자체가 존재하지 않기 때문에 바뀌지 않음
받아온 데이터 값에 \n값을 replace해
로 바꿔 문제해결
$("content).html(feedUserContent.replace(/\n\g,'</br>'))
- 사진 선택 후 삭제버튼을 누르면 이전의 사진이 PATH값으로 저장되는 문제 발생
if(!src ){
src=proimg;
}else if( $("#proImg").attr('src')=='/img/not.png'){
src='';
}else {
imgSrc=src.split("?");
src=imgSrc[0];
}
else if구문 추가 src을 변환시켜서 이미지값을 not.png로 넣고 이미지값이 있다면 호출한값의 주소를 넣어 문제 해결
- 좋아요 클릭시 api post 호출 , feedlike 값 증가 시킴
문제점 : 피드 좋아요를 클릭 후에 피드를 다시 새로고침 하면 좋아요 기능 다시 활성화 - 서버에서 데이터 저장이 안되기 때문에 나타낼수 없음
피드 수정은 본인 게시물만 수정할 수 있도록 로그인 유저와 피드 작성 유저를 비교하여 같을 경우에만 수정버튼이 보이도록 설정
if(feedUserIdx==idx){
$("#feededitbtn").css("display","inline");
}
댓글의 idx값을 불러오기 위해서 제이쿼리로 클릭했을때 그 클릭한 값의 val값을 가져옴. id값을 처음에 부여해 한가지 값만 계속 반환되었음 name으로 처리해 , 클릭한 값 데이터를 가져옴