9개월 동안 진행한 프로젝트(mago market) 회고록

진건희·2024년 1월 4일
3
post-thumbnail

GSM에는 '아이디어 페스티벌이라는 학기 초반에 팀을 짜서
1월에 완성된 프로젝트를 소개하는 이벤트가 있다.

나는 아이디어 페스티벌에서 소개하기 위해 만든 Mago market을 소개하고
아이디어 페스티벌 기간 동안 어떤 위기와 어떻게 해결했는 지 소개하겠다.

프로젝트 소개

Mago market은

사람들이 자신의 물건을 게시하고 맘에 드는 물건을
선택해 구매할 수 있는 중고거래 사이트로 계획되었다.

팀원


팀원 문제

사실 처음부터 문제가 많았다.
일단 우리 팀에는 디자이너가 없었다.
이런 문제는 넘어간다 해도 AI를 전공으로 하는 홍준이라는 팀원은
자신의 전공은 이 쪽이 아니라며 협조를 잘 해주지 않았다. 버스
그리고 manager라고 적혀있는 김예찬 씨는 개발 쪽에는 도움을 주지 않았지만
아이디어 페스티벌은 학교에서 하는 일이기 때문에
서류 작성 같은 업무를 맡아주었다.

해결

이 문제는 디자인 쪽 문제는 프론트 쪽인 내가 좀 더 신경쓰고
6명중 2명이 개발에서 빠지는 문제는 남은
4명의 개발자가 처리하기로 해결되었다.


mago market이 만들어진 이유

mago market이 만들어진 이유는 간단하였다.
프로젝트 주제를 정해야 하지만 정하는 것이 생각보다 오래걸려서

: 그럼 사람들이 자주 교류할 수 있는 종류의 웹사이트 중 하나인 중고나라 사이트는 어때?

라고 말하여 mago market을 만들기로 결정했다.


프로젝트 시작

우리의 프로젝트는 디자인은 정하지 않고 <-- 엄청 후회할 짓
바로 프론트 작업을 들어갔다.
우리의 프로젝트는 이미 어떤 페이지가 만들어 질 건지,
어떤 기능을 추가할 지, 내가 계획을 했었기에
프론트를 시작과 동시에 백엔드에게 작업을 요청했다.
이렇게 디자인 없이 프론트는 바로 시작하였고
백엔드는 프론트에 맞춰 작업을 시작했다.


프로젝트 기획

우리의 mago market은

로그인 & 회원가입 페이지
홈 페이지
프로필 페이지
포스팅 페이지
거래 페이지

로 이루어져있고
원래는

채팅 페이지
채팅 유저 선택 페이지

도 있었지만 이 페이지들이 없어진 이유는 나중에 알려주겠다.


프로젝트 초반

나는 아이디어 페스티벌이 시작하고 1주일 안에 로그인 페이지의 틀을 짰다.
일단 로그인 페이지를 만들 때 gauth를 참고하여 만들었는데 gauth와 엄청 비슷하게 만들어졌다.

이러한 로그인 페이지를 만들었지만 이때 프론트 쪽 개발을 한 지 얼마 되지 않아
페이지 이동은 되지만 이메일에 있는 @gsm.hs.kr 도메인을 강제적으로 사용하게 하는 기능은 넣지 못했었다.
그리고 백엔드 인원 모두 실력이 부족해 아이디어 페스티벌 기간동안 자기계발 기간을 주었다.

그리고 나는 로그인을 만든 뒤 홈 페이지를 만들기 시작했다.
이때는 학교 도메인을 사용할 거라서 회원가입을 만들 생각이 없었다.

나는 홈 페이지에서 사람들이 올린 물건의 이미지, 소개글 등을 올리면
그 올린 사진,텍스트 정보가 보이는 기능을 만들려 했다.

이런식으로 가운데 사진들을 스크롤해 볼 수 있는 기능을 만들고
스크롤 아래에 있는 버튼을 이용해 다른 페이지를 이동하는 기능과
마지막 로그아웃 버튼을 왼쪽 아래에 놔두었다.

그렇지만 이때는 말했다시피 개발 실력이 좋지 않기 때문에 틀만 만들고 기능을 구현하지 못했다.

이렇게 3월이 지나갔다.

4월이 되면서 프론트 팀원 정승표에게 프로필 페이지 만들기를 부탁했다.
그리고 나는 채팅 페이지를 만들기 시작했다.
백엔드 인원들은 개발은 하지 않고 이때도 아직 자기계발을 하였다.

채팅 페이지를 구현하기에는 정말 힘들었다.
채팅 페이지를 어떻게 만들었냐면 tistory를 참고하여 만들었다.

채팅 페이지를 만들면서 js를 이용해서 첫 기능 제작 이였기 때문에
정말 오래 걸렸고 복사/붙여넣기로 한게 아닌 이해하려고 최대한 노력했고
html/css, js를 공부하던 중이여서 힘들었고 css쪽을 건드려 tistory와는 다르게 바꾸어 보았다.
채팅 페이지를 만들고 나니 6월이 되었다.


프로젝트 중간발표

6월 21일 이 날은 아이디어 페스티벌 중간발표회였다.
프로젝트 중간 발표에서 발표 쪽은 문제가 없었다.
그런데 다른 팀과 격차를 느꼈다.
이때 슬슬 html/css는 적응을 해가고 js쪽을
하고 있었는데
우리 팀의 디자인이 다른 팀의 디자인에 비해 너무 별로였다.
그렇지만

디자이너가 없으니까..

라는 변명을 하고 방학이 다가왔다.


실력 부족

중간 발표를 하기 전까지에 위기/해결법을 소개하자면
첫째는 당연히 실력부족이다.
개발을 3월달에 처음해본 나로써는 3월~4월 까지는
html/css를 배우기에 급급했고 5월~6월에는
js를 배워 기능을 구현해야 했었다.
하지만 당연히 기본이 탄탄하지 않았기에 개발 당시에 많은 오류가 있었다.
그리고 오류의 대부분은 채팅 페이지에서 왔다.
(이 당시에 js코드를 처음 추가한 페이지)

이러한 문제는 어떻게 해결했을까?

해결

이러한 실력 부족 문제는 강의선례이다.
인프런 같은 강의 사이트에서 html/css 강의를 들으며 최대한 빠르게 html/css를 익혔다.(기간은 1달 정도)
그리고 tistory, velog에서 js활용법, 개발 언어 외에 모르는 것을 배웠다.
채팅 페이지도 여기서 배워왔다.

여유 부족

두번째 문제는 여유 부족이다.
이 문제는 실력 부족으로 부터 온 문제인데.
실력이 부족하니 당연히 개발 속도는 느리고,
그러다 보니 나 자신에게 채찍질 하는것이 엄청 심해지고 팀원들에게 재촉하는 그러한 행동들이
굉장히 많아졌다.

그로 인해 팀원들과 나의 건강상태, 정신상태, 실력 등 을 고려하지 않고
무리해 오히려 개발 속도가 느려지기도 하였다.

해결

이 문제는 위에서 말한 실력 부족 문제를 해결하면 같이 해결되지만
다른 방법을 말하자면 나는 습관을 길러서 해결하였다.
나는 아이디어 페스티벌을 시작한 날 부터 매일 1~2시간 동안 프론트에 관련한 강의를 듣는 습관을 새겼다.
그리고 이 습관 덕분에 나의 성장과 마인드에 굉장한 도움이 되었다.

코드 공유 문제

이 문제는 이때 당시 프로젝트 도중 아무도 github를 사용하지 않아
우리 팀의 코드들을 모아두지 못하거나
다른 쪽의 코드가 어떤 상황인지 모르는 상황이
많았다.

해결

이 방법은 내가 github를 배우는 것으로 해결하였는데
github를 처음 써보면서 github를 배웠는데 처음에는 github를 직접 써보며
기능을 알아갔고 3일 정도 지난 후에는 github에 파일, 폴더 등을 게시하고
업데이트 하는 방법 등을 익혀 공유 문제를 해결하면서도 github에 코드를
저장하고 위기/해결 방안을 적는 습관을 적는 습관까지 새겼다.

(그때 당시에 github사진이 없어서 현재의 github사진으로 대체)


방학 동안

일단 방학 동안에 많은 일이 있었다.

js 사용법 숙지
포스팅 페이지 틀 잡기
프로필 페이지 틀 잡기

이 정도가 진행이 되었다.

포스팅 페이지는 이미지 선택 버튼을 누르면
내보낼 이미지 파일을 고를 수 있게 창이 열리고
이미지를 고르면 그 이미지를 미리보기로 볼 수 있게 js로 되어있다.


프로필 페이지는 승표가 만들었는데
틀은 잡혔지만 js가 되어있지 않았다.


방학 후

방학 후에는 기존에 짜둔 틀을 수정/보안 하는 작업을 이어갔다.
8~9월 달 동안은

로그인 & 회원가입
채팅

10~12월 달 동안은

로그인 & 회원가입

포스팅
거래
프로필

등이 수정/보안 되었다.

8~9월 달 동안 수정된 로그인 & 회원가입
로그인의 디자인이 바뀌었고 @gsm.hs.kr를 도메인을 구현을 성공했지만
이러면 학교 계정을 쓰고 싶어하지 않는 사람들을 위하지 못 한것 같다는 의견이 나와 회원가입을 추가로 만들었다.

로그인

회원가입

10~12월 달 동안에는 홈, 포스팅, 프로필 페이지에 js 기능들을 추가하였고
거래 페이지가 추가 되었다.

거래페이지가 생긴 이유는 조금 뒤에 알 수 있다.

홈 페이지에서는 포스팅 페이지에서 올린 이미지와 텍스트를
보여주는 기능을 가지고 있다.

포스팅 페이지에서는 이미지를 선택하고 텍스트를 입력하고 제출하면
홈에 출력된다.
사진을 2개 이상 선택 시 아래에 슬라이드가 나와 사진을 볼 수 있다.
그리고 홈 페이지에서도 사진이 묶임으로 출력된다.

프로필 페이지는 자신의 프로필 이미지를 선택할 수 있고
자신의 자기소개를 적을 수 있으며 이 프로필 사진, 자기소개는 저장되어
언제든 프로필에 들어가도 그대로이며, 수정이 가능하다.
그리고 로그인&회원가입에서 user의 정보를 받아와 닉네임, 이름 등 을 가져와 표시한다.
그리고 프로필 이미지를 선택하지 않을 시 기본 이미지로 설정되어 있다.
그리고 상대의 닉네임을 적으면 상대의 프로필을 볼 수 있는 기능들을 추가했다.

거래 페이지는 홈에서 올려져 있는 사진들 중 아무 사진을 누르면
그 사진이 표시한 물건을 살 수 있는 거래 페이지로 이동된다
거래 페이지는 계좌 정보와 거래 금액을 적을 수 있다.

채팅 페이지는 채팅을 치고, 이미지를 올리고, 거래 시스템을 작동 시킬 수 있다.
그리고 채팅을 쳐서 스크롤이 생기면 자동으로 아래로 스크롤 되는 기능도 구현되어 있다.


백엔드 문제

이때 정말 심각한 문제가 생겼다.
3월달 부터 12월 즉 9개월 동안 백엔드가 된 것이 없다.
물론 3~12월 동안 나는 백엔드 팀원들에게
무슨무슨 기능이 필요하다. 무엇이 필요하다.
이런식으로 말했다.
하지만 자신들의 실력 부족하다.
mysql은 팠지만, 로그인 구현 했지만,
서버 호스팅이 안 된다.
클라이언트(프론트)와 연동 방법을 모르겠다.
이런식으로 되었다.

해결

이러한 문제는 남은 기간이 얼마 남지 않았고,
그렇다고 백엔드 인원들은 객관적으로 나보다 열심히 하지도, 오래 하지도
않았기 때문에 태도와 실력 쪽에서 부족하다고 생각하고 최대한
백엔드에서 구현해야 하는 기능들을 프론트로 구현을 했다.

로그인&회원가입

로그인과 회원가입을 하여 user데이터를 서버로 옮기는 것이 아닌
localStorage에 저장하여 다른 페이지에서 user데이터를 가져오고
user데이터를 저장되게 하였다.

    const usernickname = nicknameInput.value; - nickname 변수 선언
    const password = passwordInput.value; - password 변수 선언

    for (let i = 1; i <= userCount; i++) {
        const savedUsernickname = localStorage.getItem(`${USERNICKNAME_KEY}_${i}`);
        const savedPassword = localStorage.getItem(`${PASSWORD_KEY}_${i}`);

        if (usernickname === savedUsernickname && password === savedPassword) {
            localStorage.setItem("currentLoggedInUser", i);

            setTimeout(function () {
                window.location.href = "extra-home.html";
            }, 0);
            return;
        }
    }

    alert("로그인 실패: 사용자 이름 또는 비밀번호가 일치하지 않습니다.");
} - 몇 번째 가입/로그인 한 유저인지 저장하고 로그인 시 데이터가 일치하면 홈으로 가짐

const savedUsernickname = localStorage.getItem(`${USERNICKNAME_KEY}_${userCount}`);
const savedPassword = localStorage.getItem(`${PASSWORD_KEY}_${userCount}`);

if(savedUsernickname === null && savedPassword === null){
    window.location.href="membergaib.html";
} - 회원가입하지 않아서 저장된 유저 데이터가 없다면 강제로 로그인 화면으로 가지는 코드
else{
    loginForm.addEventListener("submit", Check);
}

포스팅

포스팅 페이지에서는
사진과 텍스트를 제출하면
사진은 로컬 스토리지에
key값 = postimage+usernickname,
value = Array(배열)에 넣은 data/image(이미지 파일 기본 링크 URL변환)
하여 localStorage에 저장하였다.
사진들의 크기가 너무 크다면 localStorage에 저장될 때 사진이 잘리고
잘린 사진이 저장되는 경우가 있어
사이즈를 조절하는 기능도 추가하였다.

- 사진을 localStorage에 저장하는 코드 - 
const POSTIMAGE = "postimage";
const TEXTDATA = "posttext";

const savedPostNumber = "postnumber";
const textInput = document.querySelector("#text");
const submitButton = document.querySelector("#submit");

let postnumber = parseInt(localStorage.getItem(savedPostNumber)) || 0;

function submitPost() {
  const textData = textInput.value;
  localStorage.setItem(TEXTDATA, textData);

  const imageContainer = document.querySelector('div#image_container');
  const images = imageContainer.querySelectorAll('img');
  const imageArray = [];

  images.forEach((image, index) => {
    const imagePath = image.src;

    imageArray.push(imagePath);
  });

  postnumber += 1;

  localStorage.setItem(`${POSTIMAGE}_${postnumber}`, JSON.stringify(imageArray));
  localStorage.setItem(`${TEXTDATA}_${postnumber}`, textData);
  localStorage.setItem(savedPostNumber, postnumber);

  window.location.href = "extra-home.html";
}
-이미지 파일 크기 재설정 코드 -
const resizedImagePath = await resizeImage(imagePath, 300, 300);

      newDiv.innerHTML = '<img src="' + resizedImagePath + '" style="object-fit: contain;">';
      sliderContainer.appendChild(newDiv);

홈페이지에서는 포스팅에서 저장된 사진과 텍스트를 출력하고
localStorage에 저장된 사진/텍스트를 출력하고
사진을 누르면 거래 페이지로 넘어가는 기능을 가진다.

  const POSTIMAGE = "postimage";
  const TEXTDATA = "posttext";
  const savedPostNumber = "postnumber";
  const pullPostNumber = localStorage.getItem(savedPostNumber);

  const allPostedData = [];
  for (let i = pullPostNumber; i >= 0; i--) {
    const images = JSON.parse(localStorage.getItem(`${POSTIMAGE}_${i}`));
    const text = localStorage.getItem(`${TEXTDATA}_${i}`);
    if (images && images.length > 0) {
      allPostedData.push({ images, text });
    }
  }

  // 이미지와 텍스트를 화면에 표시
if (allPostedData.length > 0) {
  const imageContainer = document.getElementById('scroll');
  allPostedData.forEach((postData, index) => {
    // 이미지 표시
    const images = postData.images;
    images.forEach((imageDataUrl, imgIndex) => {
      const imageElement = document.createElement('img');
      imageElement.src = imageDataUrl;
      imageElement.alt = `Posted Image ${index + 1} - ${imgIndex + 1}`;

      // 클래스 추가
      imageElement.classList.add('jiji');

      // 이미지 클릭 이벤트 추가
      imageElement.addEventListener('click', function () {
        handleImageClick(imageDataUrl);
      });

      // 이미지를 감싸는 div 생성
      const imageWrapper = document.createElement('div');
      imageWrapper.appendChild(imageElement);
      imageContainer.appendChild(imageWrapper);
    });

    // 텍스트 표시
    const textElement = document.createElement('p');
    textElement.innerText = postData.text || "작성한 문장이 없습니다.";
    imageContainer.appendChild(textElement);

    // 각 이미지와 텍스트를 구분하는 라인 추가 (선택적)
    const lineElement = document.createElement('hr');
    imageContainer.appendChild(lineElement);
  });
}

  // 이미지 클릭 시 처리하는 함수
  function handleImageClick(selectedImageUrl) {
    // 선택한 이미지의 URL을 저장
    localStorage.setItem('selectedImageUrl', selectedImageUrl);

    // 거래 페이지로 전환
    window.location.href = 'extra-Trance.html'; // 거래 페이지의 HTML 파일 경로로 수정
  }

채팅

채팅이란 것은 백엔드 없이는 구현되지 않는 것이기 때문에
없어졌다.
그런데 원래는 채팅페이지에 거래 기능이 있었는데
채팅이 사라지면서 거래 기능을 대체할 것이 필요했다.

거래페이지

그래서 거래페이지가 만들어졌다.

document.addEventListener('DOMContentLoaded', function () {
    // 이미지 보여주는 함수 호출
    showSelectedImage();

    // 거래 폼 제출 이벤트 처리
    const transactionForm = document.getElementById('transactionForm');
    transactionForm.addEventListener('submit', function (event) {
        event.preventDefault();

        // 여기에 거래 정보를 서버로 전송하는 로직 추가
        const transactionAmount = document.getElementById('transactionAmount').value;
        const bankInfo = document.getElementById('bankInfo').value;

        // 거래를 시작하는 함수 호출
        startTransaction(transactionAmount, bankInfo);

        // 거래 정보를 이미지를 올린 사용자에게 전송
        sendTransactionInfoToUploader(transactionAmount, bankInfo);
    });

    // 거래 신청 알림을 표시하는 함수
    function showTransactionRequestNotification(user) {
        // 모달 창을 생성하고 표시
        const modal = document.createElement('div');
        modal.innerHTML = `
            <div style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border: 1px solid #ccc; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);">
                <p style="font-size: 16px; margin-bottom: 10px;">거래 신청이 도착했습니다.</p>
                <button onclick="acceptTransaction()">수락</button>
                <button onclick="rejectTransaction()">거절</button>
            </div>
        `;
        document.body.appendChild(modal);
    }

    // 거래 수락 함수
    function acceptTransaction(user) {
        // 여기에서 거래를 수락하는 로직을 추가
        // 예: 수락 여부를 저장하고, 다음 화면에 반영
        localStorage.setItem('transactionAccepted', 'true');
        closeModal();
    }

    // 거래 거절 함수
    function rejectTransaction(user) {
        // 여기에서 거래를 거절하는 로직을 추가
        // 예: 거절 여부를 저장하고, 다음 화면에 반영
        localStorage.setItem('transactionAccepted', 'false');
        closeModal();
    }

    // 주기적으로 거래 메시지를 확인하는 함수
    function checkTransactionStatus() {
        const transactionAccepted = localStorage.getItem('transactionAccepted');
        if (transactionAccepted === 'true') {
            // 거래를 수락한 경우에 대한 처리
            alert('거래를 수락하셨습니다.');
            // 추가로 필요한 작업 수행
        } else if (transactionAccepted === 'false') {
            // 거래를 거절한 경우에 대한 처리
            alert('거래를 거절하셨습니다.');
            // 추가로 필요한 작업 수행
        }
        // 거래 여부를 초기화
        localStorage.removeItem('transactionAccepted');
    }

    // 주기적으로 거래 메시지 확인 (예: 5초마다)
    setInterval(checkTransactionStatus, 5000);

    // 모달 닫기 함수
    function closeModal() {
        const modal = document.querySelector('div');
        if (modal) {
            modal.remove();
        }
    }

    // 이미지 보여주는 함수
    function showSelectedImage() {
        // 선택한 이미지의 URL을 로컬 스토리지에서 가져옴
        const selectedImageUrl = localStorage.getItem('selectedImageUrl');

        // 이미지 엘리먼트 생성
        const imageElement = document.createElement('img');
        imageElement.src = selectedImageUrl;
        imageElement.alt = 'Selected Image';

        // 이미지를 표시하는 엘리먼트에 추가
        const selectedImageContainer = document.getElementById('selectedImageContainer');
        selectedImageContainer.appendChild(imageElement);
    }

    // 거래를 시작하는 함수
    function startTransaction(transactionAmount, bankInfo) {
        // 여기에 거래를 시작하는 서버로의 요청을 보내는 로직 추가
        // 서버로 transactionAmount와 bankInfo를 전송하여 거래를 시작하는 동작 수행
        console.log(`거래를 시작합니다. 금액: ${transactionAmount}, 계좌 정보: ${bankInfo}`);
    }

    // 거래 정보를 이미지를 올린 사용자에게 전송하는 함수
    function sendTransactionInfoToUploader(transactionAmount, bankInfo) {
        // 이미지를 올린 사용자의 정보를 가져오기
        const uploaderInfo = localStorage.getItem('uploaderInfo');

        // 여기에서 로컬 스토리지를 활용하여 거래 정보를 전송하는 로직 추가
        // 예: 이미지를 올린 사용자에게 거래 정보 메시지를 전송
        showTransactionRequestNotification(uploaderInfo);
    }
});

일단 이때까지만 해도 백엔드가 될 거라는 생각을 가지고 있어서
백엔드 서버에 올리는 기능도 가지고 있다.

이렇게 결국 했지만 마지막 발표 날에도 백엔드는 개발 되지 않았다.
그렇게 only front 100% mago market이 만들어졌다.

디자인 문제

디자인이 안되어 있는 것도 문제였다.
홈 화면에 사진이 없을 때는 흰색배경과 버튼들만 있어
비어보인다는 의견이 많았따.

해결

6~7월 부터 figma사용법을 배웠기 때문에

이런식으로 살짝 꾸몄다.


느낀 점 & 문제점

문제점

  1. localStorage을 이용해 백엔드를 흉내냈기 때문에
    보안에 취약하다.
  2. 백엔드 서버에 정보를 넘길 수 없기 때문에(백엔드 된 게 없음)
    거래를 할 수 없다.
  3. 디자인이 구리다

느낀 점

만약 내가 디자인을 먼저 하고 프론트를 했다면
내가 3~4월에 figma를 할 수 있었다면
백엔드가 잘 되었다면
팀원들 간에 협력이 잘 되었다면
좋은 결과를 내었을 테고
이런한 문제를 고칠 방법나의 실력이라고 느꼈다.
나도 axios같은 통신 쪽, git을 잘 하지 못하고
js도 엄청 잘하는 것도 아니기 때문에 나도 많이 부족했고
앞으로 무엇을 어떻게 공부할 지 고민하게 되는 경험이였다.


마고마켓-팀 github
나의 개인 github(마고마켓)

profile
프론트엔드 공부 중인 GSM 학생입니다.

2개의 댓글

comment-user-thumbnail
2024년 2월 3일

협업, 개발자들에게는 협업이 젤 중요하다고 저는 생각하고 있습니다. 어떤 프로젝트가 있든, 팀이 터질 뻔 하기도 하고, 싸우기도 하고, 하지만 그런 과정들을 이겨내고, 에러를 해결하고 완성시키는게 정말 멋지고 대단하다고 생각합니다. 어느 부분에서나 문제가 발생할 수 있고, 다음엔 안그러겠지 하겠지만 또 그런일은 일어나죠 ㅎㅎ 과거로 돌아가 나 또는 팀원을 원망하는 것 보다는 하나를 배웠다라고 생각하는건 어떨까요?

오랜 시간동안 개발한다고 수고하셨고, 좋은 글 잘 읽고 갑니당~!

1개의 답글