diff --git a/backend/.env.example b/backend/.env.example index 2d2f04eb..f8021068 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -122,10 +122,14 @@ AUTH_PROD_TOKEN_AUDIENCE= ####################################################################################################################### # redis REDIS_DEV_CONTAINER_NAME=redis_dev -REDIS_DEV_PORT=6379 +REDIS_DEV_PORT=6380 REDIS_DEV_HOST=127.0.0.1 -REDIS_PROD_CONTAINER_NAME=redis_dev +REDIS_TEST_CONTAINER_NAME=redis_test +REDIS_TEST_PORT=6381 +REDIS_TEST_HOST=127.0.0.1 + +REDIS_PROD_CONTAINER_NAME=redis REDIS_PROD_PORT=6379 -REDIS_PROD_HOST=127.0.0.1 +REDIS_PROD_HOST=redis ####################################################################################################################### \ No newline at end of file diff --git a/backend/DB/dummy/RandomGuestName/Adjective.js b/backend/DB/dummy/RandomGuestName/Adjective.js new file mode 100644 index 00000000..209a57ae --- /dev/null +++ b/backend/DB/dummy/RandomGuestName/Adjective.js @@ -0,0 +1,66 @@ +const Adjectives = [ + "춤추는", + "멋있는", + "차가운", + "뜨거운", + "호기심이 많은", + "귀여운", + "울부짖는", + "궁금한게 많은", + "노래하는", + "랩하는", + "화사한", + "맵시있는", + "요리하는", + "재미있는", + "큰 눈의", + "착한", + "정직한", + "조용한", + "즐거운", + "졸고있는", + "기쁜", + "벅찬", + "포근한", + "흐뭇한", + "상쾌한", + "시원한", + "반가운", + "후련한", + "살맛 나는", + "신바람 나는", + "아늑한", + "흥분되는", + "온화한", + "느긋한", + "끝내주는", + "괜찮은", + "쌈박한", + "정다운", + "그리운", + "자유로운", + "따사로운", + "감미로운", + "황홀한", + "상큼한", + "개념있는", + "지성적인", + "자상한", + "아리따운", + "여유있는", + "감정이 풍부한", + "활기찬", + "힘찬", + "생생한", + "의기 양양한", + "든든한", + "격렬한", + "당당한", + "팔팔한", + "엄청난", + "자신만만한", + "패기만만한", + "충만한", +]; + +export default Adjectives; diff --git a/backend/DB/dummy/RandomGuestName/Animals.js b/backend/DB/dummy/RandomGuestName/Animals.js new file mode 100644 index 00000000..5067fea8 --- /dev/null +++ b/backend/DB/dummy/RandomGuestName/Animals.js @@ -0,0 +1,3 @@ +const Animals = ["크롱", "호눅스", "부캠퍼", "타노스", "배트맨", "아이언맨", "헐크", "토르", "갈까마귀", "돌고래", "해오라기", "갈매기", "갈색여우원숭이", "개구리", "개미핥기", "개코원숭이", "거북", "거위", "고래", "고래상어", "고릴라", "고슴도치", "고양이", "곰", "공룡", "공작", "괭이갈매기", "구관조", "금붕어", "기러기", "기린", "까마귀", "까치", "꿩", "나무늘보", "나비", "낙타", "너구리", "노란눈썹펭귄", "늑대", "다람쥐", "달마시안", "달팽이", "담비", "딱다구리", "맘모스", "무당개구리", "무당벌레", "무스", "물개", "물총새", "바다코끼리", "바다표범", "방울뱀", "방울새", "백로", "북극여우", "비단뱀", "비둘기", "비버", "뻐꾸기", "사막여우", "사슴", "사자", "상어", "소", "소쩍새", "송골매", "수리부엉이", "시조새", "악어", "앵무새", "오리", "올빼미", "원숭이", "장수하늘소", "제비", "족제비", "진돗개", "참새", "청개구리", "초록비단뱀", "치타", "캥거루", "코끼리", "코뿔소", "코알라", "타조", "토끼", "파랑새", "펭귄", "표범", "퓨마", "하마", "흑표범"]; + +export default Animals; diff --git a/backend/DB/dummy/RandomGuestName/index.js b/backend/DB/dummy/RandomGuestName/index.js new file mode 100644 index 00000000..43fcfe5c --- /dev/null +++ b/backend/DB/dummy/RandomGuestName/index.js @@ -0,0 +1,12 @@ +import faker from "faker"; +import Adjectives from "./Adjective.js"; +import Animals from "./Animals.js"; + +function getRandomGuestName() { + const adjective = faker.random.arrayElement(Adjectives); + const animal = faker.random.arrayElement(Animals); + + return `${adjective} ${animal}`; +} + +export default getRandomGuestName; diff --git a/backend/DB/logger.js b/backend/DB/logger.js new file mode 100644 index 00000000..5a058011 --- /dev/null +++ b/backend/DB/logger.js @@ -0,0 +1,5 @@ +import getLogger from "../libs/logger.js"; + +const logger = getLogger("mySQL_Query"); + +export default logger; diff --git a/backend/DB/queries/candidate.js b/backend/DB/queries/candidate.js index dbec87f5..b278906c 100644 --- a/backend/DB/queries/candidate.js +++ b/backend/DB/queries/candidate.js @@ -1,17 +1,14 @@ +import Sequelize from "sequelize"; import models from "../models"; -const Sequelize = require("sequelize"); - const Op = Sequelize.Op; export async function getCandidatesByPollId(pollIdList) { - const result = await models.Candidate.findAll({ + return models.Candidate.findAll({ where: { PollId: { [Op.or]: pollIdList, }, }, }); - - return result; } diff --git a/backend/DB/queries/event.js b/backend/DB/queries/event.js index cba8bda9..f71f29c4 100644 --- a/backend/DB/queries/event.js +++ b/backend/DB/queries/event.js @@ -16,141 +16,57 @@ export async function createEvent({ return models.Event.findOrCreate({ where: {eventCode}, defaults: { + eventName, + HostId, moderationOption, replyOption, startAt, endAt, - HostId, - eventName, }, }); } -export async function updateEventById( +export async function updateEventById({ id, - {eventName, moderationOption, replyOption, startAt, endAt} -) { + eventName, + moderationOption, + replyOption, + startAt, + endAt, +}) { return models.Event.update( {eventName, moderationOption, replyOption, startAt, endAt}, - {where: {id}} + {where: {id}}, ); } export async function getEventsByHostId(hostId) { - const events = await models.Event.findAll({ + return models.Event.findAll({ where: {HostId: hostId}, }); - - return events; } -export async function getEventIdByEventCode(eventCode) { - const event = await models.Event.findOne({ +export async function getEventByEventCode(eventCode) { + return models.Event.findOne({ where: { eventCode, }, - attributes: ["id"], }); - - return event; } export async function getEventById(EventId) { - const event = await models.Event.findOne({ + return models.Event.findOne({ where: { id: EventId, }, }); - - return event; } export async function getEventOptionByEventId(id) { - const event = await models.Event.findOne({ + return models.Event.findOne({ where: { id, }, attributes: ["moderationOption", "replyOption"], }); - - return event; -} - -export async function getQuestionLikeCount(EventId = 2, limit, offset) { - return models.Question.findAll({ - attributes: ["id", [models.sequelize.fn("count", "*"), "likeCount"]], - where: {EventId, QuestionId: null}, - include: [ - { - model: models.Like, - attributes: [], - }, - ], - group: "id", - offset, - limit, - }); -} - -export async function getQuestionsByEventCodeAndGuestId( - eventCode, - guestId, - limit = 70, - offset -) { - // const event = await models.Event.findOne({where: {eventCode}}); - // const EventId = event.dataValues.id - const EventId = 2; - - return models.Question.findAll({ - where: {EventId, QuestionId: null}, - include: [ - { - model: models.Like, - }, - { - model: models.Emoji, - }, - { - model: models.Guest, - }, - ], - offset, - limit, - }); -} - -export async function raw_getQuestionsByEventCodeAndGuestId( - eventCode, - guestId, - limit = 100, - offset = 0 -) { - // const event = await models.Event.findOne({where: {eventCode}}); - // const EventId = event.dataValues.id - const EventId = 2; - - const query = ` - select *, Emojis.name, Emojis.GuestId - from Questions - inner join Emojis on Questions.id = Emojis.QuestionId - where EventId = :EventId and Questions.QuestionId is null --- order by Emojis.QuestionId DESC - --- limit :limit offset :offset - --- group by Emojis.QuestionId -`; - // console.log(event.dataValues.id) - const [questions] = await models.sequelize.query(query, { - replacements: { - EventId, - limit, - offset, - type: Sequelize.QueryTypes.SELECT, - raw: true, - }, - }); - - return questions; } diff --git a/backend/DB/queries/guest.js b/backend/DB/queries/guest.js index b66d5964..2034253e 100644 --- a/backend/DB/queries/guest.js +++ b/backend/DB/queries/guest.js @@ -1,26 +1,26 @@ import uuidv1 from "uuid/v1"; import models from "../models"; +import getRandomGuestName from "../dummy/RandomGuestName"; const Guest = models.Guest; -async function findGuestBySid(guestSid) { - const guest = await Guest.findOne({where: {guestSid}}); - const result = guest ? guest.dataValues : false; +async function getGuestByGuestSid(guestSid) { + return Guest.findOne({where: {guestSid}}); +} - return result; +async function isExistGuest(guestSid) { + return !!(await getGuestByGuestSid(guestSid)); } -async function createGuest(name, eventId) { +async function createGuest(eventId) { const guest = await Guest.create({ - name, + name: getRandomGuestName(), EventId: eventId, guestSid: uuidv1(), isAnonymous: 1, }); - const result = guest ? guest.dataValues : false; - - return result; + return guest.get({plain: true}); } async function getGuestById(id) { @@ -30,10 +30,7 @@ async function getGuestById(id) { } async function updateGuestById({id, name, isAnonymous, company, email}) { - return Guest.update( - {name, company, isAnonymous, email}, - {where: {id}}, - ); + return Guest.update({name, company, isAnonymous, email}, {where: {id}}); } async function getGuestByEventId(EventId) { @@ -45,5 +42,6 @@ export { getGuestById, updateGuestById, getGuestByEventId, - findGuestBySid, + isExistGuest, + getGuestByGuestSid }; diff --git a/backend/DB/queries/hashtag.js b/backend/DB/queries/hashtag.js index f53a44f6..44e3dd35 100644 --- a/backend/DB/queries/hashtag.js +++ b/backend/DB/queries/hashtag.js @@ -5,7 +5,7 @@ const Hashtag = models.Hashtag; export async function createHashtag({name, EventId}) { return Hashtag.create( {name, EventId}, - {default: {updateAt: new Date(), createAt: new Date()}} + {default: {updateAt: new Date(), createAt: new Date()}}, ); } diff --git a/backend/DB/queries/host.js b/backend/DB/queries/host.js index f304ddb3..b82c8cb4 100644 --- a/backend/DB/queries/host.js +++ b/backend/DB/queries/host.js @@ -1,23 +1,20 @@ import models from "../models"; -async function findHostByAuthId(oAuthid) { - const host = await models.Host.findOne({where: {oauthId: oAuthid}}); - const result = host ? host.dataValues : false; +// todo: 더 좋은 이름 +export async function findHostByAuthId(oauthId) { + const host = await models.Host.findOne({where: {oauthId}}); - return result; + return host ? host.dataValues : false; } -async function createHost(oAuthid, name, image, email) { +export async function createHost(oauthId, name, image, email) { const host = await models.Host.create({ - oauthId: oAuthid, + oauthId, name, email, image, - emailFeedBack: 0, + emailFeedBack: false, }); - const result = host ? host.dataValues : false; - return result; + return host ? host.dataValues : false; } - -export {createHost, findHostByAuthId}; diff --git a/backend/DB/queries/poll.js b/backend/DB/queries/poll.js index 69333965..2badd882 100644 --- a/backend/DB/queries/poll.js +++ b/backend/DB/queries/poll.js @@ -1,46 +1,40 @@ import models from "../models"; +import logger from "../logger.js"; const sequelize = models.sequelize; const Poll = models.Poll; const Candidate = models.Candidate; -export async function openPoll(pollId) { - const now = new Date(); - const result = await models.Poll.update( +export async function openPoll(id) { + // result should be == [1], 1개의 row가 성공했다는 의미 + return Poll.update( { state: "running", - pollDate: now, + pollDate: new Date(), }, { - where: {id: pollId}, - } + where: {id}, + }, ); - - // result should be == [1], 1개의 row가 성공했다는 의미 - return result; } -export async function closePoll(pollId) { - const result = await models.Poll.update( +export async function closePoll(id) { + // result should be == [1], 1개의 row가 성공했다는 의미 + return Poll.update( { state: "closed", }, { - where: {id: pollId}, - } + where: {id}, + }, ); - - // result should be == [1], 1개의 row가 성공했다는 의미 - return result; } -export async function getPollsByEventId(eventId) { - const result = await models.Poll.findAll({ - where: {EventId: eventId}, +export async function getPollsByEventId(EventId) { + return Poll.findAll({ + where: {EventId}, order: [["id", "DESC"]], }); - - return result; } const makeCandidateRows = (id, pollType, candidates) => { @@ -55,6 +49,7 @@ const makeCandidateRows = (id, pollType, candidates) => { }); i++; } + return nItems; }; @@ -64,13 +59,15 @@ export async function createPoll( pollType, selectionType, allowDuplication, - state, - candidates + candidates, ) { let transaction; let poll; let nItems; + const state = "standby"; + const pollDate = new Date(); + try { // get transaction transaction = await sequelize.transaction(); @@ -84,8 +81,9 @@ export async function createPoll( selectionType, allowDuplication, state, + pollDate, }, - {transaction} + {transaction}, ); // step 2 @@ -98,7 +96,7 @@ export async function createPoll( } catch (err) { // Rollback transaction only if the transaction object is defined if (transaction) await transaction.rollback(); - console.log("Transaction rollback", err); + logger.error("Transaction rollback", err); } if (poll && nItems) { diff --git a/backend/DB/queries/question.js b/backend/DB/queries/question.js index 0cce9953..cf0e1685 100644 --- a/backend/DB/queries/question.js +++ b/backend/DB/queries/question.js @@ -1,5 +1,6 @@ import Sequelize from "sequelize"; import models from "../models"; +import logger from "../logger.js"; const sequelize = models.sequelize; const Op = Sequelize.Op; @@ -22,7 +23,7 @@ export async function createQuestion( } export async function getQuestionsByEventId(EventId) { - return models.Question.findAll({ + return Question.findAll({ where: {EventId}, }); } @@ -59,19 +60,26 @@ export async function updateIsStared(from, to) { try { if (from) { - await Question.update({isStared: from.isStared}, + await Question.update( + {isStared: from.isStared}, {where: {id: from.id}}, {transaction}, ); } - await Question.update({isStared: to.isStared}, + + await Question.update( + {isStared: to.isStared}, {where: {id: to.id}}, {transaction}, ); + await transaction.commit(); } catch (err) { - if (transaction) await transaction.rollback(); - console.log("Transaction rollback", err); + if (transaction) { + await transaction.rollback(); + } + + logger.error("Transaction rollback", err); } } diff --git a/backend/DB/queries/vote.js b/backend/DB/queries/vote.js index ebdff291..d7417aec 100644 --- a/backend/DB/queries/vote.js +++ b/backend/DB/queries/vote.js @@ -1,22 +1,21 @@ -import models from "../models"; import Sequelize from "sequelize"; +import models from "../models"; +import logger from "../logger.js"; +const sequelize = models.sequelize; +const Vote = models.Vote; const Op = Sequelize.Op; export async function addVote({GuestId, CandidateId}) { - const vote = models.Vote.create({GuestId, CandidateId}); - - return vote; + return Vote.create({GuestId, CandidateId}); } export async function deleteVoteBy({GuestId, CandidateId}) { - return models.Vote.destroy({where: {GuestId, CandidateId}}); + return Vote.destroy({where: {GuestId, CandidateId}}); } -export async function addAndDelete(gId, candidateToAdd, candidateToDelete) { - const sequelize = models.sequelize; - const Vote = models.Vote; - const GuestId = gId; +export async function addAndDelete(guestId, candidateToAdd, candidateToDelete) { + const GuestId = guestId; let CandidateId = candidateToAdd; let transaction; let rows; @@ -24,13 +23,14 @@ export async function addAndDelete(gId, candidateToAdd, candidateToDelete) { try { // get transaction transaction = await sequelize.transaction(); + // step 1 await Vote.create( { GuestId, CandidateId, }, - {transaction} + {transaction}, ); // step 2 @@ -40,33 +40,28 @@ export async function addAndDelete(gId, candidateToAdd, candidateToDelete) { GuestId, CandidateId, }, + transaction, }); // commit await transaction.commit(); } catch (err) { // Rollback transaction only if the transaction object is defined - if (transaction) await transaction.rollback(); - console.log("Transaction rollback", err); + if (transaction) { + await transaction.rollback(); + } + + logger.error("Transaction rollback", err); } return rows; } -export async function deleteVoteById({}) {} - -export async function getVotesByCandidate() {} - -export async function getVotesByGuestId() {} - -export async function getFromGuest() {} - export async function getCandidatesByGuestId(candidateList, guestId) { - const result = await models.Vote.findAll({ + return Vote.findAll({ where: { [Op.and]: [ - {GuestId: guestId}, - { + {GuestId: guestId}, { CandidateId: { [Op.or]: candidateList, }, @@ -75,12 +70,10 @@ export async function getCandidatesByGuestId(candidateList, guestId) { }, attributes: ["CandidateId"], }); - - return result; } export async function getVotersByCandidateList(candidateList) { - const count = await models.Vote.count({ + return Vote.count({ where: { CandidateId: { [Op.or]: candidateList, @@ -89,6 +82,4 @@ export async function getVotersByCandidateList(candidateList) { distinct: true, col: "GuestId", }); - - return count; } diff --git a/backend/docker/docker-development.yml b/backend/docker/docker-development.yml index 1b5369b5..42939da0 100644 --- a/backend/docker/docker-development.yml +++ b/backend/docker/docker-development.yml @@ -1,31 +1,31 @@ version: "2.0" services: - mysql: - container_name: ${MYSQL_DEV_CONTAINER_NAME} - image: mysql - ports: - - ${MYSQL_DEV_PORT}:3306 - volumes: - - ${DOCKER_VOLUMNS_PATH}/${MYSQL_DEV_CONTAINER_NAME}:/var/lib/mysql - command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8 --collation-server=utf8_general_ci --log-bin-trust-function-creators=1 - environment: - - MYSQL_ROOT_PASSWORD=${MYSQL_DEV_ROOT_PASSWORD} - - MYSQL_DATABASE=${MYSQL_DEV_SCHEME} - - MYSQL_USER=${MYSQL_DEV_USER} - - MYSQL_PASSWORD=${MYSQL_DEV_PASSWORD} - networks: - - backend - redis: - container_name: ${REDIS_DEV_CONTAINER_NAME} - image: redis - ports: - - ${REDIS_DEV_PORT}:6379 - volumes: - - ${DOCKER_VOLUMNS_PATH}/${REDIS_DEV_CONTAINER_NAME}:/data - networks: - - backend + mysql: + container_name: ${MYSQL_DEV_CONTAINER_NAME} + image: mysql + ports: + - ${MYSQL_DEV_PORT}:3306 + volumes: + - ${DOCKER_VOLUMNS_PATH}/${MYSQL_DEV_CONTAINER_NAME}:/var/lib/mysql + command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8 --collation-server=utf8_general_ci --log-bin-trust-function-creators=1 + environment: + - MYSQL_ROOT_PASSWORD=${MYSQL_DEV_ROOT_PASSWORD} + - MYSQL_DATABASE=${MYSQL_DEV_SCHEME} + - MYSQL_USER=${MYSQL_DEV_USER} + - MYSQL_PASSWORD=${MYSQL_DEV_PASSWORD} + networks: + - backend + redis: + container_name: ${REDIS_DEV_CONTAINER_NAME} + image: redis + ports: + - ${REDIS_DEV_PORT}:6379 + volumes: + - ${DOCKER_VOLUMNS_PATH}/${REDIS_DEV_CONTAINER_NAME}:/data + networks: + - backend networks: - backend: - driver: "bridge" + backend: + driver: "bridge" diff --git a/backend/docker/docker-product.yml b/backend/docker/docker-product.yml index 73d7eb69..2fe43f96 100644 --- a/backend/docker/docker-product.yml +++ b/backend/docker/docker-product.yml @@ -1,89 +1,89 @@ version: "2.0" services: - express: - container_name: ${EXPRESS_PROD_CONTAINER_NAME} - image: ${NODE_IMAGE_NAME} - command: yarn pm2-runtime start ./build/express/app.js --watch - environment: - - NODE_ENV=production - # container 내부에서 DB container 를 접근하기위해 필요한 환경변수 - - IS_DOCKER_CONTAINER=true - volumes: - - ../build:/usr/src/app/build - tty: true - ports: - - 80:${EXPRESS_PROD_PORT} - networks: - - backend - links: - - mysql - - redis + express: + container_name: ${EXPRESS_PROD_CONTAINER_NAME} + image: ${NODE_IMAGE_NAME} + command: yarn pm2-runtime start ./build/express/app.js --watch + environment: + - NODE_ENV=production + # container 내부에서 DB container 를 접근하기위해 필요한 환경변수 + - IS_DOCKER_CONTAINER=true + volumes: + - ../build:/usr/src/app/build + tty: true + ports: + - 80:${EXPRESS_PROD_PORT} + networks: + - backend + links: + - mysql + - redis - socket_io: - container_name: ${SOCKET_IO_SERVER_PROD_CONTAINER_NAME} - image: ${NODE_IMAGE_NAME} - command: yarn pm2-runtime start ./build/socket_io_server/app.js --watch - environment: - - NODE_ENV=production - # container 내부에서 DB container 를 접근하기위해 필요한 환경변수 - - IS_DOCKER_CONTAINER=true - volumes: - - ../build:/usr/src/app/build - tty: true - ports: - - ${SOCKET_IO_SERVER_PROD_PORT}:${SOCKET_IO_SERVER_PROD_PORT} - networks: - - backend - links: - - mysql - - redis + socket_io: + container_name: ${SOCKET_IO_SERVER_PROD_CONTAINER_NAME} + image: ${NODE_IMAGE_NAME} + command: yarn pm2-runtime start ./build/socket_io_server/app.js --watch + environment: + - NODE_ENV=production + # container 내부에서 DB container 를 접근하기위해 필요한 환경변수 + - IS_DOCKER_CONTAINER=true + volumes: + - ../build:/usr/src/app/build + tty: true + ports: + - ${SOCKET_IO_SERVER_PROD_PORT}:${SOCKET_IO_SERVER_PROD_PORT} + networks: + - backend + links: + - mysql + - redis - graphQL_yoga: - container_name: ${GRAPHQL_YOGA_SERVER_PROD_CONTAINER_NAME} - image: ${NODE_IMAGE_NAME} - command: yarn pm2-runtime start ./build/graphQL/app.js --watch - environment: - - NODE_ENV=production - # container 내부에서 DB container 를 접근하기위해 필요한 환경변수 - - IS_DOCKER_CONTAINER=true - volumes: - - ../build:/usr/src/app/build - tty: true - ports: - - ${GRAPHQL_YOGA_SERVER_PROD_PORT}:${GRAPHQL_YOGA_SERVER_PROD_PORT} - networks: - - backend - links: - - mysql - - redis + graphQL_yoga: + container_name: ${GRAPHQL_YOGA_SERVER_PROD_CONTAINER_NAME} + image: ${NODE_IMAGE_NAME} + command: yarn pm2-runtime start ./build/graphQL/app.js --watch + environment: + - NODE_ENV=production + # container 내부에서 DB container 를 접근하기위해 필요한 환경변수 + - IS_DOCKER_CONTAINER=true + volumes: + - ../build:/usr/src/app/build + tty: true + ports: + - ${GRAPHQL_YOGA_SERVER_PROD_PORT}:${GRAPHQL_YOGA_SERVER_PROD_PORT} + networks: + - backend + links: + - mysql + - redis - mysql: - container_name: ${MYSQL_PROD_CONTAINER_NAME} - image: mysql - ports: - - ${MYSQL_PROD_PORT}:3306 - volumes: - - ${DOCKER_VOLUMNS_PATH}/${MYSQL_PROD_CONTAINER_NAME}:/var/lib/mysql - command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8 --collation-server=utf8_general_ci --log-bin-trust-function-creators=1 - environment: - - MYSQL_ROOT_PASSWORD=${MYSQL_PROD_ROOT_PASSWORD} - - MYSQL_DATABASE=${MYSQL_PROD_SCHEME} - - MYSQL_USER=${MYSQL_PROD_USER} - - MYSQL_PASSWORD=${MYSQL_PROD_PASSWORD} - networks: - - backend + mysql: + container_name: ${MYSQL_PROD_CONTAINER_NAME} + image: mysql + ports: + - ${MYSQL_PROD_PORT}:3306 + volumes: + - ${DOCKER_VOLUMNS_PATH}/${MYSQL_PROD_CONTAINER_NAME}:/var/lib/mysql + command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8 --collation-server=utf8_general_ci --log-bin-trust-function-creators=1 + environment: + - MYSQL_ROOT_PASSWORD=${MYSQL_PROD_ROOT_PASSWORD} + - MYSQL_DATABASE=${MYSQL_PROD_SCHEME} + - MYSQL_USER=${MYSQL_PROD_USER} + - MYSQL_PASSWORD=${MYSQL_PROD_PASSWORD} + networks: + - backend - redis: - container_name: ${REDIS_PROD_CONTAINER_NAME} - image: redis - ports: - - ${REDIS_PROD_PORT}:6379 - volumes: - - ${DOCKER_VOLUMNS_PATH}/${REDIS_PROD_CONTAINER_NAME}:/data - networks: - - backend + redis: + container_name: ${REDIS_PROD_CONTAINER_NAME} + image: redis + ports: + - ${REDIS_PROD_PORT}:6379 + volumes: + - ${DOCKER_VOLUMNS_PATH}/${REDIS_PROD_CONTAINER_NAME}:/data + networks: + - backend networks: - backend: - driver: "bridge" + backend: + driver: "bridge" diff --git a/backend/docker/docker-test.yml b/backend/docker/docker-test.yml new file mode 100644 index 00000000..6ae7af62 --- /dev/null +++ b/backend/docker/docker-test.yml @@ -0,0 +1,32 @@ +version: "2.0" + +services: + mysql_test: + container_name: ${MYSQL_TEST_CONTAINER_NAME} + image: mysql + ports: + - ${MYSQL_TEST_PORT}:3306 + volumes: + - ${DOCKER_VOLUMNS_PATH}/${MYSQL_TEST_CONTAINER_NAME}:/var/lib/mysql + command: --default-authentication-plugin=mysql_native_password --character-set-server=utf8 --collation-server=utf8_general_ci --log-bin-trust-function-creators=1 + environment: + - MYSQL_ROOT_PASSWORD=${MYSQL_TEST_ROOT_PASSWORD} + - MYSQL_DATABASE=${MYSQL_TEST_SCHEME} + - MYSQL_USER=${MYSQL_TEST_USER} + - MYSQL_PASSWORD=${MYSQL_TEST_PASSWORD} + networks: + - backend + + redis_test: + container_name: ${REDIS_TEST_CONTAINER_NAME} + image: redis + ports: + - ${REDIS_TEST_PORT}:6379 + volumes: + - ${DOCKER_VOLUMNS_PATH}/${REDIS_TEST_CONTAINER_NAME}:/data + networks: + - backend + +networks: + backend: + driver: "bridge" diff --git a/backend/express/CookieKeys.js b/backend/express/CookieKeys.js new file mode 100644 index 00000000..1cfc8e0a --- /dev/null +++ b/backend/express/CookieKeys.js @@ -0,0 +1,6 @@ +const GUEST_APP = "vaagle-guest"; +const HOST_APP = "vaagle-host"; + +export default { + GUEST_APP, HOST_APP, +}; diff --git a/backend/express/app.js b/backend/express/app.js index 02b5743f..47dd7d9a 100644 --- a/backend/express/app.js +++ b/backend/express/app.js @@ -1,19 +1,20 @@ -import {config} from "dotenv"; +import dotenv from "dotenv"; import express from "express"; import passport from "passport"; import cors from "cors"; import cookieParser from "cookie-parser"; import morgan from "morgan"; -import loadConfig from "./config/configLoader.js"; +import config from "./config"; import applyStaticAppServing from "./middleware/applyStaticAppServing.js"; import "./authentication/google.js"; import authRouter from "./routes/auth"; import guestRouter from "./routes/guest"; import hostRouter from "./routes/host"; +import logger from "./logger.js"; -config(); +dotenv.config() -const {port, publicPath, routePage} = loadConfig(); +const {port, publicPath, routePage} = config; const app = express(); applyStaticAppServing(app, publicPath); @@ -32,10 +33,9 @@ app.get("/", (req, res, next) => { }); app.listen(port, () => { - console.log( - `start express server at ${port} with ${process.env.NODE_ENV} mode`, + logger.info( + `start express server at ${port} with ${process.env.NODE_ENV} mode at public path = ${publicPath}`, ); - console.log(`public path = ${publicPath}`); }); export default app; diff --git a/backend/express/authentication/google.js b/backend/express/authentication/google.js index c882fe17..51ba1b9d 100644 --- a/backend/express/authentication/google.js +++ b/backend/express/authentication/google.js @@ -1,7 +1,8 @@ import passport from "passport"; import {Strategy} from "passport-google-oauth20"; -import loadConfig from "../config/configLoader"; +import config from "../config"; import {createHost, findHostByAuthId} from "../../DB/queries/host"; +import logger from "../logger.js"; const GoogleStrategy = Strategy; @@ -11,6 +12,7 @@ function extractProfile(profile) { if (profile.photos && profile.photos.length) { imageUrl = profile.photos[0].value; } + return { id: profile.id, displayName: profile.displayName, @@ -19,9 +21,9 @@ function extractProfile(profile) { }; } -export default (function() { - const {oAuthArgs} = loadConfig(); +const {oAuthArgs} = config; +export default (function() { const verify = async (accessToken, refreshToken, profile, cb) => { try { const {id, displayName, image, email} = extractProfile(profile); @@ -30,9 +32,11 @@ export default (function() { if (!host) { host = await createHost(id, displayName, image, email); } + return cb(null, host); } catch (error) { - console.error(error); + logger.error(error); + return null; } }; diff --git a/backend/express/authentication/token.js b/backend/express/authentication/token.js index 5b45358f..1c6fcda6 100644 --- a/backend/express/authentication/token.js +++ b/backend/express/authentication/token.js @@ -1,15 +1,14 @@ import jwt from "jsonwebtoken"; -import loadConfig from "../config/configLoader"; +import config from "../config"; + +const {tokenArgs} = config; +const expiresIn = "24 hour"; export default function generateAccessToken(sub, aud) { - const {tokenArgs} = loadConfig(); - const expiresIn = "24 hour"; - const token = jwt.sign({}, tokenArgs.secret, { + return jwt.sign({}, tokenArgs.secret, { expiresIn, issuer: tokenArgs.issuer, audience: aud, subject: sub, }); - - return token; } diff --git a/backend/express/config/configLoader.js b/backend/express/config/index.js similarity index 79% rename from backend/express/config/configLoader.js rename to backend/express/config/index.js index 1701a295..f3acf69b 100644 --- a/backend/express/config/configLoader.js +++ b/backend/express/config/index.js @@ -1,9 +1,9 @@ -import {config} from "dotenv"; +import dotenv from "dotenv"; import devConfig from "./express.dev.config.js"; import prodConfig from "./express.prod.config.js"; import testConfig from "./express.test.config.js"; -config(); +dotenv.config(); function loadConfig() { let configs = {}; @@ -19,4 +19,6 @@ function loadConfig() { return configs; } -export default loadConfig; +const config = loadConfig(); + +export default config; diff --git a/backend/express/logger.js b/backend/express/logger.js new file mode 100644 index 00000000..e43dab6e --- /dev/null +++ b/backend/express/logger.js @@ -0,0 +1,5 @@ +import getLogger from "../libs/logger.js"; + +const logger = getLogger("express"); + +export default logger; diff --git a/backend/express/middleware/authenticate.js b/backend/express/middleware/authenticate.js index 82aa7e27..ae987d10 100644 --- a/backend/express/middleware/authenticate.js +++ b/backend/express/middleware/authenticate.js @@ -1,62 +1,55 @@ import jwt from "jsonwebtoken"; -import loadConfig from "../config/configLoader.js"; +import config from "../config"; import {findHostByAuthId} from "../../DB/queries/host"; -import {findGuestBySid} from "../../DB/queries/guest"; -import {getEventIdByEventCode} from "../../DB/queries/event"; - -const {tokenArgs, routePage} = loadConfig(); - -async function comparePathToCookie(path, guest) { - const eventCode = Buffer.from(path, "base64").toString(); - const EventId = await getEventIdByEventCode(eventCode); - if (guest.EventId !== EventId.dataValues.id) { - return true; - } - return false; -} +import {isExistGuest} from "../../DB/queries/guest"; +import {convertPathToEventId} from "../utils"; +import logger from "../logger.js"; +import CookieKeys from "../CookieKeys.js"; -export function guestAuthenticate() { - const cookieName = "vaagle-guest"; +const {tokenArgs, routePage} = config; +export function guestAuthenticate() { return async function(req, res, next) { const path = req.params.path; + try { const payload = jwt.verify( - req.cookies[cookieName], + req.cookies[CookieKeys.GUEST_APP], tokenArgs.secret ); - const guest = await findGuestBySid(payload.sub); - const isAnotherPath = await comparePathToCookie(path, guest); - if (isAnotherPath) { - return next(); - } - if (!guest) { - throw Error("token is invalid"); + + const guest = await isExistGuest(payload.sub); + const eventId = await convertPathToEventId(path); + const isGuestBelongToEvent = guest.EventId === eventId; + + if (isGuestBelongToEvent) { + return res.redirect(routePage.guest); } - res.redirect(routePage.guest); + + return next(); } catch (e) { + logger.error(e); return next(); } }; } export function hostAuthenticate() { - const cookieName = "vaagle-host"; - return async function(req, res, next) { try { const payload = jwt.verify( - req.cookies[cookieName], + req.cookies[CookieKeys.HOST_APP], tokenArgs.secret ); const host = await findHostByAuthId(payload.sub); - if (!host) { - throw Error("token is invalid"); + if (host) { + res.redirect(routePage.host); } - res.redirect(routePage.host); + + return next(); } catch (e) { - console.log(e); + logger.error(e); return next(); } }; diff --git a/backend/express/routes/auth.js b/backend/express/routes/auth.js index 99ab7cab..d9360de1 100644 --- a/backend/express/routes/auth.js +++ b/backend/express/routes/auth.js @@ -1,10 +1,12 @@ import express from "express"; import passport from "passport"; -import {getTokenExpired} from "../utils"; +import {getTokenExpired} from "../../libs/utils"; import generateAccessToken from "../authentication/token"; -import loadConfig from "../config/configLoader"; +import config from "../config"; +import CookieKeys from "../CookieKeys.js"; -const {routePage} = loadConfig(); +const EXPIRE_TIME = 2; +const {routePage} = config; const router = express.Router(); router.get( @@ -13,7 +15,7 @@ router.get( session: false, scope: ["email", "profile"], prompt: "select_account", - }), + }) ); router.get( @@ -24,8 +26,11 @@ router.get( (req, res) => { const accessToken = generateAccessToken(req.user.oauthId, "host"); - res.cookie("vaagle-host", accessToken, {expires: getTokenExpired(24)}); + res.cookie(CookieKeys.HOST_APP, accessToken, { + expires: getTokenExpired(EXPIRE_TIME), + }); res.redirect(routePage.host); - }, + } ); -module.exports = router; + +export default router; diff --git a/backend/express/routes/guest.js b/backend/express/routes/guest.js index 108940c9..08a0aebe 100644 --- a/backend/express/routes/guest.js +++ b/backend/express/routes/guest.js @@ -1,42 +1,55 @@ import express from "express"; -import {getTokenExpired} from "../utils"; +import jwt from "jsonwebtoken"; +import {getTokenExpired} from "../../libs/utils"; import generateAccessToken from "../authentication/token"; -import loadConfig from "../config/configLoader"; +import config from "../config"; import {guestAuthenticate} from "../middleware/authenticate"; import {createGuest} from "../../DB/queries/guest"; -import {getEventIdByEventCode} from "../../DB/queries/event"; +import {convertPathToEventId} from "../utils"; +import CookieKeys from "../CookieKeys.js"; +import logger from "../logger.js"; +import {isExistGuest} from "../../DB/queries/guest"; -const {routePage} = loadConfig(); +const {routePage, tokenArgs} = config; const router = express.Router(); -const cookieName = "vaagle-guest"; +const cookieExpireTime = 2; -async function pathToCode(path) { - const eventCode = Buffer.from(path, "base64").toString(); - const eventId = await getEventIdByEventCode(eventCode); +router.get("/", async (req, res, next) => { + try { + const payload = jwt.verify( + req.cookies[CookieKeys.GUEST_APP], + tokenArgs.secret + ); - return eventId.dataValues.id; -} + const guest = await isExistGuest(payload.sub); + if (!guest) { + throw Error("Guest is not found"); + } -router.get("/", guestAuthenticate(), (req, res, next) => { - res.redirect(routePage.main); + res.redirect(routePage.guest); + } catch (e) { + logger.error([e, e.stack]); + res.redirect(routePage.main); + } }); router.get("/logout", (req, res, next) => { - res.clearCookie(cookieName).redirect(routePage.main); + res.clearCookie(CookieKeys.GUEST_APP).redirect(routePage.main); }); router.get("/:path", guestAuthenticate(), async (req, res, next) => { try { const path = req.params.path; - const eventId = await pathToCode(path); - const guest = await createGuest("Anonymous", eventId); + const eventId = await convertPathToEventId(path); + const guest = await createGuest(eventId); const accessToken = generateAccessToken(guest.guestSid, "guest"); - res.cookie(cookieName, accessToken, { - expires: getTokenExpired(24), + + res.cookie(CookieKeys.GUEST_APP, accessToken, { + expires: getTokenExpired(cookieExpireTime), }); res.redirect(routePage.guest); } catch (e) { - console.log(e); + logger.error([e, e.stack]); res.redirect(routePage.main); } }); diff --git a/backend/express/routes/host.js b/backend/express/routes/host.js index 988897cb..5b8cc851 100644 --- a/backend/express/routes/host.js +++ b/backend/express/routes/host.js @@ -1,13 +1,13 @@ import express from "express"; -import loadConfig from "../config/configLoader"; +import config from "../config"; import {hostAuthenticate} from "../middleware/authenticate"; +import CookieKeys from "../CookieKeys.js"; -const {routePage} = loadConfig(); +const {routePage} = config; const router = express.Router(); -const cookieName = "vaagle-host"; router.get("/logout", (req, res, next) => { - res.clearCookie(cookieName).redirect(routePage.main); + res.clearCookie(CookieKeys.HOST_APP).redirect(routePage.main); }); router.get("/", hostAuthenticate(), (req, res, next) => { diff --git a/backend/express/utils.js b/backend/express/utils.js index 9e1735e9..d8efa91b 100644 --- a/backend/express/utils.js +++ b/backend/express/utils.js @@ -1,7 +1,15 @@ -const getSequelizeData = function(data) { - return JSON.parse(JSON.stringify(data)); -}; +import {getEventByEventCode} from "../DB/queries/event"; +import {compareCurrentDateToTarget} from "../libs/utils"; -const getTokenExpired = hour => new Date(new Date().getTime() + 1000 * 60 * 60 * Number(hour)); +async function convertPathToEventId(path, guest) { + const eventCode = Buffer.from(path, "base64").toString(); + let event = await getEventByEventCode(eventCode); + event = event.get({plain: true}); + const diff = compareCurrentDateToTarget(event.endAt); + if (diff <= 0) { + throw new Error("이벤트 만료기간이 지났습니다."); + } + return event.id; +} -export {getSequelizeData, getTokenExpired}; +export {convertPathToEventId}; diff --git a/backend/graphQL/app.js b/backend/graphQL/app.js index f0cab76c..50743ae5 100644 --- a/backend/graphQL/app.js +++ b/backend/graphQL/app.js @@ -6,55 +6,40 @@ import cookieParser from "cookie-parser"; import resolvers from "./resolvers.js"; import typeDefs from "./typeDefs.js"; import config from "./config.js"; -import {findHostByAuthId} from "../DB/queries/host"; import logger from "./logger.js"; +import authenticate from "./middlewares/authenticate.js"; -const authenticate = async (resolve, root, args, context, info) => { - let audience = "anonymous"; +function parserJWT(data) { + return data.split(" ")[1]; +} - audience = context.payload && context.payload.aud; - let authority = {sub: null, info: null}; +const context = ({request}) => { + const authorization = request.headers.authorization; - switch (audience) { - case "host": - { - const hostInfo = await findHostByAuthId(context.payload.sub); + if (!authorization) { + return {payload: undefined}; + } - authority = {sub: "host", info: hostInfo}; - break; - } - case "guest": - { - const guestInfo = context.payload.sub; + let payload; - authority = {sub: "guest", info: guestInfo}; - break; - } - default : { - // console.log(`unexpected type of audience ${audience}`) - } + try { + payload = jwt.verify( + parserJWT(authorization), + process.env.AUTH_TOKEN_SECRET, + ); + } catch (e) { + logger.error(e); + payload = undefined; } - const result = await resolve(root, args, authority, info); - return result; + return {payload}; }; const server = new GraphQLServer({ typeDefs, resolvers, middlewares: [authenticate], - context: ({request}) => { - let payload; - const token = request.headers.authorization || ""; - - try { - payload = token === "" ? undefined : jwt.verify(token.split(" ")[1], process.env.AUTH_TOKEN_SECRET); - } catch (e) { - payload = undefined; - } finally { - return {payload}; - } - }, + context, }); server.express.use(cors()); diff --git a/backend/graphQL/authentication/jwt.js b/backend/graphQL/authentication/jwt.js deleted file mode 100644 index 1c3e695d..00000000 --- a/backend/graphQL/authentication/jwt.js +++ /dev/null @@ -1,30 +0,0 @@ -import passport from "passport"; -import passportJwt from "passport-jwt"; -import {findHostById} from "../../DB/queries/host"; - -export default (function() { - const tokenArgs = { - secret: process.env.AUTH_TOKEN_SECRET, - issuer: process.env.AUTH_TOKEN_ISSUER, - audience: process.env.AUTH_TOKEN_AUDIENCE, - }; - const jwtOptions = { - jwtFromRequest: passportJwt.ExtractJwt.fromAuthHeaderAsBearerToken(), - secretOrKey: tokenArgs.secret, - issuer: tokenArgs.issuer, - audience: tokenArgs.audience, - }; - - passport.use( - new passportJwt.Strategy(jwtOptions, async (payload, cb) => { - try { - const host = await findHostById(payload.sub); - - if (host) { - return cb(null, host, payload); - } - return cb(); - } catch (error) {} - }), - ); -})(); diff --git a/backend/graphQL/authentication/token.js b/backend/graphQL/authentication/token.js index 20477415..56f5aa45 100644 --- a/backend/graphQL/authentication/token.js +++ b/backend/graphQL/authentication/token.js @@ -1,18 +1,17 @@ import jwt from "jsonwebtoken"; +const expiresIn = "1 hour"; +const tokenArgs = { + secret: process.env.AUTH_TOKEN_SECRET, + issuer: process.env.AUTH_TOKEN_ISSUER, + audience: process.env.AUTH_TOKEN_AUDIENCE, +}; + export default function generateAccessToken(hostOauthId) { - const tokenArgs = { - secret: process.env.AUTH_TOKEN_SECRET, - issuer: process.env.AUTH_TOKEN_ISSUER, - audience: process.env.AUTH_TOKEN_AUDIENCE, - }; - const expiresIn = "1 hour"; - const token = jwt.sign({}, tokenArgs.secret, { + return jwt.sign({}, tokenArgs.secret, { expiresIn, issuer: tokenArgs.issuer, audience: tokenArgs.audience, subject: hostOauthId, }); - - return token; } diff --git a/backend/graphQL/middlewares/authenticate.js b/backend/graphQL/middlewares/authenticate.js new file mode 100644 index 00000000..6058a824 --- /dev/null +++ b/backend/graphQL/middlewares/authenticate.js @@ -0,0 +1,29 @@ +import {findHostByAuthId} from "../../DB/queries/host.js"; +import logger from "../logger.js"; + +const authenticate = async (resolve, root, args, context, info) => { + const audience = context.payload && context.payload.aud; + let authority = {sub: null, info: null}; + + switch (audience) { + case "host": { + const hostInfo = await findHostByAuthId(context.payload.sub); + + authority = {sub: "host", info: hostInfo}; + break; + } + case "guest": { + const guestInfo = context.payload.sub; + + authority = {sub: "guest", info: guestInfo}; + break; + } + default: { + logger.error(`unexpected type of audience ${audience}`); + } + } + + return resolve(root, args, authority, info); +}; + +export default authenticate; diff --git a/backend/graphQL/model/guest/guest.resolver.js b/backend/graphQL/model/guest/guest.resolver.js index 948d2366..e92246ff 100644 --- a/backend/graphQL/model/guest/guest.resolver.js +++ b/backend/graphQL/model/guest/guest.resolver.js @@ -1,4 +1,7 @@ -import {getGuestByEventId, findGuestBySid} from "../../../DB/queries/guest.js"; +import { + getGuestByEventId, + getGuestByGuestSid, +} from "../../../DB/queries/guest.js"; import {getEventById} from "../../../DB/queries/event.js"; const guestResolver = async EventId => getGuestByEventId(EventId); @@ -8,10 +11,10 @@ const guestInEventResolver = async authority => { throw Error("AuthenticationError in guestInEventResolver"); } - const guest = await findGuestBySid(authority.info); - const event = await getEventById(guest.EventId); + const guest = (await getGuestByGuestSid(authority.info)).get({plain: true}); + const event = (await getEventById(guest.EventId)).get({plain: true}); - return {event: event.dataValues, guest}; + return {event, guest}; }; // noinspection JSUnusedGlobalSymbols diff --git a/backend/graphQL/model/hostpage/hostpage.resolver.js b/backend/graphQL/model/hostpage/hostpage.resolver.js index 0c1adb51..f0d77ee0 100644 --- a/backend/graphQL/model/hostpage/hostpage.resolver.js +++ b/backend/graphQL/model/hostpage/hostpage.resolver.js @@ -11,12 +11,44 @@ import { createHashtag, getHashtagByEventIds, } from "../../../DB/queries/hashtag.js"; +import {compareCurrentDateToTarget} from "../../../libs/utils"; -const moderationResolver = async (eventId, moderationOption) => { - const updatedEvent = await updateEventById(eventId, {moderationOption}); +function verifySubjectHostJwt(jwtSub) { + if (jwtSub !== "host") { + throw new Error("AuthenticationError"); + } +} - return updatedEvent[0]; -}; +function mappingHashTagsToEvents(hashTags, events, eventMap) { + hashTags.forEach(hashTag => { + const hashTagObject = hashTag.get({plain: true}); + + eventMap.get(hashTagObject.EventId).push(hashTagObject); + }); + events.forEach(event => { + Object.assign(event, {HashTags: eventMap.get(event.id)}); + }); + + return events; +} + +async function generateEventCode() { + let generatedEventCode = faker.random.alphaNumeric(4); + const events = await getAllEvents(); + const allreadyExistEventCode = events.map(event => event.eventCode); + + while (1) { + const isExist = allreadyExistEventCode.some( + someCode => generateEventCode === someCode + ); + + if (!isExist) { + break; + } + generatedEventCode = faker.random.alphaNumeric(4); + } + return generatedEventCode; +} const getEventOptionResolver = async eventId => { const evnetOption = await getEventOptionByEventId(eventId); @@ -27,80 +59,69 @@ const getEventOptionResolver = async eventId => { export default { Query: { init: async (_, {param}, authority) => { - if (authority.sub === "host") { - const host = authority.info; - let events = await getEventsByHostId(host.id); - events = events.map(event => event.get({plain: true})); - - const eventMap = new Map(); - const eventIdList = events.map(event => { - eventMap.set(event.id, []); - return event.id; - }); + verifySubjectHostJwt(authority.sub); + const host = authority.info; + let events = await getEventsByHostId(host.id); - let hashTags = await getHashtagByEventIds(eventIdList); - hashTags.forEach(hashTag => { - const hashTagObject = hashTag.get({plain: true}); - eventMap.get(hashTagObject.EventId).push(hashTagObject); - }); - events.forEach(event => { - Object.assign(event, {HashTags: eventMap.get(event.id)}); - }); + events = events.filter(event => { + const eventPlainObject = event.get({plain: true}); + return eventPlainObject; + }); - return {events, host}; - } + const eventMap = new Map(); + const eventIdList = events.map(event => { + eventMap.set(event.id, []); + return event.id; + }); + + const hashTags = await getHashtagByEventIds(eventIdList); + events = mappingHashTagsToEvents(hashTags, events, eventMap); - throw new Error("AuthenticationError"); + return {events, host}; }, + getEventOption: async (_, {EventId}) => getEventOptionResolver(EventId), }, + Mutation: { createHashTags: async (_, {hashTags}, authority) => { - for (let hashTag of hashTags) { + verifySubjectHostJwt(authority.sub); + for (const hashTag of hashTags) { await createHashtag({ name: hashTag.name, EventId: hashTag.EventId, }); } }, + createEvent: async (_, {info}, authority) => { - if (authority.sub === "host") { - let eventCode = faker.random.alphaNumeric(4); - const events = await getAllEvents(); - const existCode = events.map(event => event.eventCode); - - while (true) { - const exist = existCode.some( - someCode => eventCode === someCode - ); - - if (!exist) break; - eventCode = faker.random.alphaNumeric(4); - } - let event = await createEvent({ - eventName: info.eventName, - eventCode, - HostId: authority.info.id, - startAt: info.startAt, - endAt: info.endAt, - }); + verifySubjectHostJwt(authority.sub); + const eventCode = await generateEventCode(); - event = event[0].dataValues; - return {...event}; - } - throw new Error("AuthenticationError"); + let event = await createEvent({ + eventName: info.eventName, + eventCode: eventCode, + HostId: authority.info.id, + startAt: info.startAt, + endAt: info.endAt, + }); + + event = event[0].get({plain: true}); + return {...event}; }, + updateEvent: async (_, {event}, authority) => { - let updatedEvent = await updateEventById(event.EventId, { + verifySubjectHostJwt(authority.sub); + await updateEventById({ + id: event.EventId, eventName: event.eventName, startAt: event.startAt, endAt: event.endAt, }); - updatedEvent = await getEventById(event.EventId); + + const updatedEvent = await getEventById(event.EventId); return updatedEvent.get({plain: true}); }, - moderation: (_, {eventId, moderationOption}) => - moderationResolver(eventId, moderationOption), }, }; diff --git a/backend/graphQL/model/poll/pollGuest.resolver.js b/backend/graphQL/model/poll/pollGuest.resolver.js index b60ba219..032d83f7 100644 --- a/backend/graphQL/model/poll/pollGuest.resolver.js +++ b/backend/graphQL/model/poll/pollGuest.resolver.js @@ -1,100 +1,11 @@ -import {getPollsByEventId} from "../../../DB/queries/poll.js"; -import {getCandidatesByPollId} from "../../../DB/queries/candidate.js"; +import {getCandidatesByGuestId} from "../../../DB/queries/vote.js"; import { - getVotersByCandidateList, - getCandidatesByGuestId, -} from "../../../DB/queries/vote.js"; - -const simplifyList = list => list.map(n => n.get({plain: true})); - -/** - * - * @param {int} pollId - * @param {array of int} candidates - * - * 하나의 poll 은 여러개의 candidates (예. 기호1번, 기호2번 등)을 가지고 있음 - * 각 candidate 별 득표수를 계산하고, - * 가장 많은 득표수를 받은 candidate의 firstPlace 값을 true로 설정하고, - * candidates들을 묶어서 nItems 라는 하나의 array에 넣어서 poll 객체에 넣어주는 함수 - */ -async function getItems(pollId, candidates) { - const nItems = []; - let firstPlaceIndex = []; - let index = 0; - let firstPlaceValue = 0; - - for (const n of candidates) { - if (n.PollId === pollId) { - const voters = await getVotersByCandidateList([n.id]); - - nItems.push({ - ...n, - voters, - voted: false, - firstPlace: false, - }); - // Poll에 속한 candidates들 중에 가장 많은 득표수를 받은 candidate을 찾아내는 부분 - if (voters == firstPlaceValue) { - firstPlaceIndex.push(index); - } else if (voters > firstPlaceValue) { - firstPlaceIndex = []; - firstPlaceIndex.push(index); - firstPlaceValue = voters; - } - index++; - } - } - // Poll에 속한 candidates들 중에 가장 많은 득표수를 받은 candidate을 지정하는 부분 - firstPlaceIndex.forEach(i => { - nItems[i].firstPlace = true; - }); - - return nItems; -} - -/** - * - * @param {array of object} polls - * - * 하나의 poll에 속한 candidates들을 DB에서 읽어오는 함수 - */ -async function getCandidatesByPolls(polls) { - const pollIdList = polls.map(poll => poll.id); - let candidates = await getCandidatesByPollId(pollIdList); - - candidates = simplifyList(candidates); - - return candidates; -} - -/** - * - * @param {array of object} items - * - * CandidateId (int) 만 읽어서 array로 반환해주는 함수 - */ -const getCandidateList = items => items.map(n => n.id); - -/** - * - * @param {array of object} polls - * @param {array of object} candidates - * - * 여러개의 poll들과 그 poll들에 속하는 모든 candidates를 가져와서 - * 각 poll에 속한 candidates 들만 nItems 객체로 만들어서 저장함 - * - * 그리고 해당 poll에 속한 모든 candidates들에 투표한 투표자수를 unique하게 더한 총투표수를 구함 - */ -async function setPollItems(polls, candidates) { - for (const poll of polls) { - poll.nItems = await getItems(poll.id, candidates); - const candidateList = getCandidateList(poll.nItems); - - poll.totalVoters = await getVotersByCandidateList(candidateList); - } - - return polls; -} + getCandidateList, + getCandidatesByPolls, + setPollItems, + simplifyList, +} from "./resolveHelper.js"; +import {getPollsByEventId} from "../../../DB/queries/poll.js"; /** * diff --git a/backend/graphQL/model/poll/pollHost.resolver.js b/backend/graphQL/model/poll/pollHost.resolver.js index fbd2cead..28e28a0c 100644 --- a/backend/graphQL/model/poll/pollHost.resolver.js +++ b/backend/graphQL/model/poll/pollHost.resolver.js @@ -1,98 +1,5 @@ import {getPollsByEventId} from "../../../DB/queries/poll.js"; -import {getCandidatesByPollId} from "../../../DB/queries/candidate.js"; -import {getVotersByCandidateList} from "../../../DB/queries/vote.js"; -// import { getCandidatesByGuestId } from "../../../DB/queries/vote.js"; - -const simplifyList = list => list.map(n => n.get({plain: true})); - -/** - * - * @param {int} pollId - * @param {array of int} candidates - * - * 하나의 poll 은 여러개의 candidates (예. 기호1번, 기호2번 등)을 가지고 있음 - * 각 candidate 별 득표수를 계산하고, - * 가장 많은 득표수를 받은 candidate의 firstPlace 값을 true로 설정하고, - * candidates들을 묶어서 nItems 라는 하나의 array에 넣어서 poll 객체에 넣어주는 함수 - */ -async function getItems(pollId, candidates) { - const nItems = []; - let firstPlaceIndex = []; - let index = 0; - let firstPlaceValue = 0; - - for (const n of candidates) { - if (n.PollId === pollId) { - const voters = await getVotersByCandidateList([n.id]); - - nItems.push({ - ...n, - voters, - voted: false, - firstPlace: false, - }); - // Poll에 속한 candidates들 중에 가장 많은 득표수를 받은 candidate을 찾아내는 부분 - if (voters == firstPlaceValue) { - firstPlaceIndex.push(index); - } else if (voters > firstPlaceValue) { - firstPlaceIndex = []; - firstPlaceIndex.push(index); - firstPlaceValue = voters; - } - index++; - } - } - // Poll에 속한 candidates들 중에 가장 많은 득표수를 받은 candidate을 지정하는 부분 - firstPlaceIndex.forEach(i => { - nItems[i].firstPlace = true; - }); - - return nItems; -} - -/** - * - * @param {array of object} polls - * - * 하나의 poll에 속한 candidates들을 DB에서 읽어오는 함수 - */ -async function getCandidatesByPolls(polls) { - const pollIdList = polls.map(poll => poll.id); - let candidates = await getCandidatesByPollId(pollIdList); - - candidates = simplifyList(candidates); - - return candidates; -} - -/** - * - * @param {array of object} items - * - * CandidateId (int) 만 읽어서 array로 반환해주는 함수 - */ -const getCandidateList = items => items.map(n => n.id); - -/** - * - * @param {array of object} polls - * @param {array of object} candidates - * - * 여러개의 poll들과 그 poll들에 속하는 모든 candidates를 가져와서 - * 각 poll에 속한 candidates 들만 nItems 객체로 만들어서 저장함 - * - * 그리고 해당 poll에 속한 모든 candidates들에 투표한 투표자수를 unique하게 더한 총투표수를 구함 - */ -async function setPollItems(polls, candidates) { - for (const poll of polls) { - poll.nItems = await getItems(poll.id, candidates); - const candidateList = getCandidateList(poll.nItems); - - poll.totalVoters = await getVotersByCandidateList(candidateList); - } - - return polls; -} +import {getCandidatesByPolls, setPollItems, simplifyList} from "./resolveHelper.js"; /** * @@ -116,7 +23,6 @@ async function pollHostResolver(EventId) { const candidates = await getCandidatesByPolls(polls); polls = await setPollItems(polls, candidates); - return polls; } diff --git a/backend/graphQL/model/poll/resolveHelper.js b/backend/graphQL/model/poll/resolveHelper.js new file mode 100644 index 00000000..6d1811da --- /dev/null +++ b/backend/graphQL/model/poll/resolveHelper.js @@ -0,0 +1,90 @@ +import {getVotersByCandidateList} from "../../../DB/queries/vote.js"; +import {getCandidatesByPollId} from "../../../DB/queries/candidate.js"; + +export const simplifyList = list => list.map(n => n.get({plain: true})); + +/** + * + * @param {array of object} items + * + * CandidateId (int) 만 읽어서 array로 반환해주는 함수 + */ +export const getCandidateList = items => items.map(n => n.id); +/** + * + * @param {int} pollId + * @param {array of int} candidates + * + * 하나의 poll 은 여러개의 candidates (예. 기호1번, 기호2번 등)을 가지고 있음 + * 각 candidate 별 득표수를 계산하고, + * 가장 많은 득표수를 받은 candidate의 firstPlace 값을 true로 설정하고, + * candidates들을 묶어서 nItems 라는 하나의 array에 넣어서 poll 객체에 넣어주는 함수 + */ +export async function getItems(pollId, candidates) { + const nItems = []; + let firstPlaceIndex = []; + let index = 0; + let firstPlaceValue = 0; + + for (const n of candidates) { + if (n.PollId === pollId) { + const voters = await getVotersByCandidateList([n.id]); + + nItems.push({ + ...n, + voters, + voted: false, + firstPlace: false, + }); + // Poll에 속한 candidates들 중에 가장 많은 득표수를 받은 candidate을 찾아내는 부분 + if (voters === firstPlaceValue) { + firstPlaceIndex.push(index); + } else if (voters > firstPlaceValue) { + firstPlaceIndex = []; + firstPlaceIndex.push(index); + firstPlaceValue = voters; + } + index++; + } + } + // Poll에 속한 candidates들 중에 가장 많은 득표수를 받은 candidate을 지정하는 부분 + firstPlaceIndex.forEach(i => { + nItems[i].firstPlace = true; + }); + + return nItems; +} + +/** + * + * @param {array of object} polls + * + * 하나의 poll에 속한 candidates들을 DB에서 읽어오는 함수 + */ +export async function getCandidatesByPolls(polls) { + const pollIdList = polls.map(poll => poll.id); + const candidates = await getCandidatesByPollId(pollIdList); + + return simplifyList(candidates); +} + +/** + * + * @param {array of object} polls + * @param {array of object} candidates + * + * 여러개의 poll들과 그 poll들에 속하는 모든 candidates를 가져와서 + * 각 poll에 속한 candidates 들만 nItems 객체로 만들어서 저장함 + * + * 그리고 해당 poll에 속한 모든 candidates들에 투표한 투표자수를 unique하게 더한 총투표수를 구함 + */ +export async function setPollItems(polls, candidates) { + for (const poll of polls) { + poll.nItems = await getItems(poll.id, candidates); + const candidateList = getCandidateList(poll.nItems); + + poll.totalVoters = await getVotersByCandidateList(candidateList); + } + + return polls; +} diff --git a/backend/libs/utils.js b/backend/libs/utils.js new file mode 100644 index 00000000..b0f615dc --- /dev/null +++ b/backend/libs/utils.js @@ -0,0 +1,17 @@ +import moment from "moment"; + +const compareCurrentDateToTarget = baseDate => { + const endAt = moment(baseDate); + const current = moment(); + + return endAt.diff(current, "minute"); +}; + +const getSequelizeData = function(data) { + return JSON.parse(JSON.stringify(data)); +}; + +const getTokenExpired = hour => + new Date(new Date().getTime() + 1000 * 60 * 60 * Number(hour)); + +export {getSequelizeData, getTokenExpired, compareCurrentDateToTarget}; diff --git a/backend/package.json b/backend/package.json index 8b172589..d723043c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,10 +8,10 @@ "scripts": { "node:babel": "node -r @babel/register", "reinstall": "rm -rf ../node_modules && yarn install", - "test": "./node_modules/.bin/mocha ./spec --recursive", - "test:DB": "./node_modules/.bin/mocha ./spec -name '*.db.spec.js' --recursive --watch", - "test:watch": "./node_modules/.bin/mocha ./spec --recursive --watch", - "test:only": "./node_modules/.bin/mocha --watch", + "test": "cross-env NODE_ENV=test ./node_modules/.bin/mocha ./spec --recursive", + "test:DB": "cross-env NODE_ENV=test ./node_modules/.bin/mocha ./spec -name '*.db.spec.js' --recursive --watch", + "test:watch": "cross-env NODE_ENV=test ./node_modules/.bin/mocha ./spec --recursive --watch", + "test:only": "cross-env NODE_ENV=test ./node_modules/.bin/mocha --watch", "build": "sh script/build.sh", "build:clean": "rm -rf ./build", "start": "concurrently \"yarn start:socket\" \"yarn start:express\" \"yarn start:yoga\" \"yarn start:DB\" ", @@ -19,8 +19,10 @@ "start:socket": "cross-env NODE_ENV=development ./node_modules/.bin/nodemon --config ./nodemon.json --exec babel-watch ./socket_io_server/app.js", "start:yoga": "cross-env NODE_ENV=development ./node_modules/.bin/nodemon --config ./nodemon.json --exec babel-watch ./graphQL/app.js", "start:DB": "docker-compose -f docker/docker-development.yml up -d", - "stop": "yarn stop:DB", + "start:DB:test": "docker-compose -f docker/docker-test.yml up -d", "stop:DB": "docker-compose -f docker/docker-development.yml down --remove-orphans", + "stop:DB:test": "docker-compose -f docker/docker-test.yml down --remove-orphans", + "stop": "yarn stop:DB", "migration:development": "cross-env NODE_ENV=development sh ./script/sequelize.sh", "migration:test": "cross-env NODE_ENV=test sh ./script/sequelize.sh", "migration:production": "cross-env NODE_ENV=production sh ./script/sequelize.sh", diff --git a/backend/redis/config/dev.config.js b/backend/redis/config/dev.config.js index 25b91377..a0692eed 100644 --- a/backend/redis/config/dev.config.js +++ b/backend/redis/config/dev.config.js @@ -3,7 +3,7 @@ import dotenv from "dotenv"; dotenv.config(); const config = { - port: process.env.REDIS_DEV_PORT, + port: parseInt(process.env.REDIS_DEV_PORT, 10), host: process.env.REDIS_DEV_HOST, }; diff --git a/backend/redis/config/configLoader.js b/backend/redis/config/index.js similarity index 87% rename from backend/redis/config/configLoader.js rename to backend/redis/config/index.js index 76b974ef..52eae70c 100644 --- a/backend/redis/config/configLoader.js +++ b/backend/redis/config/index.js @@ -17,4 +17,6 @@ function loadConfig() { return config; } -export default loadConfig; +const config = loadConfig(); + +export default config; diff --git a/backend/redis/config/prod.config.js b/backend/redis/config/prod.config.js index 25b91377..1f1fc5eb 100644 --- a/backend/redis/config/prod.config.js +++ b/backend/redis/config/prod.config.js @@ -3,8 +3,8 @@ import dotenv from "dotenv"; dotenv.config(); const config = { - port: process.env.REDIS_DEV_PORT, - host: process.env.REDIS_DEV_HOST, + port: parseInt(process.env.REDIS_PROD_PORT, 10), + host: process.env.REDIS_PROD_HOST, }; export default config; diff --git a/backend/redis/config/test.config.js b/backend/redis/config/test.config.js index 25b91377..91d8d18c 100644 --- a/backend/redis/config/test.config.js +++ b/backend/redis/config/test.config.js @@ -3,8 +3,8 @@ import dotenv from "dotenv"; dotenv.config(); const config = { - port: process.env.REDIS_DEV_PORT, - host: process.env.REDIS_DEV_HOST, + port: parseInt(process.env.REDIS_TEST_PORT, 10), + host: process.env.REDIS_TEST_HOST, }; export default config; diff --git a/backend/redis/redisClient.js b/backend/redis/redisClient.js index acedd402..e72339e6 100644 --- a/backend/redis/redisClient.js +++ b/backend/redis/redisClient.js @@ -1,9 +1,8 @@ import redis from "redis"; import asyncRedis from "async-redis"; import getLogger from "../libs/logger.js"; -import loadConfig from "./config/configLoader.js"; +import config from "./config"; -const config = loadConfig(); const client = redis.createClient(config.port, config.host); const asyncRedisClient = asyncRedis.decorate(client); diff --git a/backend/socket_io_server/IORoomManager.js b/backend/socket_io_server/IORoomManager.js deleted file mode 100644 index 468164e0..00000000 --- a/backend/socket_io_server/IORoomManager.js +++ /dev/null @@ -1,59 +0,0 @@ -import logger from "./logger.js"; - -const IORoomManager = ({socket, afterJoinRoom, afterLeaveRoom, room}) => { - const id = socket.id; - let currentRoom = room; - - const joinRoom = async room => { - try { - await socket.join(room); - currentRoom = room; - } catch (e) { - logger.error(`error raised while join room ${e}`); - } - }; - - const leaveRoom = async () => { - try { - socket.leave(currentRoom); - currentRoom = null; - } catch (e) { - logger.error(`error raised while leave room ${e}`); - } - }; - - const onJoinRoom = async req => { - if (currentRoom) { - logger.error(`id: ${id} already join in room`); - return; - } - - await joinRoom(req.room); - socket.emit("joinRoom"); - afterJoinRoom(req.room, socket); - }; - - const onLeaveRoom = async req => { - if (currentRoom === null) { - logger.error(`id: ${id} is not in room`); - return; - } - - const lastRoom = currentRoom; - - await leaveRoom(); - socket.emit("leaveRoom"); - afterLeaveRoom(lastRoom, socket); - }; - - const onChangeRoom = async req => { - await onLeaveRoom(req); - await onJoinRoom(req); - }; - - socket.on("joinRoom", onJoinRoom); - socket.on("leaveRoom", onLeaveRoom); - socket.on("changeRoom", onChangeRoom); -}; - -export default IORoomManager; diff --git a/backend/socket_io_server/RoomSocket.js b/backend/socket_io_server/RoomSocket.js new file mode 100644 index 00000000..a64f8fb1 --- /dev/null +++ b/backend/socket_io_server/RoomSocket.js @@ -0,0 +1,52 @@ +class RoomSocket { + constructor({socket, server, handlerEventPair}) { + this.socket = socket; + this.server = server; + this.handlerPair = handlerEventPair; + this.registeredHandler = []; + } + + joinRoom(room) { + this.room = room; + + this.socket.join(this.room); + + this.registeredHandler = this.handlerPair.map(({eventName, handler}) => + this.addListener(eventName, handler) + ); + + this.socket.emit("joinRoom"); + } + + leaveRoom() { + this.socket.leave(this.room); + + this.registeredHandler.map(({eventName, handler}) => + this.socket.removeListener(eventName, handler) + ); + + this.socket.emit("leaveRoom"); + + this.room = null; + this.registeredHandler = []; + } + + addListener(event, handler) { + const roomEmit = res => { + this.server.to(this.room).emit(event, res); + }; + + const wrappedSocketHandler = data => { + handler(data, roomEmit, {socket: this.socket, server: this.server}); + }; + + this.socket.on(event, wrappedSocketHandler); + + return { + eventName: event, + handler: wrappedSocketHandler, + }; + } +} + +export default RoomSocket; diff --git a/backend/socket_io_server/RoomSocketHelper.js b/backend/socket_io_server/RoomSocketHelper.js new file mode 100644 index 00000000..d98d20cb --- /dev/null +++ b/backend/socket_io_server/RoomSocketHelper.js @@ -0,0 +1,45 @@ +import logger from "./logger.js"; +import RoomSocket from "./RoomSocket.js"; + +const RoomSocketHelper = ({server, socket, handlerEventPair}) => { + const id = socket.id; + const currentRoom = null; + const roomSocket = new RoomSocket({ + socket, + server, + handlerEventPair, + }); + + const onJoinRoom = async req => { + const {room} = req; + + if (currentRoom) { + logger.error(`id: ${id} already join in room`); + return; + } + + try { + roomSocket.joinRoom(room); + } catch (e) { + logger.error(`error raised while join room ${e}`); + } + }; + + const onLeaveRoom = async () => { + // if (currentRoom === null) { + // logger.error(`id: ${id} is not in room`); + // return; + // } + + try { + roomSocket.leaveRoom(); + } catch (e) { + logger.error(`error raised while leave room ${e}`); + } + }; + + socket.on("joinRoom", onJoinRoom); + socket.on("leaveRoom", onLeaveRoom); +}; + +export default RoomSocketHelper; diff --git a/backend/socket_io_server/SocketIOBulkHandlerManager.js b/backend/socket_io_server/SocketIOBulkHandlerManager.js deleted file mode 100644 index 6f55bf9f..00000000 --- a/backend/socket_io_server/SocketIOBulkHandlerManager.js +++ /dev/null @@ -1,41 +0,0 @@ -import logger from "./logger.js"; - -const bindAddSocketListener = (socket, server, room) => ( - eventName, - handler, -) => { - const wrappedHandler = data => { - const emit = res => { - server.to(room).emit(eventName, res); - }; - - try { - handler(data, emit, {socket, server}); - } catch (e) { - logger.error( - `while handing ${eventName} error raise,\n ${e.toString()}\n${ - e.stack - }`, - ); - socket.send({status: "error", error: e}); - } - }; - - socket.on(eventName, wrappedHandler); - - return () => { - socket.off(eventName, wrappedHandler); - }; -}; - -export const addBulkSocketIOHandlers = ({handlers, socket, server, room}) => { - const addSocketListener = bindAddSocketListener(socket, server, room); - - const AttachedHandlers = handlers.map(({eventName, handler}) => { - addSocketListener(eventName, handler); - }); - - return () => AttachedHandlers.map(removeHandler => removeHandler()); -}; - -export const removeBulkSocketIOHandlers = handlers => handlers(); diff --git a/backend/socket_io_server/app.js b/backend/socket_io_server/app.js index 1556161b..6e2f0f3b 100644 --- a/backend/socket_io_server/app.js +++ b/backend/socket_io_server/app.js @@ -5,8 +5,7 @@ import io from "socket.io"; import configLoader from "./config/configLoader.js"; import logger from "./logger.js"; import authenticate from "./middleware/authenticate"; -import IORoomManager from "./IORoomManager.js"; -import {addBulkSocketIOHandlers, removeBulkSocketIOHandlers} from "./SocketIOBulkHandlerManager.js"; +import RoomSocketHelper from "./RoomSocketHelper.js"; import socketHandlers from "./socketHandler"; dotenv.config(); @@ -25,25 +24,13 @@ const namedServer = socketServer.of(NAME_SPACE); socketServer.use(authenticate()); namedServer.on("connection", async socket => { const id = socket.id; - let bulkHandlers = null; logger.info(`id ${id} connected at /${NAME_SPACE}`); - IORoomManager({ - bulkHandlers, + RoomSocketHelper({ socket, - afterJoinRoom: (room, socket) => { - bulkHandlers = addBulkSocketIOHandlers({ - handlers: socketHandlers, - socket, - server: namedServer, - room, - }); - }, - afterLeaveRoom: (room, socket) => { - removeBulkSocketIOHandlers(bulkHandlers); - bulkHandlers = null; - }, + server: namedServer, + handlerEventPair: socketHandlers, }); socket.on("error", error => diff --git a/backend/socket_io_server/middleware/authenticate.js b/backend/socket_io_server/middleware/authenticate.js index c711b9b6..5ca5f03e 100644 --- a/backend/socket_io_server/middleware/authenticate.js +++ b/backend/socket_io_server/middleware/authenticate.js @@ -2,25 +2,34 @@ import jwt from "jsonwebtoken"; import loadConfig from "../config/configLoader"; import logger from "../logger"; import {findHostByAuthId} from "../../DB/queries/host"; -import {findGuestBySid} from "../../DB/queries/guest"; +import {isExistGuest} from "../../DB/queries/guest"; const {tokenArgs} = loadConfig(); +const audienceVerify = {guest: isExistGuest, host: findHostByAuthId}; + +const isValidAud = aud => aud !== "guest" && aud !== "host"; + +const isValidIss = iss => iss !== tokenArgs.issuer; + async function payloadVerify(payload) { const {aud, iss, sub} = payload; - const audienceVerify = {guest: findGuestBySid, host: findHostByAuthId}; - if (iss !== tokenArgs.issuer) { - return new Error("Authentication Error"); + + if (isValidIss(iss)) { + throw new Error("Authentication Error: invalid iss"); } - if (aud !== "guest" && aud !== "host") { - return new Error("Authentication Error"); + + if (isValidAud(aud)) { + throw new Error("Authentication Error: invalid aud"); } - const audience = aud === "guest" ? "guest" : "host"; - const userInfo = await audienceVerify[audience](sub); + + const userInfo = await audienceVerify[aud](sub); + if (!userInfo) { - return new Error("Authentication Error"); + throw new Error("Authentication Error: invalid userInfo"); } - return userInfo; + + return null; } function authenticate() { @@ -28,10 +37,9 @@ function authenticate() { try { const token = socket.handshake.query.token; const payload = jwt.verify(token, tokenArgs.secret); - const userInfo = await payloadVerify(payload); - if (!userInfo) { - throw Error("Authentication Error"); - } + + await payloadVerify(payload); + return next(); } catch (e) { logger.debug(e); diff --git a/backend/socket_io_server/socketHandler/Poll/closePoll.socketHandler.js b/backend/socket_io_server/socketHandler/Poll/closePoll.socketHandler.js index 98ddb169..7b2112b1 100644 --- a/backend/socket_io_server/socketHandler/Poll/closePoll.socketHandler.js +++ b/backend/socket_io_server/socketHandler/Poll/closePoll.socketHandler.js @@ -1,20 +1,22 @@ import {closePoll} from "../../../DB/queries/poll"; +import logger from "../../logger.js"; const closePollSocketHandler = async (data, emit) => { try { + let status = "ok"; const {pollId} = data; const result = await closePoll(pollId); if (result[0] != 1) { - console.log( + logger.error( `Something wrong with poll/close: affected number of rows = ${result[0]}` ); - return; + status = "error"; } - emit(pollId); + emit({status, pollId}); } catch (e) { - console.error(e); + logger.error(e); emit({status: "error", e}); } }; diff --git a/backend/socket_io_server/socketHandler/Poll/createPoll.socketHandler.js b/backend/socket_io_server/socketHandler/Poll/createPoll.socketHandler.js index 08d41cd7..665b18ba 100644 --- a/backend/socket_io_server/socketHandler/Poll/createPoll.socketHandler.js +++ b/backend/socket_io_server/socketHandler/Poll/createPoll.socketHandler.js @@ -1,4 +1,5 @@ import {createPoll} from "../../../DB/queries/poll"; +import logger from "../../logger.js"; const createPollSocketHandler = async (data, emit) => { try { @@ -11,21 +12,18 @@ const createPollSocketHandler = async (data, emit) => { candidates, } = data; - const state = "standby"; - - const result = await createPoll( + const poll = await createPoll( EventId, pollName, pollType, selectionType, allowDuplication, - state, - candidates, + candidates ); - emit(result); + emit({status: "ok", poll}); } catch (e) { - console.error(e); + logger.error(e); emit({status: "error", e}); } }; diff --git a/backend/socket_io_server/socketHandler/Poll/nofityPollClose.socketHandler.js b/backend/socket_io_server/socketHandler/Poll/nofityPollClose.socketHandler.js index c32f0330..d2c60c5f 100644 --- a/backend/socket_io_server/socketHandler/Poll/nofityPollClose.socketHandler.js +++ b/backend/socket_io_server/socketHandler/Poll/nofityPollClose.socketHandler.js @@ -1,10 +1,12 @@ +import logger from "../../logger.js"; + const notifyPollCloseSocketHandler = async (data, emit) => { try { - const {id} = data; + const {pollId} = data; - emit(id); + emit({status: "ok", pollId}); } catch (e) { - console.error(e); + logger.error(e); emit({status: "error", e}); } }; diff --git a/backend/socket_io_server/socketHandler/Poll/notifyPollOpen.socketHandler.js b/backend/socket_io_server/socketHandler/Poll/notifyPollOpen.socketHandler.js index 3af38287..bf5f1c38 100644 --- a/backend/socket_io_server/socketHandler/Poll/notifyPollOpen.socketHandler.js +++ b/backend/socket_io_server/socketHandler/Poll/notifyPollOpen.socketHandler.js @@ -1,10 +1,12 @@ +import logger from "../../logger.js"; + const notifyPollOpenSocketHandler = async (data, emit) => { try { const {poll} = data; - emit(poll); + emit({status: "ok", poll}); } catch (e) { - console.error(e); + logger.error(e); emit({status: "error", e}); } }; diff --git a/backend/socket_io_server/socketHandler/Poll/openPoll.socketHandler.js b/backend/socket_io_server/socketHandler/Poll/openPoll.socketHandler.js index f8a273e2..387616c1 100644 --- a/backend/socket_io_server/socketHandler/Poll/openPoll.socketHandler.js +++ b/backend/socket_io_server/socketHandler/Poll/openPoll.socketHandler.js @@ -1,20 +1,22 @@ import {openPoll} from "../../../DB/queries/poll"; +import logger from "../../logger.js"; const openPollSocketHandler = async (data, emit) => { try { + let status = "ok"; const {pollId} = data; const result = await openPoll(pollId); if (result[0] != 1) { - console.log( - `Something wrong with poll/open: affected number of rows = ${result[0]}`, + logger.error( + `Something wrong with poll/open: affected number of rows = ${result[0]}` ); - return; + status = "error"; } - emit(pollId); + emit({status, pollId}); } catch (e) { - console.error(e); + logger.error(e); emit({status: "error", e}); } }; diff --git a/backend/socket_io_server/socketHandler/Question/toggleModeration.socketHandler.js b/backend/socket_io_server/socketHandler/Question/toggleModeration.socketHandler.js index 8681f602..89033457 100644 --- a/backend/socket_io_server/socketHandler/Question/toggleModeration.socketHandler.js +++ b/backend/socket_io_server/socketHandler/Question/toggleModeration.socketHandler.js @@ -1,17 +1,21 @@ import {updateEventById} from "../../../DB/queries/event"; import eventCache from "../../EventCache"; +import logger from "../../logger.js"; const toggleModerationSocketHandler = async (data, emit) => { try { const currentState = data.state; const currentOption = await eventCache.get(data.eventId); - await updateEventById(data.eventId, {moderationOption: currentState}); + await updateEventById({ + id: data.eventId, + moderationOption: currentState, + }); currentOption.moderationOption = currentState; await eventCache.set(data.eventId, currentOption); emit({eventId: data.eventId, state: currentState}); } catch (e) { - console.log(e); + logger.error(e); emit({status: "error", e}); } }; diff --git a/backend/socket_io_server/socketHandler/Vote/rateOff.socketHandler.js b/backend/socket_io_server/socketHandler/Vote/rateOff.socketHandler.js index b67eddf4..b1ab0b2e 100644 --- a/backend/socket_io_server/socketHandler/Vote/rateOff.socketHandler.js +++ b/backend/socket_io_server/socketHandler/Vote/rateOff.socketHandler.js @@ -1,20 +1,24 @@ import {deleteVoteBy} from "../../../DB/queries/vote"; +import updateVoters from "./updateVoters"; +import logger from "../../logger.js"; const rateOffSocketHandler = async (data, emit) => { try { const {GuestId, CandidateId, poll, index} = data; - console.log("vote/off handler", data); await deleteVoteBy({GuestId, CandidateId}); + await updateVoters(poll); + emit({ + status: "ok", GuestId, poll, index, }); } catch (e) { - console.error(e); - emit({status: "error(rate/off)", e}); + logger.error(e); + emit({status: "error", e}); } }; diff --git a/backend/socket_io_server/socketHandler/Vote/rateOn.socketHandler.js b/backend/socket_io_server/socketHandler/Vote/rateOn.socketHandler.js index 4daaa9f7..f4b0fa61 100644 --- a/backend/socket_io_server/socketHandler/Vote/rateOn.socketHandler.js +++ b/backend/socket_io_server/socketHandler/Vote/rateOn.socketHandler.js @@ -1,4 +1,6 @@ -import {addVote, deleteVoteBy} from "../../../DB/queries/vote"; +import {addVote} from "../../../DB/queries/vote"; +import updateVoters from "./updateVoters"; +import logger from "../../logger.js"; // const data = { // GuestId: guest.id, @@ -12,14 +14,17 @@ const rateOnSocketHandler = async (data, emit) => { await addVote({GuestId, CandidateId}); + await updateVoters(poll); + emit({ + status: "ok", GuestId, poll, index, }); } catch (e) { - console.error(e); - emit({status: "error(rate/on)", e}); + logger.error(e); + emit({status: "error", e}); } }; diff --git a/backend/socket_io_server/socketHandler/Vote/updateVoters.js b/backend/socket_io_server/socketHandler/Vote/updateVoters.js new file mode 100644 index 00000000..5fcbc0dc --- /dev/null +++ b/backend/socket_io_server/socketHandler/Vote/updateVoters.js @@ -0,0 +1,14 @@ +import {getVotersByCandidateList} from "../../../DB/queries/vote.js"; + +const getCandidateList = items => items.map(n => n.id); + +const updateVoters = async poll => { + // DB에서 갱신된 투표인수를 읽어옴 + for (let candidate of poll.nItems) { + candidate.voters = await getVotersByCandidateList([candidate.id]); + } + const candidateList = getCandidateList(poll.nItems); + poll.totalVoters = await getVotersByCandidateList(candidateList); +}; + +export default updateVoters; diff --git a/backend/socket_io_server/socketHandler/Vote/voteOff.socketHandler.js b/backend/socket_io_server/socketHandler/Vote/voteOff.socketHandler.js index 9277ac5d..aa1160aa 100644 --- a/backend/socket_io_server/socketHandler/Vote/voteOff.socketHandler.js +++ b/backend/socket_io_server/socketHandler/Vote/voteOff.socketHandler.js @@ -1,18 +1,23 @@ import {deleteVoteBy} from "../../../DB/queries/vote"; +import updateVoters from "./updateVoters"; +import logger from "../../logger.js"; const voteOffSocketHandler = async (data, emit) => { try { - const {GuestId, CandidateId, allowDuplication, poll} = data; + const {GuestId, CandidateId, poll} = data; await deleteVoteBy({GuestId, CandidateId}); + await updateVoters(poll); + emit({ + status: "ok", GuestId, poll, }); } catch (e) { - console.error(e); - emit({status: "error(vote/off)", e}); + logger.error(e); + emit({status: "error", e}); } }; diff --git a/backend/socket_io_server/socketHandler/Vote/voteOn.socketHandler.js b/backend/socket_io_server/socketHandler/Vote/voteOn.socketHandler.js index 4b123b8c..2f5158e3 100644 --- a/backend/socket_io_server/socketHandler/Vote/voteOn.socketHandler.js +++ b/backend/socket_io_server/socketHandler/Vote/voteOn.socketHandler.js @@ -1,11 +1,6 @@ -import {addVote, deleteVoteBy, addAndDelete} from "../../../DB/queries/vote"; - -// const data = { -// GuestId: guest.id, -// CandidateId: vote.candidateId, -// allowDuplication: poll.allowDuplication, -// poll: poll, -// }; +import {addVote, addAndDelete} from "../../../DB/queries/vote"; +import updateVoters from "./updateVoters"; +import logger from "../../logger.js"; const voteOnSocketHandler = async (data, emit) => { try { @@ -17,27 +12,22 @@ const voteOnSocketHandler = async (data, emit) => { candidateToDelete, } = data; - // if (!allowDuplication && candidateToDelete) { - // await addAndDelete(GuestId, CandidateId, candidateToDelete); - // } else { - // await addVote({GuestId, CandidateId}); - // } - - await addVote({GuestId, CandidateId}); if (!allowDuplication && candidateToDelete) { - await deleteVoteBy({ - GuestId, - CandidateId: candidateToDelete, - }); + await addAndDelete(GuestId, CandidateId, candidateToDelete); + } else { + await addVote({GuestId, CandidateId}); } + await updateVoters(poll); + emit({ + status: "ok", GuestId, poll, }); } catch (e) { - console.error(e); - emit({status: "error(vote/on)", e}); + logger.error(e); + emit({status: "error", e}); } }; diff --git a/backend/spec/DB/QuestionQuery.test.js b/backend/spec/DB/QuestionQuery.test.js index 75158f08..37d192cc 100644 --- a/backend/spec/DB/QuestionQuery.test.js +++ b/backend/spec/DB/QuestionQuery.test.js @@ -1,4 +1,3 @@ -import {getQuestionLikeCount} from "../../DB/queries/event.js"; import { createQuestion, deleteQuestionById, @@ -45,15 +44,6 @@ describe("questions query api", () => { // console.log(res.length); }); - it("should able to get question likeCount", async () => { - const eventId = 2; - const res = await getQuestionLikeCount(eventId); - - QueryExpectMoreThanOne(res); - // res = res.map(x => x.get({ plain: true })); - // console.log(res.slice(0, 2)); - // console.log(res.length); - }); it("should able to get by event id", async () => { const eventId = 2; diff --git a/frontend/guest-app/package.json b/frontend/guest-app/package.json index 18591eca..9e16093d 100644 --- a/frontend/guest-app/package.json +++ b/frontend/guest-app/package.json @@ -53,6 +53,7 @@ "js-cookie": "^2.2.1", "lodash": "^4.17.15", "mini-css-extract-plugin": "0.8.0", + "moment": "^2.24.0", "optimize-css-assets-webpack-plugin": "5.0.3", "pnp-webpack-plugin": "1.5.0", "postcss-flexbugs-fixes": "4.1.0", diff --git a/frontend/guest-app/public/android-icon-144x144.png b/frontend/guest-app/public/android-icon-144x144.png deleted file mode 100644 index b65cbf87..00000000 Binary files a/frontend/guest-app/public/android-icon-144x144.png and /dev/null differ diff --git a/frontend/guest-app/public/android-icon-192x192.png b/frontend/guest-app/public/android-icon-192x192.png deleted file mode 100644 index 6f1148bd..00000000 Binary files a/frontend/guest-app/public/android-icon-192x192.png and /dev/null differ diff --git a/frontend/guest-app/public/android-icon-36x36.png b/frontend/guest-app/public/android-icon-36x36.png deleted file mode 100644 index fc96f1dd..00000000 Binary files a/frontend/guest-app/public/android-icon-36x36.png and /dev/null differ diff --git a/frontend/guest-app/public/android-icon-48x48.png b/frontend/guest-app/public/android-icon-48x48.png deleted file mode 100644 index 5cdde9da..00000000 Binary files a/frontend/guest-app/public/android-icon-48x48.png and /dev/null differ diff --git a/frontend/guest-app/public/android-icon-72x72.png b/frontend/guest-app/public/android-icon-72x72.png deleted file mode 100644 index 6c2dcd4a..00000000 Binary files a/frontend/guest-app/public/android-icon-72x72.png and /dev/null differ diff --git a/frontend/guest-app/public/android-icon-96x96.png b/frontend/guest-app/public/android-icon-96x96.png deleted file mode 100644 index 0ac97f36..00000000 Binary files a/frontend/guest-app/public/android-icon-96x96.png and /dev/null differ diff --git a/frontend/guest-app/public/apple-icon.png b/frontend/guest-app/public/apple-icon.png deleted file mode 100644 index 9d0064a4..00000000 Binary files a/frontend/guest-app/public/apple-icon.png and /dev/null differ diff --git a/frontend/guest-app/public/favicon-16x16.png b/frontend/guest-app/public/favicon-16x16.png deleted file mode 100644 index c0db580c..00000000 Binary files a/frontend/guest-app/public/favicon-16x16.png and /dev/null differ diff --git a/frontend/guest-app/public/favicon-32x32.png b/frontend/guest-app/public/favicon-32x32.png deleted file mode 100644 index 8b271bda..00000000 Binary files a/frontend/guest-app/public/favicon-32x32.png and /dev/null differ diff --git a/frontend/guest-app/public/favicon-96x96.png b/frontend/guest-app/public/favicon-96x96.png deleted file mode 100644 index 0ac97f36..00000000 Binary files a/frontend/guest-app/public/favicon-96x96.png and /dev/null differ diff --git a/frontend/guest-app/public/logo_vaagle(transparent).png b/frontend/guest-app/public/logo_vaagle(transparent).png deleted file mode 100644 index 45ec93dc..00000000 Binary files a/frontend/guest-app/public/logo_vaagle(transparent).png and /dev/null differ diff --git a/frontend/guest-app/public/manifest.json b/frontend/guest-app/public/manifest.json index b8ac14a7..144fa033 100644 --- a/frontend/guest-app/public/manifest.json +++ b/frontend/guest-app/public/manifest.json @@ -6,48 +6,6 @@ "src": "favicon.ico", "sizes": "96x96 32x32 16x16", "type": "image/x-icon" - }, - { - "src": "\/android-icon-36x36.png", - "sizes": "36x36", - "type": "image\/png", - "density": "0.75" - }, - { - "src": "\/android-icon-48x48.png", - "sizes": "48x48", - "type": "image\/png", - "density": "1.0" - }, - { - "src": "\/android-icon-72x72.png", - "sizes": "72x72", - "type": "image\/png", - "density": "1.5" - }, - { - "src": "\/android-icon-96x96.png", - "sizes": "96x96", - "type": "image\/png", - "density": "2.0" - }, - { - "src": "\/android-icon-144x144.png", - "sizes": "144x144", - "type": "image\/png", - "density": "3.0" - }, - { - "src": "\/android-icon-192x192.png", - "sizes": "192x192", - "type": "image\/png", - "density": "4.0" - }, - { - "src": "\/android-icon-192x192.png", - "sizes": "192x192", - "type": "image\/png", - "density": "4.0" } ], "start_url": ".", diff --git a/frontend/guest-app/src/App/App.css b/frontend/guest-app/src/App/App.css index 57ea2d58..fe065cc8 100644 --- a/frontend/guest-app/src/App/App.css +++ b/frontend/guest-app/src/App/App.css @@ -1,3 +1,8 @@ +/*아이폰*/ +*, *::before, *::after{ + overflow: hidden; +} + .App { text-align: center; } diff --git a/frontend/guest-app/src/App/App.js b/frontend/guest-app/src/App/App.js index 7e21ec5d..5d03f278 100644 --- a/frontend/guest-app/src/App/App.js +++ b/frontend/guest-app/src/App/App.js @@ -1,60 +1,37 @@ import React from "react"; import styled from "styled-components"; -import {useQuery} from "@apollo/react-hooks"; import "./App.css"; -import {GET_GUEST_APP_GLOBAL_DATA} from "../apollo/gqlSchemes.js"; -import TopProgressBar from "../components/TopProcessBar.js"; -import config from "../config"; -import {UIController} from "../components/UIController/UIController.js"; -import {GuestGlobalProvider} from "../libs/guestGlobalContext.js"; import NavBar from "../components/NavBar/NavBar.js"; import TabGroup from "../components/TabGroup/TabGroup.js"; -import { - createSocketIOClient, - SocketIoClientProvider, -} from "../libs/socketIoClientProvider.js"; +import UIControllerProvider from "../contexts/UIController/UIControllerProvider.js"; +import ApolloClientProvider from "../apollo/ApolloClientProvider.js"; +import useGlobalData from "../contexts/GlobalData/useGlobalData.js"; +import GlobalDataProvider from "../contexts/GlobalData/GlobalDataProvider.js"; const AppStyle = styled.div` height: 100vh; width: 100vw; `; -export default function App() { - const {data, loading, error} = useQuery(GET_GUEST_APP_GLOBAL_DATA); - - if (loading) { - return ; - } - - if (error) { - window.location.href = config.inValidGuestRedirectURL; - return
; - } - - const {event, guest} = data.guestInEvent; - const client = createSocketIOClient({ - host: config.socketIOHost, - port: config.socketIOPort, - namespace: "event", - room: event.id, - }); - - client.on("connect", () => { - client.emit("joinRoom", {room: event.id}); - }); - - const globalData = {event, guest}; +const App = () => { + const {event} = useGlobalData(); return ( - - - - - - - - + + + + ); -} +}; + +const WrappedApp = () => ( + + + + + +); + +export default WrappedApp; diff --git a/frontend/guest-app/src/apollo/ApolloClientProvider.js b/frontend/guest-app/src/apollo/ApolloClientProvider.js new file mode 100644 index 00000000..ce68c1ad --- /dev/null +++ b/frontend/guest-app/src/apollo/ApolloClientProvider.js @@ -0,0 +1,19 @@ +import React from "react"; +import Cookies from "js-cookie"; +import {ApolloProvider} from "@apollo/react-hooks"; +import createApolloClient from "./createApolloClient.js"; +import config from "../config"; + +const cookieName = "vaagle-guest"; +const token = Cookies.get(cookieName); +const client = createApolloClient(config.apolloURI, token); + +function ApolloClientProvider(props) { + return ( + + {props.children} + + ); +} + +export default ApolloClientProvider; diff --git a/frontend/guest-app/src/apollo/asembleGetQuestionQuerys.js b/frontend/guest-app/src/apollo/asembleGetQuestionQuerys.js new file mode 100644 index 00000000..ae0c6c90 --- /dev/null +++ b/frontend/guest-app/src/apollo/asembleGetQuestionQuerys.js @@ -0,0 +1,104 @@ +import _ from "lodash"; + +function mappingByKey(object, key) { + const mapped = {}; + + object.forEach(x => { + mapped[x[key]] = x; + }); + + return mapped; +} + +function unMappingByKey(object) { + return Object.values(object); +} + +function JSONNestJoin(parents, children, parentKey, childKey, func) { + const mapped = mappingByKey(parents, parentKey); + + children.forEach(child => { + const joinValue = child[childKey]; + + if (mapped[joinValue]) { + const parentElement = mapped[joinValue]; + + mapped[joinValue] = func(parentElement, child); + } + }); + + return unMappingByKey(mapped); +} + +function JSONNestJoin2(parents, children, parentKey, childKey, func) { + const mapped = mappingByKey(children, childKey); + + parents.forEach(parent => { + const joinValue = parent[parentKey]; + + if (mapped[joinValue]) { + const childElement = mapped[joinValue]; + + // eslint-disable-next-line no-param-reassign + parent = func(parent, childElement); + } + }); + + return parents; +} + +function buildQuestions(object) { + const copyData = _.cloneDeep(object); + const {guests, didILikes} = copyData; + let {questions, emojis, emojiPicks} = copyData; + + questions = JSONNestJoin2(questions, guests, "GuestId", "id", (a, b) => { + a.guestName = b.name; + a.isAnonymous = b.isAnonymous; + + return a; + }); + + questions = questions.map(x => { + x.didILike = false; + return x; + }); + + questions = JSONNestJoin( + questions, + didILikes, + "id", + "QuestionId", + (x, y) => { + x.didILike = true; + return x; + }, + ); + + emojis = emojis.map(x => { + x.key = `${x.QuestionId}_${x.name}`; + x.didIPick = false; + return x; + }); + emojiPicks = emojiPicks.map(x => { + x.key = `${x.QuestionId}_${x.name}`; + return x; + }); + emojis = JSONNestJoin(emojis, emojiPicks, "key", "key", (a, b) => { + a.didIPick = true; + return a; + }); + + questions.map(x => { + x.emojis = []; + return x; + }); + questions = JSONNestJoin(questions, emojis, "id", "QuestionId", (a, b) => { + a.emojis.push(b); + return a; + }); + + return questions; +} + +export default buildQuestions; diff --git a/frontend/guest-app/src/libs/createApolloClient.js b/frontend/guest-app/src/apollo/createApolloClient.js similarity index 68% rename from frontend/guest-app/src/libs/createApolloClient.js rename to frontend/guest-app/src/apollo/createApolloClient.js index 92ec4d3a..21e23446 100644 --- a/frontend/guest-app/src/libs/createApolloClient.js +++ b/frontend/guest-app/src/apollo/createApolloClient.js @@ -3,17 +3,16 @@ import {createHttpLink} from "apollo-link-http"; import {InMemoryCache} from "apollo-cache-inmemory"; import {setContext} from "apollo-link-context"; -export default function creaetApolloClient(uri, token) { +export default function createApolloClient(uri, token) { const httpLink = createHttpLink({uri}); + if (token) { - const authLink = setContext((_, {headers}) => { - return { - headers: { - ...headers, - authorization: `Bearer ${token}`, - }, - }; - }); + const authLink = setContext((_, {headers}) => ({ + headers: { + ...headers, + authorization: `Bearer ${token}`, + }, + })); return new ApolloClient({ link: authLink.concat(httpLink), diff --git a/frontend/guest-app/src/apollo/gqlSchemes.js b/frontend/guest-app/src/apollo/gqlSchemes.js index d56296b9..6837f00a 100644 --- a/frontend/guest-app/src/apollo/gqlSchemes.js +++ b/frontend/guest-app/src/apollo/gqlSchemes.js @@ -1,22 +1,81 @@ import {gql} from "apollo-boost"; export const GET_GUEST_APP_GLOBAL_DATA = gql` - query { - guestInEvent { - event { - id - eventCode - startAt - endAt - eventName - HostId - } - guest { - id - name - email - company - } - } - } + query { + guestInEvent { + event { + id + eventCode + startAt + endAt + eventName + HostId + } + guest { + id + name + email + company + } + } + } +`; + +export const QUERY_INIT_QUESTIONS = gql` + query getQuestions($EventId: ID!, $GuestId: ID!) { + questions(EventId: $EventId) { + id + EventId + GuestId + createdAt + content + state + isStared + likeCount + QuestionId + } + emojis(EventId: $EventId) { + name + count + QuestionId + createdAt + } + emojiPicks(EventId: $EventId, GuestId: $GuestId) { + name + QuestionId + } + guests(EventId: $EventId) { + id + name + isAnonymous + } + didILikes(GuestId: $GuestId) { + QuestionId + } + } +`; + +export const POLL_QUERY = gql` + query PollGuest($EventId: ID!, $guestId: ID!) { + pollGuest(EventId: $EventId, guestId: $guestId) { + id + pollName + pollType + selectionType + allowDuplication + state + totalVoters + pollDate + rated + ratingValue + nItems { + id + number + content + voters + voted + firstPlace + } + } + } `; diff --git a/frontend/guest-app/src/components/Emoji/EmojiPickerModal.js b/frontend/guest-app/src/components/Emoji/EmojiPickerModal.js deleted file mode 100644 index 85b7045f..00000000 --- a/frontend/guest-app/src/components/Emoji/EmojiPickerModal.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from "react"; -import {Modal} from "@material-ui/core"; -import "emoji-mart/css/emoji-mart.css"; -import {Picker} from "emoji-mart"; -import customEmojis from "./CustomEmojis"; - -function EmojiPickerModal({onClose, onSelect}) { - return ( - - - - ); -} - -export default EmojiPickerModal; diff --git a/frontend/guest-app/src/components/Emoji/CustomEmojis.js b/frontend/guest-app/src/components/EmojiArea/CustomEmojis.js similarity index 100% rename from frontend/guest-app/src/components/Emoji/CustomEmojis.js rename to frontend/guest-app/src/components/EmojiArea/CustomEmojis.js diff --git a/frontend/guest-app/src/components/Emoji/EmojiArea.js b/frontend/guest-app/src/components/EmojiArea/EmojiArea.js similarity index 64% rename from frontend/guest-app/src/components/Emoji/EmojiArea.js rename to frontend/guest-app/src/components/EmojiArea/EmojiArea.js index 67208d11..c7a1d77d 100644 --- a/frontend/guest-app/src/components/Emoji/EmojiArea.js +++ b/frontend/guest-app/src/components/EmojiArea/EmojiArea.js @@ -1,12 +1,11 @@ -import React, {useContext} from "react"; +import React from "react"; import styled from "styled-components"; -import IconButton from "@material-ui/core/IconButton"; -import InsertEmoticonOutlinedIcon from "@material-ui/icons/InsertEmoticonOutlined"; -import EmojiInstance from "./EmojiInstance"; +import EmojiBadge from "./EmojiBadge.js"; import EmojiPickerModal from "./EmojiPickerModal"; import useCommonModal from "../CommonComponent/CommonModal/useCommonModal"; -import {socketClient} from "../../libs/socketIoClientProvider.js"; -import {GuestGlobalContext} from "../../libs/guestGlobalContext.js"; +import {socketClient} from "../../socket.io"; +import EmojiInsertButton from "./EmojiInsertButton.js"; +import useGlobalData from "../../contexts/GlobalData/useGlobalData.js"; const RowWrapper = styled.div` display: flex; @@ -22,16 +21,6 @@ const RowWrapper = styled.div` } `; -function EmojiInsertButton(props) { - const {onClick} = props; - - return ( - - - - ); -} - const unPickEmoji = (emojis, name, guestGlobal, QuestionId) => { const {event, guest} = guestGlobal; @@ -70,15 +59,19 @@ const pickEmoji = (emojis, name, guestGlobal, QuestionId) => { }); }; +function isIncludeSameEmoji(emojis, name) { + return emojis.filter(x => x.name === name).length > 0; +} + function EmojiArea(props) { const {emojis, id: QuestionId} = props; - const guestGlobal = useContext(GuestGlobalContext); + const guestGlobal = useGlobalData(); const emojiPickerModal = useCommonModal(); const onEmojiInstanceClick = (name, didIPick) => { - didIPick ? - unPickEmoji(emojis, name, guestGlobal, QuestionId) : - pickEmoji(emojis, name, guestGlobal, QuestionId); + didIPick + ? unPickEmoji(emojis, name, guestGlobal, QuestionId) + : pickEmoji(emojis, name, guestGlobal, QuestionId); }; const onSelectOfEmojiPicker = value => { @@ -91,7 +84,7 @@ function EmojiArea(props) { EventId: event.id, }; - if (emojis.filter(x => x.name === name).length) { + if (isIncludeSameEmoji(emojis, name)) { return; } @@ -102,15 +95,18 @@ function EmojiArea(props) { return ( {emojis.map((emj, index) => ( - + ))} - {emojiPickerModal.isOpened && ( - - )} + ); } diff --git a/frontend/guest-app/src/components/Emoji/EmojiInstance.js b/frontend/guest-app/src/components/EmojiArea/EmojiBadge.js similarity index 92% rename from frontend/guest-app/src/components/Emoji/EmojiInstance.js rename to frontend/guest-app/src/components/EmojiArea/EmojiBadge.js index e9fb023c..da16f378 100644 --- a/frontend/guest-app/src/components/Emoji/EmojiInstance.js +++ b/frontend/guest-app/src/components/EmojiArea/EmojiBadge.js @@ -24,7 +24,7 @@ const EmojiInstanceStyle = styled.div` } `; -function EmojiInstance(props) { +function EmojiBadge(props) { const {name, count, didIPick, onClick} = props; return ( @@ -35,4 +35,4 @@ function EmojiInstance(props) { ); } -export default EmojiInstance; +export default EmojiBadge; diff --git a/frontend/guest-app/src/components/EmojiArea/EmojiInsertButton.js b/frontend/guest-app/src/components/EmojiArea/EmojiInsertButton.js new file mode 100644 index 00000000..e33b18a4 --- /dev/null +++ b/frontend/guest-app/src/components/EmojiArea/EmojiInsertButton.js @@ -0,0 +1,15 @@ +import IconButton from "@material-ui/core/IconButton"; +import InsertEmoticonOutlinedIcon from "@material-ui/icons/InsertEmoticonOutlined.js"; +import React from "react"; + +function EmojiInsertButton(props) { + const {onClick} = props; + + return ( + + + + ); +} + +export default EmojiInsertButton; diff --git a/frontend/guest-app/src/components/EmojiArea/EmojiPickerModal.js b/frontend/guest-app/src/components/EmojiArea/EmojiPickerModal.js new file mode 100644 index 00000000..9a78d7eb --- /dev/null +++ b/frontend/guest-app/src/components/EmojiArea/EmojiPickerModal.js @@ -0,0 +1,19 @@ +import React from "react"; +import {Modal} from "@material-ui/core"; +import "emoji-mart/css/emoji-mart.css"; +import {Picker} from "emoji-mart"; +import customEmojis from "./CustomEmojis"; + +function EmojiPickerModal({open, onClose, onSelect}) { + return + + ; +} + + +export default EmojiPickerModal; diff --git a/frontend/guest-app/src/components/ErrorPage/ErrorPage.js b/frontend/guest-app/src/components/ErrorPage/ErrorPage.js deleted file mode 100644 index 057df528..00000000 --- a/frontend/guest-app/src/components/ErrorPage/ErrorPage.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from "react"; - -export default function ErrorPage() { - return ( -
- { -
- ); -} diff --git a/frontend/guest-app/src/components/LikeButton/LikeButton.js b/frontend/guest-app/src/components/LikeButton/LikeButton.js index c4b70fd1..4cef254d 100644 --- a/frontend/guest-app/src/components/LikeButton/LikeButton.js +++ b/frontend/guest-app/src/components/LikeButton/LikeButton.js @@ -1,10 +1,10 @@ -import React, {useContext} from "react"; +import React from "react"; import Button from "@material-ui/core/Button"; import ThumbUpIcon from "@material-ui/icons/ThumbUp"; import useCommonModal from "../CommonComponent/CommonModal/useCommonModal.js"; -import UndoLikeConfirmModal from "./UndoLikeModal.js"; -import {socketClient} from "../../libs/socketIoClientProvider.js"; -import {GuestGlobalContext} from "../../libs/guestGlobalContext.js"; +import UndoLikeConfirmModal from "./LikeUndoModal.js"; +import {socketClient} from "../../socket.io"; +import useGlobalData from "../../contexts/GlobalData/useGlobalData.js"; function LikeButtonAtom({isLikeClicked, onLikeButtonClicked, likeCount}) { return ( @@ -18,7 +18,7 @@ function LikeButtonAtom({isLikeClicked, onLikeButtonClicked, likeCount}) { justifyContent: "space-between", }} > - + {likeCount} ); @@ -40,7 +40,7 @@ function emitQuestionLikeRemove(GuestId, QuestionId) { function LikeButton(props) { const {likeCount, didILike, id} = props; - const {guest} = useContext(GuestGlobalContext); + const {guest} = useGlobalData(); const modalState = useCommonModal(); const onLikeButtonClicked = () => { if (didILike) { diff --git a/frontend/guest-app/src/components/LikeButton/UndoLikeModal.js b/frontend/guest-app/src/components/LikeButton/LikeUndoModal.js similarity index 73% rename from frontend/guest-app/src/components/LikeButton/UndoLikeModal.js rename to frontend/guest-app/src/components/LikeButton/LikeUndoModal.js index da45d4d5..4b82e82f 100644 --- a/frontend/guest-app/src/components/LikeButton/UndoLikeModal.js +++ b/frontend/guest-app/src/components/LikeButton/LikeUndoModal.js @@ -3,14 +3,14 @@ import Grid from "@material-ui/core/Grid"; import Typography from "@material-ui/core/Typography"; import {Box} from "@material-ui/core"; import CommonModal from "../CommonComponent/CommonModal/CommonModal.js"; -import ConfirmButton from "../CommonComponent/CommonButtons/ConfirmButton.js"; -import CancelButton from "../CommonComponent/CommonButtons/CancelButton.js"; +import ConfirmButton from "../atoms/ConfirmButton.js"; +import CancelButton from "../atoms/CancelButton.js"; function UndoLikeConfirmModal({isOpened, onCancelClick, onConfirmClick}) { return ( - 좋아하기를 취소하기겠습니까? + 좋아하기를 취소하시겠습니까? diff --git a/frontend/guest-app/src/components/LikeButton/useLikeButton.js b/frontend/guest-app/src/components/LikeButton/useLikeButton.js deleted file mode 100644 index 38f9486d..00000000 --- a/frontend/guest-app/src/components/LikeButton/useLikeButton.js +++ /dev/null @@ -1,18 +0,0 @@ -import {useState} from "react"; - -function useLikeButton(initialState = {isLikeClicked: false, likeCount: 0}) { - const [likeState, setLikeState] = useState(initialState); - - const onLike = () => - setLikeState({likeCount: likeState.likeCount + 1, isLikeClicked: true}); - - const onUndoLike = () => - setLikeState({ - likeCount: likeState.likeCount - 1, - isLikeClicked: false, - }); - - return {...likeState, onLike, onUndoLike}; -} - -export default useLikeButton; diff --git a/frontend/guest-app/src/components/Modals/EditProfileModal.js b/frontend/guest-app/src/components/Modals/EditProfileModal.js deleted file mode 100644 index 4fadf58e..00000000 --- a/frontend/guest-app/src/components/Modals/EditProfileModal.js +++ /dev/null @@ -1,85 +0,0 @@ -import React from "react"; -import {Grid, Typography} from "@material-ui/core"; -import Button from "@material-ui/core/Button"; -import BusinessIcon from "@material-ui/icons/Business"; -import EmailIcon from "@material-ui/icons/Email"; -import PersonIcon from "@material-ui/icons/Person"; -import UserAvatar from "../UserAvatar/UserAvatar.js"; -import CommonModal from "../CommonComponent/CommonModal/CommonModal.js"; -import CommonTextInput from "../CommonComponent/CommonTextInput/CommonTextInput.js"; -import useCommonTextInput from "../CommonComponent/CommonTextInput/useCommonTextInput.js"; -import useStringState from "../UserAvatar/useStringState.js"; - -function UserNameInput() { - const {state, setState} = useStringState(); - - const onUserNameChange = e => { - setState(e.target.value); - }; - - return ( - - - - } - label={"이름"} - value={state} - onChange={onUserNameChange} - /> - - ); -} - -function CompanyInput({company = ""}) { - const textInputState = useCommonTextInput(company); - - return ( - } - label={"회사"} - value={textInputState.value} - onChange={textInputState.onChange} - /> - ); -} - -function EmailInput({email = ""}) { - const textInputState = useCommonTextInput(email); - - return ( - } - label={"이메일"} - value={textInputState.value} - onChange={textInputState.onChange} - /> - ); -} - -function SaveButton({onSave}) { - return ( - - ); -} - -function EditProfileModal({isOpened = false, onCancelClick, onSave}) { - return ( - - - 내 프로필 변경 - - - - - - - - - - ); -} - -export default EditProfileModal; diff --git a/frontend/guest-app/src/components/Modals/MyQuestionModal.js b/frontend/guest-app/src/components/Modals/MyQuestionModal.js deleted file mode 100644 index 87fc8c93..00000000 --- a/frontend/guest-app/src/components/Modals/MyQuestionModal.js +++ /dev/null @@ -1,33 +0,0 @@ -import React, {useState} from "react"; -import PropTypes from "prop-types"; -import {Scrollbars} from "react-custom-scrollbars"; -import {Typography} from "@material-ui/core"; -import CommonModal from "../CommonComponent/CommonModal/CommonModal.js"; -import DummyData from "../Question/QuestionDummyData.js"; -import QuestionCardList from "../Question/QuestionCard/QuestionCardList.js"; - -function MyQuestionModal(props) { - const {isOpened, onCancelClick} = props; - const [datas] = useState({questions: DummyData()}); - - return ( - - 내 질문들 - - - - - ); -} - -MyQuestionModal.propTypes = { - isOpened: PropTypes.bool, - onCancel: PropTypes.func, -}; - -MyQuestionModal.defualtProps = { - isOpened: false, - onCancel: undefined, -}; - -export default MyQuestionModal; diff --git a/frontend/guest-app/src/components/Poll/ActiveRating.js b/frontend/guest-app/src/components/Poll/ActiveRating.js index 6deda330..143ef141 100644 --- a/frontend/guest-app/src/components/Poll/ActiveRating.js +++ b/frontend/guest-app/src/components/Poll/ActiveRating.js @@ -37,13 +37,14 @@ function ActiveRating({ onCancelRating, }) { return ( - + {!rated && "별 갯수로 평가해주세요"} {rated && `투표했음: ${ratingValue}점`} - (props.firstPlace ? "yellow" : "#868e96")}; /* Gray6 */ + (props.firstPlace ? "yellow" : "#adb5bd")}; /* Gray5 */ height: 100%; width: ${props => props.ratio}; box-sizing: border-box; diff --git a/frontend/guest-app/src/components/Poll/PollApollo.js b/frontend/guest-app/src/components/Poll/PollApollo.js deleted file mode 100644 index 7f3f4093..00000000 --- a/frontend/guest-app/src/components/Poll/PollApollo.js +++ /dev/null @@ -1,48 +0,0 @@ -import React, {useContext} from "react"; -import {useQuery} from "@apollo/react-hooks"; -import {gql} from "apollo-boost"; -import PollContainer from "./PollContainer"; -import {GuestGlobalContext} from "../../libs/guestGlobalContext.js"; - -const POLL_QUERY = gql` - query PollGuest($EventId: ID!, $guestId: ID!) { - pollGuest(EventId: $EventId, guestId: $guestId) { - id - pollName - pollType - selectionType - allowDuplication - state - totalVoters - pollDate - rated - ratingValue - nItems { - id - number - content - voters - voted - firstPlace - } - } - } -`; - -function PollApollo() { - const {event, guest} = useContext(GuestGlobalContext); - const options = { - variables: { - EventId: event.id, - guestId: guest.id, - }, - }; - const {loading, error, data} = useQuery(POLL_QUERY, options); - - if (loading) return

Loading...

; - if (error) return

Error :(

; - - return ; -} - -export default PollApollo; diff --git a/frontend/guest-app/src/components/Poll/PollCard.js b/frontend/guest-app/src/components/Poll/PollCard.js index 9ada20b6..1aa1d185 100644 --- a/frontend/guest-app/src/components/Poll/PollCard.js +++ b/frontend/guest-app/src/components/Poll/PollCard.js @@ -10,12 +10,10 @@ const ColumnWrapper = styled.div` align-items: center; justify-content: flex-start; box-sizing: border-box; - border: 1px solid #adb5bd; /* Gray5 */ - // box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.25); + border: 1px solid #dee2e6; /* Gray3 */ width: 100%; - & + & { - margin-top: 1rem; - } + margin-top: 1rem; + background-color: white; `; const RowWrapper = styled.div` @@ -50,13 +48,14 @@ function PollCard(props) { } = props; let localePollDate; + // socket.io, sequelize, graphQL 을 거치면서 format이 변경되어서 그때그때 처리하기 위함 if (pollDate) { localePollDate = pollDate; if (localePollDate.includes("-")) { localePollDate = new Date(localePollDate); } else { - localePollDate = new Date(parseInt(localePollDate)); + localePollDate = new Date(parseInt(localePollDate, 10)); } localePollDate = ` ${localePollDate.getMonth() + 1}월 @@ -89,7 +88,7 @@ function PollCard(props) { {pollType === "rating" && } - {`${parseInt(totalVoters).toLocaleString()} 명 참여`} + {`${parseInt(totalVoters, 10).toLocaleString()} 명 참여`}
); diff --git a/frontend/guest-app/src/components/Poll/PollContainer.js b/frontend/guest-app/src/components/Poll/PollContainer.js index 57faa5f7..2d970415 100644 --- a/frontend/guest-app/src/components/Poll/PollContainer.js +++ b/frontend/guest-app/src/components/Poll/PollContainer.js @@ -1,8 +1,10 @@ import React, {useReducer, useState} from "react"; import styled from "styled-components"; import PollCard from "./PollCard"; -import {useSocket} from "../../libs/socketIoClientProvider.js"; -import reducer from "./PollReducer"; +import {useSocket} from "../../socket.io"; +import reducer from "../../reducers/PollsReducer.js"; +import useGlobalData from "../../contexts/GlobalData/useGlobalData.js"; +import usePolls from "../../contexts/Polls/usePolls.js"; const ColumnWrapper = styled.div` display: flex; @@ -15,152 +17,164 @@ const ColumnWrapper = styled.div` width: 100%; `; -function PollContainer({data, GuestId}) { - let activePollData = null; - let clPollData = null; +function PollContainer() { + const {guest} = useGlobalData(); + const GuestId = guest.id; + + const {pollGuest} = usePolls(); + const data = pollGuest; + + let rPolls = null; + let cPolls = null; if (data) { - const initialPollData = data; + const initialPolls = data; - activePollData = initialPollData.filter( - poll => poll.state === "running", - ); - clPollData = initialPollData.filter(poll => poll.state === "closed"); + rPolls = initialPolls.filter(poll => poll.state === "running"); + cPolls = initialPolls.filter(poll => poll.state === "closed"); } - const [pollData, dispatch] = useReducer(reducer, activePollData); - const [closedPollData, setClosedPollData] = useState(clPollData); + const [runningPolls, dispatch] = useReducer(reducer, rPolls); + const [closedPolls, setClosedPolls] = useState(cPolls); + // guest가 N지선다형 투표를 했을때 호출되는 handler const onVote = (id, candidateId, number, state) => { if (state !== "running") return; dispatch({ type: "VOTE", - id, + pollId: id, candidateId, number, GuestId, }); }; + // guest가 별점매기기 투표를 했을때 호출되는 handler const onChange = (value, state, id) => { if (state !== "running") return; dispatch({ type: "RATE", value, - id, + pollId: id, GuestId, }); }; + // guest가 별점매기기 투표를 취소했을때 호출되는 handler const onCancelRating = (id, state) => { if (state !== "running") return; dispatch({ type: "CANCEL_RATING", - id, + pollId: id, GuestId, }); }; - useSocket("poll/notify_open", thePoll => { - if (thePoll.error) { + // host가 투표를 open했음을 guest들에게 socket.io 서버로 emit 하면 + // guest들은 아래 함수를 통해 listen하고 있다가 + // useReduer를 호출하여 투표에 상태를 update 함 + useSocket("poll/notify_open", res => { + if (res.status === "error") { return; } - // console.log("Guest received poll/notify_open", thePoll); + dispatch({ type: "NOTIFY_OPEN", - poll: thePoll, + poll: res.poll, GuestId, }); }); - useSocket("poll/notify_close", id => { - if (id.error) { + // host가 투표를 close했음을 guest들에게 socket.io 서버로 emit 하면 + // guest들은 아래 함수를 통해 listen하고 있다가 + // useReduer를 호출하여 투표에 상태를 update 함 + useSocket("poll/notify_close", res => { + if (res.status === "error") { return; } - // console.log("Guest received poll/notify_close", id); - const thePoll = pollData.filter(poll => poll.id === id)[0]; + + const {pollId} = res; + const thePoll = {...runningPolls.find(poll => poll.id === pollId)}; thePoll.state = "closed"; - setClosedPollData([thePoll].concat(closedPollData)); + setClosedPolls([thePoll].concat(closedPolls)); dispatch({ type: "NOTIFY_CLOSE", - id, + pollId, }); }); + // 다른 guest들이 투표했음을 socket.io 서버로 emit 하면 + // guest는 아래 함수를 통해 listen하고 있다가 + // useReduer를 호출하여 투표에 상태를 update 함 useSocket("vote/on", res => { - if (res.error) { + if (res.status === "error") { + // eslint-disable-next-line no-console + console.error("vote/on ERROR"); return; } // 하나의 브라우저에서 여러개의 tab으로 guest들을 생성한 경우, // 해당 guest를 제외한 나머지 guest에 상태가 적용되지 않아서 comment 처리했음 - // console.log("useSocket vote/on", res); - // if (res.GuestId === GuestId) { - // console.log("My vote!"); - // // return; - // } dispatch({ type: "SOMEONE_VOTE", - id: res.poll.id, + pollId: res.poll.id, poll: res.poll, GuestId, }); }); + // 다른 guest들이 투표했음을 socket.io 서버로 emit 하면 + // guest는 아래 함수를 통해 listen하고 있다가 + // useReduer를 호출하여 투표에 상태를 update 함 useSocket("vote/off", res => { - if (res.error) { + if (res.status === "error") { + // eslint-disable-next-line no-console + console.error("vote/off ERROR"); return; } // 하나의 브라우저에서 여러개의 tab으로 guest들을 생성한 경우, // 해당 guest를 제외한 나머지 guest에 상태가 적용되지 않아서 comment 처리했음 - // if (res.GuestId === GuestId) { - // console.log("My vote!"); - // // return; - // } dispatch({ type: "SOMEONE_VOTE", - id: res.poll.id, + pollId: res.poll.id, poll: res.poll, GuestId, }); }); + // 다른 guest들이 별점매기기 했음을 socket.io 서버로 emit 하면 + // guest는 아래 함수를 통해 listen하고 있다가 + // useReduer를 호출하여 투표에 상태를 update 함 useSocket("rate/on", res => { - if (res.error) { + if (res.status === "error") { return; } // 하나의 브라우저에서 여러개의 tab으로 guest들을 생성한 경우, // 해당 guest를 제외한 나머지 guest에 상태가 적용되지 않아서 comment 처리했음 - // console.log("useSocket vote/on", res); - // if (res.GuestId === GuestId) { - // console.log("My rate!"); - // // return; - // } dispatch({ type: "SOMEONE_RATE", - id: res.poll.id, + pollId: res.poll.id, poll: res.poll, GuestId, }); }); + // 다른 guest들이 별점매기기를 취소했음을 socket.io 서버로 emit 하면 + // guest는 아래 함수를 통해 listen하고 있다가 + // useReduer를 호출하여 투표에 상태를 update 함 useSocket("rate/off", res => { - if (res.error) { + if (res.status === "error") { return; } // 하나의 브라우저에서 여러개의 tab으로 guest들을 생성한 경우, // 해당 guest를 제외한 나머지 guest에 상태가 적용되지 않아서 comment 처리했음 - // if (res.GuestId === GuestId) { - // console.log("My rate!"); - // // return; - // } dispatch({ type: "SOMEONE_RATE", - id: res.poll.id, + pollId: res.poll.id, poll: res.poll, GuestId, }); @@ -168,8 +182,8 @@ function PollContainer({data, GuestId}) { return ( - {pollData && - pollData.map(poll => ( + {runningPolls && + runningPolls.map(poll => ( ))} - {closedPollData && - closedPollData.map(poll => ( + {closedPolls && + closedPolls.map(poll => ( ))} diff --git a/frontend/guest-app/src/components/Poll/PollDummyData.js b/frontend/guest-app/src/components/Poll/PollDummyData.js deleted file mode 100644 index 2f7c59db..00000000 --- a/frontend/guest-app/src/components/Poll/PollDummyData.js +++ /dev/null @@ -1,85 +0,0 @@ -export default function PollDummyData() { - return [ - { - pollName: "2019년 송년회 일자 투표", - pollType: "nItems", - selectionType: "date", - allowDuplication: true, - nItems: [ - { - id: 0, - name: "12월 19일(목)", - voters: 55, - voted: true, - firstPlace: false, - }, { - id: 1, - name: "12월 20일(금)", - voters: 77, - voted: true, - firstPlace: true, - }, { - id: 2, - name: "12월 21일(토)", - voters: 33, - voted: false, - firstPlace: false, - }, { - id: 3, - name: "12월 22일(일)", - voters: 22, - voted: false, - firstPlace: false, - }, - ], - pollDate: new Date(), - active: true, - totalVoters: 99, - }, { - pollName: "대한민국 수도는 어디인가요?", - pollType: "nItems", - selectionType: "text", - allowDuplication: false, - nItems: [ - { - id: 0, - name: "부산", - voters: 11, - voted: false, - firstPlace: false, - }, { - id: 1, - name: "제주", - voters: 22, - voted: false, - firstPlace: false, - }, { - id: 2, - name: "서울", - voters: 999, - voted: true, - firstPlace: true, - }, - ], - pollDate: new Date(), - active: false, - totalVoters: 1032, - }, { - pollName: "마스터 강의 평가", - pollType: "rating", - allowDuplication: false, - nItems: [ - { - id: 0, - voters: 80, - voted: false, - maxStars: 10, - value: 0, - }, - ], - pollDate: new Date(), - active: false, - totalVoters: 80, - }, - ]; -} diff --git a/frontend/guest-app/src/components/Poll/RatingItem.js b/frontend/guest-app/src/components/Poll/RatingItem.js index 4d741b8c..a0ea2cd2 100644 --- a/frontend/guest-app/src/components/Poll/RatingItem.js +++ b/frontend/guest-app/src/components/Poll/RatingItem.js @@ -31,7 +31,7 @@ function RatingItem({ id={id} rated={rated} ratingValue={ratingValue} - max={parseInt(selectionType)} + max={parseInt(selectionType, 10)} state={state} onChange={onChange} onCancelRating={onCancelRating} @@ -44,7 +44,7 @@ function RatingItem({ )} diff --git a/frontend/guest-app/src/components/Question/QuestionDummyData.js b/frontend/guest-app/src/components/Question/QuestionDummyData.js deleted file mode 100644 index bcf4bfa8..00000000 --- a/frontend/guest-app/src/components/Question/QuestionDummyData.js +++ /dev/null @@ -1,96 +0,0 @@ -function DummyData() { - return [ - { - userName: "오랑캐1", - date: new Date(), - content: "content", - isAnonymous: false, - isShowEditButton: true, - isLike: false, - likeCount: 10, - }, { - userName: "오랑캐2", - date: new Date(), - content: "long content", - isAnonymous: false, - isShowEditButton: false, - isLike: true, - likeCount: 11, - }, { - userName: "오랑캐3", - date: new Date(), - content: - "long content long content long content long content long contentlong contentlong content long contentlong content", - isAnonymous: false, - isShowEditButton: true, - isLike: false, - likeCount: 100, - }, { - userName: "오랑캐3", - date: new Date(), - content: "content", - isAnonymous: true, - isShowEditButton: false, - isLike: false, - likeCount: 12, - }, { - userName: "Anonymous", - date: new Date(), - content: "content", - isAnonymous: true, - isShowEditButton: false, - isLike: false, - likeCount: 1, - }, { - userName: "Anonymous", - date: new Date(), - content: "content", - isAnonymous: true, - isShowEditButton: true, - isLike: false, - likeCount: 0, - }, { - userName: "Anonymous", - date: new Date(), - content: "content", - isAnonymous: true, - isShowEditButton: false, - isLike: false, - likeCount: 1, - }, { - userName: "Anonymous", - date: new Date(), - content: "content", - isAnonymous: true, - isShowEditButton: false, - isLike: false, - likeCount: 53, - }, { - userName: "Anonymous", - date: new Date(), - content: "content", - isAnonymous: true, - isShowEditButton: false, - isLike: false, - likeCount: 10, - }, { - userName: "Anonymous", - date: new Date(), - content: "content", - isAnonymous: true, - isShowEditButton: false, - isLike: false, - likeCount: 4, - }, { - userName: "Anonymous", - date: new Date(), - content: "content", - isAnonymous: true, - isShowEditButton: false, - isLike: false, - likeCount: 10, - }, - ]; -} - -export default DummyData; diff --git a/frontend/guest-app/src/components/Question/QuestionInputArea/UserInfoInput.js b/frontend/guest-app/src/components/Question/QuestionInputArea/UserInfoInput.js deleted file mode 100644 index f4760a21..00000000 --- a/frontend/guest-app/src/components/Question/QuestionInputArea/UserInfoInput.js +++ /dev/null @@ -1,31 +0,0 @@ -import PropTypes from "prop-types"; -import React from "react"; -import useStringState from "../../UserAvatar/useStringState.js"; -import UserAvatar from "../../UserAvatar/UserAvatar.js"; -import QuestionUserNameInput from "./QuestionUserNameInput.js"; - -function UserInfoInput(props) { - const {userNameRef, initialUserName = ""} = props; - const {state, setState} = useStringState(initialUserName); - - const onUserNameChange = e => { - setState(e.target.value); - }; - - return ( - <> - - - - ); -} - -UserInfoInput.propTypes = { - userNameRef: PropTypes.any, -}; - -export default UserInfoInput; diff --git a/frontend/guest-app/src/components/Question/ToggleReducer.js b/frontend/guest-app/src/components/Question/ToggleReducer.js deleted file mode 100644 index 0a45541e..00000000 --- a/frontend/guest-app/src/components/Question/ToggleReducer.js +++ /dev/null @@ -1,41 +0,0 @@ -import _ from "lodash"; - -const ToggleReducer = (state, action) => { - const {type, data} = action; - - const actionTable = { - toggle: (state, data) => { - const newState = _.cloneDeep(state); - - newState.data = _.cloneDeep(data); - newState.state = !newState.state; - - return newState; - }, - on: (state, data) => { - const newState = _.cloneDeep(state); - - newState.data = _.cloneDeep(data); - newState.state = true; - - return newState; - }, - off: (state, data) => { - const newState = _.cloneDeep(state); - - newState.data = _.cloneDeep(data); - newState.state = false; - - return newState; - }, - }; - - if (!(type in actionTable)) { - console.error(`unexpected action.type: ${type}`); - return state; - } - - return actionTable[type](state, data); -}; - -export default ToggleReducer; diff --git a/frontend/guest-app/src/components/Question/QuestionCard/QuestionCard.js b/frontend/guest-app/src/components/QuestionCard/QuestionCard.js similarity index 84% rename from frontend/guest-app/src/components/Question/QuestionCard/QuestionCard.js rename to frontend/guest-app/src/components/QuestionCard/QuestionCard.js index 4b4e1c93..ba89d74c 100644 --- a/frontend/guest-app/src/components/Question/QuestionCard/QuestionCard.js +++ b/frontend/guest-app/src/components/QuestionCard/QuestionCard.js @@ -4,8 +4,8 @@ import {CardContent} from "@material-ui/core"; import Divider from "@material-ui/core/Divider"; import QuestionHeader from "./QuestionCardHeader.js"; import QuestionBody from "./QuestionCardBody.js"; -import EmojiArea from "../../Emoji/EmojiArea.js"; -import ReplyArea from "../../Reply/ReplyArea"; +import EmojiArea from "../EmojiArea/EmojiArea.js"; +import ReplyPreviewArea from "../ReplyPreviewArea/ReplyPreviewArea.js"; const cardColor = { focused: "rgb(242,248,255)", @@ -24,7 +24,7 @@ const QuestionCard = React.memo(props => { /> - + ); diff --git a/frontend/guest-app/src/components/Question/QuestionCard/QuestionCardBody.js b/frontend/guest-app/src/components/QuestionCard/QuestionCardBody.js similarity index 55% rename from frontend/guest-app/src/components/Question/QuestionCard/QuestionCardBody.js rename to frontend/guest-app/src/components/QuestionCard/QuestionCardBody.js index c52f7ba2..d829075b 100644 --- a/frontend/guest-app/src/components/Question/QuestionCard/QuestionCardBody.js +++ b/frontend/guest-app/src/components/QuestionCard/QuestionCardBody.js @@ -1,18 +1,21 @@ -import React, {useContext} from "react"; +import React from "react"; import PropTypes from "prop-types"; +import Typography from "@material-ui/core/Typography"; import QuestionEditButton from "./QuestionCardEditButton.js"; -import {GuestGlobalContext} from "../../../libs/guestGlobalContext.js"; +import useGlobalData from "../../contexts/GlobalData/useGlobalData.js"; function QuestionBody(props) { - const {guest} = useContext(GuestGlobalContext); + const {guest} = useGlobalData(); const {content, GuestId} = props; const isMyQuestion = guest.id === GuestId; return ( - {content} - {isMyQuestion && } + + {content} + {isMyQuestion && } + ); } diff --git a/frontend/guest-app/src/components/Question/QuestionCard/QuestionCardEditButton.js b/frontend/guest-app/src/components/QuestionCard/QuestionCardEditButton.js similarity index 83% rename from frontend/guest-app/src/components/Question/QuestionCard/QuestionCardEditButton.js rename to frontend/guest-app/src/components/QuestionCard/QuestionCardEditButton.js index 40661528..1254389f 100644 --- a/frontend/guest-app/src/components/Question/QuestionCard/QuestionCardEditButton.js +++ b/frontend/guest-app/src/components/QuestionCard/QuestionCardEditButton.js @@ -2,7 +2,7 @@ import React from "react"; import styled from "styled-components"; import {Typography} from "@material-ui/core"; import MoreHorizIcon from "@material-ui/icons/MoreHoriz.js"; -import {useUIControllerContext} from "../../UIController/UIController.js"; +import useUIController from "../../contexts/UIController/useUIController.js"; const QuestionEditButtonStyle = styled.div` float: right; @@ -12,7 +12,7 @@ const QuestionEditButtonStyle = styled.div` function QuestionEditButton(props) { const question = props; - const {questionEditMenuReducer} = useUIControllerContext(); + const {questionEditMenuReducer} = useUIController(); return ( diff --git a/frontend/guest-app/src/components/Question/QuestionCard/QuestionCardHeader.js b/frontend/guest-app/src/components/QuestionCard/QuestionCardHeader.js similarity index 88% rename from frontend/guest-app/src/components/Question/QuestionCard/QuestionCardHeader.js rename to frontend/guest-app/src/components/QuestionCard/QuestionCardHeader.js index 96a6372c..fcf35cb4 100644 --- a/frontend/guest-app/src/components/Question/QuestionCard/QuestionCardHeader.js +++ b/frontend/guest-app/src/components/QuestionCard/QuestionCardHeader.js @@ -1,8 +1,8 @@ import React from "react"; import styled from "styled-components"; import Grid from "@material-ui/core/Grid"; -import UserAvatar from "../../UserAvatar/UserAvatar.js"; -import LikeButton from "../../LikeButton/LikeButton.js"; +import UserAvatar from "../UserAvatar/UserAvatar.js"; +import LikeButton from "../LikeButton/LikeButton.js"; import QuestionCardDate from "./QuestionsCardDate.js"; import QuestionUserName from "./QuestionCardUserName.js"; diff --git a/frontend/guest-app/src/components/Question/QuestionCard/QuestionCardList.js b/frontend/guest-app/src/components/QuestionCard/QuestionCardList.js similarity index 100% rename from frontend/guest-app/src/components/Question/QuestionCard/QuestionCardList.js rename to frontend/guest-app/src/components/QuestionCard/QuestionCardList.js diff --git a/frontend/guest-app/src/components/Question/QuestionCard/QuestionCardUserName.js b/frontend/guest-app/src/components/QuestionCard/QuestionCardUserName.js similarity index 100% rename from frontend/guest-app/src/components/Question/QuestionCard/QuestionCardUserName.js rename to frontend/guest-app/src/components/QuestionCard/QuestionCardUserName.js diff --git a/frontend/guest-app/src/components/Question/QuestionCard/QuestionsCardDate.js b/frontend/guest-app/src/components/QuestionCard/QuestionsCardDate.js similarity index 100% rename from frontend/guest-app/src/components/Question/QuestionCard/QuestionsCardDate.js rename to frontend/guest-app/src/components/QuestionCard/QuestionsCardDate.js diff --git a/frontend/guest-app/src/components/Question/QuestionCard/DeleteQuestionCardModal.js b/frontend/guest-app/src/components/QuestionCardEditMenuDrawer/QuestionCardDeleteModal.js similarity index 74% rename from frontend/guest-app/src/components/Question/QuestionCard/DeleteQuestionCardModal.js rename to frontend/guest-app/src/components/QuestionCardEditMenuDrawer/QuestionCardDeleteModal.js index a716cdb0..009a74e0 100644 --- a/frontend/guest-app/src/components/Question/QuestionCard/DeleteQuestionCardModal.js +++ b/frontend/guest-app/src/components/QuestionCardEditMenuDrawer/QuestionCardDeleteModal.js @@ -2,9 +2,9 @@ import React from "react"; import PropTypes from "prop-types"; import Grid from "@material-ui/core/Grid"; import Button from "@material-ui/core/Button"; -import CommonModal from "../../CommonComponent/CommonModal/CommonModal.js"; +import CommonModal from "../CommonComponent/CommonModal/CommonModal.js"; -function DeleteQuestionCardModal(props) { +function QuestionCardDeleteModal(props) { const {isOpened, closeModal, onCancel, onDelete} = props; return ( @@ -20,15 +20,15 @@ function DeleteQuestionCardModal(props) { ); } -DeleteQuestionCardModal.propTypes = { +QuestionCardDeleteModal.propTypes = { isOpened: PropTypes.bool, closeModal: PropTypes.func, onCancel: PropTypes.func, onDelete: PropTypes.func, }; -DeleteQuestionCardModal.defaultProps = { +QuestionCardDeleteModal.defaultProps = { isOpened: PropTypes.bool, }; -export default DeleteQuestionCardModal; +export default QuestionCardDeleteModal; diff --git a/frontend/guest-app/src/components/Question/QuestionCard/QuestionEditMenuDrawer.js b/frontend/guest-app/src/components/QuestionCardEditMenuDrawer/QuestionCardEditMenuDrawer.js similarity index 58% rename from frontend/guest-app/src/components/Question/QuestionCard/QuestionEditMenuDrawer.js rename to frontend/guest-app/src/components/QuestionCardEditMenuDrawer/QuestionCardEditMenuDrawer.js index 1aec9771..32017380 100644 --- a/frontend/guest-app/src/components/Question/QuestionCard/QuestionEditMenuDrawer.js +++ b/frontend/guest-app/src/components/QuestionCardEditMenuDrawer/QuestionCardEditMenuDrawer.js @@ -2,25 +2,22 @@ import React from "react"; import PropTypes from "prop-types"; import Drawer from "@material-ui/core/Drawer"; import List from "@material-ui/core/List"; -import EditIcon from "@material-ui/icons/Edit.js"; -import SideMenuItem from "../../SideMenu/SideMenuItem.js"; -import DeleteQuestionCardMenuButton from "./DeleteQuestionCardMenuButton.js"; +import QuestionCardMenuDeleteButton from "./QuestionCardMenuDeleteButton.js"; +import EditQuestionCardMenuButton from "./QuestionCardMenuEditButton.js"; -function QuestionEditMenuDrawer(props) { +function QuestionCardEditMenuDrawer(props) { const {isOpen, onClose, onEdit, onDelete} = props; return ( - } - itemText={"질문 수정"} + { onEdit(); onClose(); }} /> - @@ -29,18 +26,18 @@ function QuestionEditMenuDrawer(props) { ); } -QuestionEditMenuDrawer.propTypes = { +QuestionCardEditMenuDrawer.propTypes = { isOpen: PropTypes.bool, onClose: PropTypes.func, onEdit: PropTypes.func, onDelete: PropTypes.func, }; -QuestionEditMenuDrawer.defaultProps = { +QuestionCardEditMenuDrawer.defaultProps = { isOpen: false, onClose: () => {}, onEdit: () => {}, onDelete: () => {}, }; -export default QuestionEditMenuDrawer; +export default QuestionCardEditMenuDrawer; diff --git a/frontend/guest-app/src/components/Question/QuestionCard/DeleteQuestionCardMenuButton.js b/frontend/guest-app/src/components/QuestionCardEditMenuDrawer/QuestionCardMenuDeleteButton.js similarity index 55% rename from frontend/guest-app/src/components/Question/QuestionCard/DeleteQuestionCardMenuButton.js rename to frontend/guest-app/src/components/QuestionCardEditMenuDrawer/QuestionCardMenuDeleteButton.js index 3f439eb9..4cdd786e 100644 --- a/frontend/guest-app/src/components/Question/QuestionCard/DeleteQuestionCardMenuButton.js +++ b/frontend/guest-app/src/components/QuestionCardEditMenuDrawer/QuestionCardMenuDeleteButton.js @@ -1,11 +1,11 @@ import React from "react"; import PropTypes from "prop-types"; import DeleteIcon from "@material-ui/icons/Delete.js"; -import useCommonModal from "../../CommonComponent/CommonModal/useCommonModal.js"; -import SideMenuItem from "../../SideMenu/SideMenuItem.js"; -import DeleteQuestionCardModal from "./DeleteQuestionCardModal.js"; +import useCommonModal from "../CommonComponent/CommonModal/useCommonModal.js"; +import SideMenuItem from "../SideMenu/SideMenuItem.js"; +import QuestionCardDeleteModal from "./QuestionCardDeleteModal.js"; -function DeleteQuestionCardMenuButton(props) { +function QuestionCardMenuDeleteButton(props) { const {onDelete} = props; const {isOpened, openModal, closeModal} = useCommonModal(); @@ -16,7 +16,7 @@ function DeleteQuestionCardMenuButton(props) { itemText={"질문 삭제"} onClick={openModal} /> - } + itemText={"질문 수정"} + onClick={onClick} + /> + ); +} + +export default EditQuestionCardMenuButton; diff --git a/frontend/guest-app/src/components/Question/QuestionInputArea/AddQuestionInputButton.js b/frontend/guest-app/src/components/QuestionContainer/AddQuestionInputButton.js similarity index 97% rename from frontend/guest-app/src/components/Question/QuestionInputArea/AddQuestionInputButton.js rename to frontend/guest-app/src/components/QuestionContainer/AddQuestionInputButton.js index 23b7f7c7..4ad7cb2f 100644 --- a/frontend/guest-app/src/components/Question/QuestionInputArea/AddQuestionInputButton.js +++ b/frontend/guest-app/src/components/QuestionContainer/AddQuestionInputButton.js @@ -11,6 +11,7 @@ const cardStyle = { zIndex: 100, margin: "1rem", backgroundColor: "#3f51b5", + cursor: "pointer", }; const cardContentStyle = {paddingBottom: "1rem"}; diff --git a/frontend/guest-app/src/components/Question/EditQuestionInputDrawer.js b/frontend/guest-app/src/components/QuestionContainer/EditQuestionInputDrawer.js similarity index 72% rename from frontend/guest-app/src/components/Question/EditQuestionInputDrawer.js rename to frontend/guest-app/src/components/QuestionContainer/EditQuestionInputDrawer.js index 912a09ca..631ac265 100644 --- a/frontend/guest-app/src/components/Question/EditQuestionInputDrawer.js +++ b/frontend/guest-app/src/components/QuestionContainer/EditQuestionInputDrawer.js @@ -1,10 +1,10 @@ -import React, {useContext} from "react"; -import {GuestGlobalContext} from "../../libs/guestGlobalContext.js"; -import {socketClient} from "../../libs/socketIoClientProvider.js"; -import QuestionInputDrawer from "./QuestionInputArea/QuestionInputDrawer.js"; +import React from "react"; +import {socketClient} from "../../socket.io"; +import QuestionInputDrawer from "./QuestionInputDrawer.js"; +import useGlobalData from "../../contexts/GlobalData/useGlobalData.js"; function EditQuestionInputDrawer({userNameRef, questionRef, toggleReducer}) { - const {event, guest} = useContext(GuestGlobalContext); + const {event, guest} = useGlobalData(); const onConfirmEditQuestion = () => { socketClient.emit("question/update", { diff --git a/frontend/guest-app/src/components/Question/MyQuestionDrawer.js b/frontend/guest-app/src/components/QuestionContainer/MyQuestionDrawer.js similarity index 71% rename from frontend/guest-app/src/components/Question/MyQuestionDrawer.js rename to frontend/guest-app/src/components/QuestionContainer/MyQuestionDrawer.js index 1949b2c7..53167fde 100644 --- a/frontend/guest-app/src/components/Question/MyQuestionDrawer.js +++ b/frontend/guest-app/src/components/QuestionContainer/MyQuestionDrawer.js @@ -1,12 +1,12 @@ -import React, {useContext} from "react"; +import React from "react"; import PropTypes from "prop-types"; import {grey} from "@material-ui/core/colors"; import {Scrollbars} from "react-custom-scrollbars"; import AppDrawer from "../AppDrawer/AppDrawer.js"; -import {useQuestions} from "./QuestionsContext.js"; -import QuestionCardList from "./QuestionCard/QuestionCardList.js"; -import {GuestGlobalContext} from "../../libs/guestGlobalContext.js"; -import PaddingArea from "./QuestionInputArea/PaddingArea.js"; +import QuestionCardList from "../QuestionCard/QuestionCardList.js"; +import PaddingArea from "../atoms/PaddingArea.js"; +import useQuestions from "../../contexts/Questions/useQuestions.js"; +import useGlobalData from "../../contexts/GlobalData/useGlobalData.js"; const fullSizeCardStyle = { width: "100vw", @@ -16,7 +16,7 @@ const fullSizeCardStyle = { function MyQuestionsDrawer(props) { const {isOpen, onClose} = props; - const {guest} = useContext(GuestGlobalContext); + const {guest} = useGlobalData(); const {questions, replies} = useQuestions(); return ( diff --git a/frontend/guest-app/src/components/Question/NewQuestionInputDrawer.js b/frontend/guest-app/src/components/QuestionContainer/NewQuestionInputDrawer.js similarity index 72% rename from frontend/guest-app/src/components/Question/NewQuestionInputDrawer.js rename to frontend/guest-app/src/components/QuestionContainer/NewQuestionInputDrawer.js index cbe37f58..3811679f 100644 --- a/frontend/guest-app/src/components/Question/NewQuestionInputDrawer.js +++ b/frontend/guest-app/src/components/QuestionContainer/NewQuestionInputDrawer.js @@ -1,7 +1,7 @@ -import React, {useContext} from "react"; -import {GuestGlobalContext} from "../../libs/guestGlobalContext.js"; -import {socketClient} from "../../libs/socketIoClientProvider.js"; -import QuestionInputDrawer from "./QuestionInputArea/QuestionInputDrawer.js"; +import React from "react"; +import {socketClient} from "../../socket.io"; +import QuestionInputDrawer from "./QuestionInputDrawer.js"; +import useGlobalData from "../../contexts/GlobalData/useGlobalData.js"; function getNewQuestion({EventId, GuestId, guestName, content}) { return { @@ -14,7 +14,7 @@ function getNewQuestion({EventId, GuestId, guestName, content}) { } function NewQuestionInputDrawer({userNameRef, questionRef, toggleReducer}) { - const {event, guest} = useContext(GuestGlobalContext); + const {event, guest} = useGlobalData(); const onConfirmNewQuestion = () => { socketClient.emit( @@ -35,6 +35,7 @@ function NewQuestionInputDrawer({userNameRef, questionRef, toggleReducer}) { onConfirm: onConfirmNewQuestion, userNameRef, questionRef, + initialUserName: guest.name, }; return ; diff --git a/frontend/guest-app/src/components/Question/QuestionContainer.js b/frontend/guest-app/src/components/QuestionContainer/QuestionContainer.js similarity index 73% rename from frontend/guest-app/src/components/Question/QuestionContainer.js rename to frontend/guest-app/src/components/QuestionContainer/QuestionContainer.js index 6ebc5e77..62f78f1c 100644 --- a/frontend/guest-app/src/components/Question/QuestionContainer.js +++ b/frontend/guest-app/src/components/QuestionContainer/QuestionContainer.js @@ -1,21 +1,26 @@ import React, {useRef} from "react"; +import styled from "styled-components"; import QuestionContainerTabBar from "./QuestionContainerTabBar.js"; -import useTabs from "../../materialUIHooks/useTabs.js"; -import AddQuestionInputButton from "./QuestionInputArea/AddQuestionInputButton.js"; -import QuestionCardList from "./QuestionCard/QuestionCardList.js"; -import {socketClient} from "../../libs/socketIoClientProvider.js"; -import PaddingArea from "./QuestionInputArea/PaddingArea.js"; -import QuestionEditMenuDrawer from "./QuestionCard/QuestionEditMenuDrawer.js"; +import useTabs from "../../hooks/useTabs.js"; +import AddQuestionInputButton from "./AddQuestionInputButton.js"; +import QuestionCardList from "../QuestionCard/QuestionCardList.js"; +import {socketClient} from "../../socket.io"; +import PaddingArea from "../atoms/PaddingArea.js"; +import QuestionCardEditMenuDrawer from "../QuestionCardEditMenuDrawer/QuestionCardEditMenuDrawer.js"; import NewQuestionInputDrawer from "./NewQuestionInputDrawer.js"; import EditQuestionInputDrawer from "./EditQuestionInputDrawer.js"; -import {useUIControllerContext} from "../UIController/UIController.js"; -import {useQuestions} from "./QuestionsContext.js"; - import MyQuestionsDrawer from "./MyQuestionDrawer.js"; +import useUIController from "../../contexts/UIController/useUIController.js"; +import useQuestions from "../../contexts/Questions/useQuestions.js"; const RECENT_TAB_IDX = 1; const POPULAR_TAB_IDX = 2; +const QuestionContainerStyle = styled.div` + overflow-y:scroll; + height: 100% +`; + function QuestionContainer() { const {dispatch, questions, replies} = useQuestions(); @@ -24,7 +29,7 @@ function QuestionContainer() { editQuestionInputDrawer, questionEditMenuReducer, myQuestionDrawerReducer, - } = useUIControllerContext(); + } = useUIController(); const {tabIdx, selectTabIdx} = useTabs(RECENT_TAB_IDX); const userNameRef = useRef(null); const questionRef = useRef(null); @@ -42,7 +47,7 @@ function QuestionContainer() { }; return ( - <> + - questionEditMenuReducer.setOff()} onDelete={() => { @@ -77,14 +82,13 @@ function QuestionContainer() { editQuestionInputDrawer.setOn(questionEditMenuReducer.data); }} /> - { myQuestionDrawerReducer.setOff(); }} /> - + ); } diff --git a/frontend/guest-app/src/components/Question/QuestionContainerTabBar.js b/frontend/guest-app/src/components/QuestionContainer/QuestionContainerTabBar.js similarity index 91% rename from frontend/guest-app/src/components/Question/QuestionContainerTabBar.js rename to frontend/guest-app/src/components/QuestionContainer/QuestionContainerTabBar.js index 709d10bc..c2690ef2 100644 --- a/frontend/guest-app/src/components/Question/QuestionContainerTabBar.js +++ b/frontend/guest-app/src/components/QuestionContainer/QuestionContainerTabBar.js @@ -5,7 +5,7 @@ import Tab from "@material-ui/core/Tab"; import Tabs from "@material-ui/core/Tabs"; import gray from "@material-ui/core/colors/grey.js"; import PropTypes from "prop-types"; -import {useQuestions} from "./QuestionsContext.js"; +import useQuestions from "../../contexts/Questions/useQuestions.js"; const RECENT_TAB_IDX = 1; const POPULAR_TAB_IDX = 2; @@ -24,7 +24,7 @@ function QuestionContainerTabBar(props) { > 최근순} + label={시간순} selected={tabIdx === RECENT_TAB_IDX} /> { - onConfirm && onConfirm(); - onClose(); + if (onConfirm) { + if (questionRef.current.value.trim() !== "") { + onConfirm(); + onClose(); + } + } }} onCancel={onClose} questionRef={questionRef} diff --git a/frontend/guest-app/src/components/Question/QuestionInputArea/QuestionContentInput.js b/frontend/guest-app/src/components/QuestionInput/QuestionContentInput.js similarity index 89% rename from frontend/guest-app/src/components/Question/QuestionInputArea/QuestionContentInput.js rename to frontend/guest-app/src/components/QuestionInput/QuestionContentInput.js index cd2b59c4..c5e8759f 100644 --- a/frontend/guest-app/src/components/Question/QuestionInputArea/QuestionContentInput.js +++ b/frontend/guest-app/src/components/QuestionInput/QuestionContentInput.js @@ -1,7 +1,7 @@ import React from "react"; import PropTypes from "prop-types"; import TextField from "@material-ui/core/TextField"; -import useCommonTextInput from "../../CommonComponent/CommonTextInput/useCommonTextInput.js"; +import useCommonTextInput from "../CommonComponent/CommonTextInput/useCommonTextInput.js"; function QuestionContentInput(props) { const {questionRef, initValue} = props; diff --git a/frontend/guest-app/src/components/Question/QuestionInputArea/QuestionInput.js b/frontend/guest-app/src/components/QuestionInput/QuestionInput.js similarity index 94% rename from frontend/guest-app/src/components/Question/QuestionInputArea/QuestionInput.js rename to frontend/guest-app/src/components/QuestionInput/QuestionInput.js index 04360281..dfd69a36 100644 --- a/frontend/guest-app/src/components/Question/QuestionInputArea/QuestionInput.js +++ b/frontend/guest-app/src/components/QuestionInput/QuestionInput.js @@ -6,7 +6,7 @@ import Grid from "@material-ui/core/Grid"; import Divider from "@material-ui/core/Divider"; import QuestionContentInput from "./QuestionContentInput.js"; -import UserInfoInput from "./UserInfoInput.js"; +import QuestionUserInfoInput from "./QuestionUserInfoInput.js"; const FlexedCenterDiv = styled.div` display: flex; @@ -39,7 +39,7 @@ function QuestionInput(props) { - diff --git a/frontend/guest-app/src/components/QuestionInput/QuestionUserInfoInput.js b/frontend/guest-app/src/components/QuestionInput/QuestionUserInfoInput.js new file mode 100644 index 00000000..2c9f083c --- /dev/null +++ b/frontend/guest-app/src/components/QuestionInput/QuestionUserInfoInput.js @@ -0,0 +1,26 @@ +import PropTypes from "prop-types"; +import React from "react"; +import useStringState from "../../hooks/useStringState.js"; +import UserAvatar from "../UserAvatar/UserAvatar.js"; +import QuestionUserNameInput from "./QuestionUserNameInput.js"; + +function QuestionUserInfoInput(props) { + const {userNameRef, initialUserName = ""} = props; + const {state} = useStringState(initialUserName); + + return ( + <> + + + + ); +} + +QuestionUserInfoInput.propTypes = { + userNameRef: PropTypes.any, +}; + +export default QuestionUserInfoInput; diff --git a/frontend/guest-app/src/components/Question/QuestionInputArea/QuestionUserNameInput.js b/frontend/guest-app/src/components/QuestionInput/QuestionUserNameInput.js similarity index 100% rename from frontend/guest-app/src/components/Question/QuestionInputArea/QuestionUserNameInput.js rename to frontend/guest-app/src/components/QuestionInput/QuestionUserNameInput.js diff --git a/frontend/guest-app/src/components/Reply/AddReplyRow.js b/frontend/guest-app/src/components/Reply/AddReplyRow.js deleted file mode 100644 index 31aae0a3..00000000 --- a/frontend/guest-app/src/components/Reply/AddReplyRow.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from "react"; -import Button from "@material-ui/core/Button"; -import ChatBubble from "@material-ui/icons/ChatBubble"; -import {styled} from "@material-ui/core/styles"; - -const StyledButton = styled(Button)({ - height: "2rem", - marginBottom: "1rem", -}); - -function AddReplyRow(props) { - const {openReplies} = props; - - return ( - - - 댓글달기 - - ); -} - -export default AddReplyRow; diff --git a/frontend/guest-app/src/components/Reply/RepliesContainer.js b/frontend/guest-app/src/components/Reply/RepliesContainer.js deleted file mode 100644 index a48164cb..00000000 --- a/frontend/guest-app/src/components/Reply/RepliesContainer.js +++ /dev/null @@ -1,35 +0,0 @@ -import React from "react"; -import Box from "@material-ui/core/Box"; -import {styled} from "@material-ui/core/styles"; -import AppDrawNavBar from "../AppDrawer/AppDrawerNavBar"; -import PreviewQuestion from "./PreviewQuestion"; -import RepliesList from "./RepliesList"; -import ReplyQuestionDivider from "./ReplyQuestionDivider"; -import ReplyInputContainer from "./ReplyInputContainer"; - -const Container = styled(Box)({ - display: "flex", - flexDirection: "column", - position: "absolute", - top: "4rem", - width: "99%", -}); - -function RepliesContainer(props) { - const {onClose} = props; - - return ( - - - - - - - - ); -} - -export default RepliesContainer; diff --git a/frontend/guest-app/src/components/Reply/RepliesList.js b/frontend/guest-app/src/components/Reply/RepliesList.js deleted file mode 100644 index ed8c907e..00000000 --- a/frontend/guest-app/src/components/Reply/RepliesList.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import {styled} from "@material-ui/core/styles"; -import Paper from "@material-ui/core/Paper"; -import Reply from "./Reply"; - -const RepliesListContainer = styled(Paper)({ - width: "100%", - backgroundColor: "#FFFFF0", -}); - -function RepliesList(props) { - const {replies} = props; - - return ( - <> - - {replies.map((reply, idx) => )} - - - ); -} - -RepliesList.propTypes = { - replies: PropTypes.any, -}; - -export default RepliesList; diff --git a/frontend/guest-app/src/components/Reply/RepliesPaper.js b/frontend/guest-app/src/components/Reply/RepliesPaper.js deleted file mode 100644 index 7645b426..00000000 --- a/frontend/guest-app/src/components/Reply/RepliesPaper.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from "react"; -import {styled} from "@material-ui/core/styles"; -import Paper from "@material-ui/core/Paper"; -import RepliesContainer from "./RepliesContainer"; - -const StyledPaper = styled(Paper)({ - width: "100vw", - height: "100vh", - position: "relative", -}); - -function RepliesPaper(props) { - return ( - <> - - - - - ); -} - -export default RepliesPaper; diff --git a/frontend/guest-app/src/components/Reply/Reply.js b/frontend/guest-app/src/components/Reply/Reply.js deleted file mode 100644 index ba711b50..00000000 --- a/frontend/guest-app/src/components/Reply/Reply.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from "react"; -import Card from "@material-ui/core/Card"; -import {CardContent} from "@material-ui/core"; -import Divider from "@material-ui/core/Divider"; -import QuestionHeader from "../Question/QuestionCard/QuestionCardHeader.js"; -import QuestionBody from "../Question/QuestionCard/QuestionCardBody"; -import EmojiArea from "../Emoji/EmojiArea"; - -function Reply(props) { - return ( - <> - - - - - - - - - - ); -} - -export default Reply; diff --git a/frontend/guest-app/src/components/Reply/ReplyArea.js b/frontend/guest-app/src/components/Reply/ReplyArea.js deleted file mode 100644 index d6b3345f..00000000 --- a/frontend/guest-app/src/components/Reply/ReplyArea.js +++ /dev/null @@ -1,78 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import {styled} from "@material-ui/core/styles"; -import Paper from "@material-ui/core/Paper"; -import Drawer from "@material-ui/core/Drawer"; -import ReplyAvatar from "./ReplyAvatar"; -import RepliesPaper from "./RepliesPaper"; -import AddReplyRow from "./AddReplyRow"; -import useReplies from "./useReplies"; - -import CurrentRepliesTextField from "./CurrentRepliesTextField"; - -const MAX_SHOWING_AVATAR = 5; -const PreviewReplyContainer = styled(Paper)({ - display: "flex", - height: "3rem", - marginBottom: 5, - alignItems: "center", - backgroundColor: "#FFFFF0", -}); - -function extractUniqueReplier(replies) { - const uniqueGuestIdMap = new Map(); - - replies.forEach(replie => { - uniqueGuestIdMap.set(replie.GuestId, replie.guestName); - }); - return [...uniqueGuestIdMap.values()]; -} - -export default function ReplyArea(props) { - const {repliesIsOpened, openReplies, closeReplies} = useReplies(); - const {replies} = props; - const repliers = extractUniqueReplier(replies); - const repliersNum = repliers.length; - const showingReplierList = repliers.slice(0, MAX_SHOWING_AVATAR); - - return ( - <> - {replies.length !== 0 ? ( - - {showingReplierList.map((userName, idx) => { - if (idx === MAX_SHOWING_AVATAR - 1) { - return ( - - ); - } - return ( - - ); - })} - - - {`${replies.length}개 댓글`} - - - ) : ( - - )} - - - - - ); -} - -ReplyArea.propTypes = { - replies: PropTypes.array, -}; diff --git a/frontend/guest-app/src/components/ReplyCard/ReplyCard.js b/frontend/guest-app/src/components/ReplyCard/ReplyCard.js new file mode 100644 index 00000000..584b37d9 --- /dev/null +++ b/frontend/guest-app/src/components/ReplyCard/ReplyCard.js @@ -0,0 +1,24 @@ +import React from "react"; +import Card from "@material-ui/core/Card"; +import {CardContent} from "@material-ui/core"; +import Divider from "@material-ui/core/Divider"; +import QuestionHeader from "../QuestionCard/QuestionCardHeader.js"; +import QuestionBody from "../QuestionCard/QuestionCardBody.js"; +import EmojiArea from "../EmojiArea/EmojiArea.js"; + +function ReplyCard(props) { + return ( + + + + + + + + + ); +} + +export default ReplyCard; diff --git a/frontend/guest-app/src/components/ReplyCard/ReplyCardList.js b/frontend/guest-app/src/components/ReplyCard/ReplyCardList.js new file mode 100644 index 00000000..cad2f6e5 --- /dev/null +++ b/frontend/guest-app/src/components/ReplyCard/ReplyCardList.js @@ -0,0 +1,29 @@ +import React from "react"; +import PropTypes from "prop-types"; +import {styled} from "@material-ui/core/styles"; +import Paper from "@material-ui/core/Paper"; +import gray from "@material-ui/core/colors/grey.js"; +import ReplyCard from "./ReplyCard.js"; + +const RepliesListStyle = styled(Paper)({ + width: "100%", + backgroundColor: gray[300], +}); + +function ReplyCardList(props) { + const {replies} = props; + + return ( + + {replies.map((reply, idx) => ( + + ))} + + ); +} + +ReplyCardList.propTypes = { + replies: PropTypes.any, +}; + +export default ReplyCardList; diff --git a/frontend/guest-app/src/components/Reply/PreviewQuestion.js b/frontend/guest-app/src/components/ReplyContainer/PreviewQuestion.js similarity index 71% rename from frontend/guest-app/src/components/Reply/PreviewQuestion.js rename to frontend/guest-app/src/components/ReplyContainer/PreviewQuestion.js index e3aa40d0..98a64af1 100644 --- a/frontend/guest-app/src/components/Reply/PreviewQuestion.js +++ b/frontend/guest-app/src/components/ReplyContainer/PreviewQuestion.js @@ -4,17 +4,17 @@ import {styled} from "@material-ui/core/styles"; import {CardContent} from "@material-ui/core"; import Divider from "@material-ui/core/Divider"; import Box from "@material-ui/core/Box"; -import QuestionHeader from "../Question/QuestionCard/QuestionCardHeader.js"; -import QuestionBody from "../Question/QuestionCard/QuestionCardBody"; -import EmojiArea from "../Emoji/EmojiArea"; +import QuestionHeader from "../QuestionCard/QuestionCardHeader.js"; +import QuestionBody from "../QuestionCard/QuestionCardBody.js"; +import EmojiArea from "../EmojiArea/EmojiArea.js"; -const Container = styled(Box)({ +const PreviewQuestionStyle = styled(Box)({ backgroundColor: "#E0E0E0", }); function PreviewQuestion(props) { return ( - + @@ -25,7 +25,7 @@ function PreviewQuestion(props) { - + ); } diff --git a/frontend/guest-app/src/components/ReplyContainer/RepliesContainer.js b/frontend/guest-app/src/components/ReplyContainer/RepliesContainer.js new file mode 100644 index 00000000..5c5255f8 --- /dev/null +++ b/frontend/guest-app/src/components/ReplyContainer/RepliesContainer.js @@ -0,0 +1,41 @@ +import React from "react"; +import styled from "styled-components"; +import {Scrollbars} from "react-custom-scrollbars"; +import Box from "@material-ui/core/Box"; +import PreviewQuestion from "./PreviewQuestion.js"; +import ReplyCardList from "../ReplyCard/ReplyCardList.js"; +import ReplyQuestionDivider from "./ReplyQuestionDivider.js"; +import ReplyInputContainer from "../ReplyInput/ReplyInputContainer.js"; +import PaddingArea from "../atoms/PaddingArea.js"; + +const RepliesContainerStyle = styled.div` + overflow-y: scroll; + ::-webkit-scrollbar { + display: none; + } +`; + +const fullScreen = { + width: "100vw", + height: "100vh", +}; + +function RepliesContainer(props) { + return ( + + + + + + + + + + + ); +} + +export default RepliesContainer; diff --git a/frontend/guest-app/src/components/Reply/ReplyQuestionDivider.js b/frontend/guest-app/src/components/ReplyContainer/ReplyQuestionDivider.js similarity index 78% rename from frontend/guest-app/src/components/Reply/ReplyQuestionDivider.js rename to frontend/guest-app/src/components/ReplyContainer/ReplyQuestionDivider.js index 5f9eed1e..92fb89c8 100644 --- a/frontend/guest-app/src/components/Reply/ReplyQuestionDivider.js +++ b/frontend/guest-app/src/components/ReplyContainer/ReplyQuestionDivider.js @@ -2,6 +2,7 @@ import React from "react"; import {styled} from "@material-ui/core/styles"; import Divider from "@material-ui/core/Divider"; import Box from "@material-ui/core/Box"; +import {Typography} from "@material-ui/core"; const Container = styled(Box)({ marginTop: "1rem", @@ -12,10 +13,6 @@ const Container = styled(Box)({ flex: 1, }); -const ReplieNumField = styled(Box)({ - fontWeight: "bold", -}); - const CustomDivider = styled(Divider)({ marginLeft: "2rem", width: "70%", @@ -28,8 +25,8 @@ function ReplyQuestionDivider(props) { return ( - {`${replies.length}개 댓글`} - + {`댓글 ${replies.length}개`} + ); } diff --git a/frontend/guest-app/src/components/Reply/ReplierInfoInput.js b/frontend/guest-app/src/components/ReplyInput/ReplierInfoInput.js similarity index 78% rename from frontend/guest-app/src/components/Reply/ReplierInfoInput.js rename to frontend/guest-app/src/components/ReplyInput/ReplierInfoInput.js index db4cfaaf..06ec7c0a 100644 --- a/frontend/guest-app/src/components/Reply/ReplierInfoInput.js +++ b/frontend/guest-app/src/components/ReplyInput/ReplierInfoInput.js @@ -12,11 +12,7 @@ const TextFieldStyle = styled(TextField)({ }); function UserInfoInput(props) { - const {userNameRef, userName, setUserName} = props; - - const onUserNameChange = e => { - setUserName(e.target.value); - }; + const {userNameRef, userName} = props; return ( <> @@ -24,7 +20,6 @@ function UserInfoInput(props) { @@ -34,7 +29,6 @@ function UserInfoInput(props) { UserInfoInput.propTypes = { userNameRef: PropTypes.any, userName: PropTypes.string, - setUserName: PropTypes.func, }; export default UserInfoInput; diff --git a/frontend/guest-app/src/components/Reply/ReplyContentInput.js b/frontend/guest-app/src/components/ReplyInput/ReplyContentInput.js similarity index 96% rename from frontend/guest-app/src/components/Reply/ReplyContentInput.js rename to frontend/guest-app/src/components/ReplyInput/ReplyContentInput.js index c4f3b5b7..19d3e0eb 100644 --- a/frontend/guest-app/src/components/Reply/ReplyContentInput.js +++ b/frontend/guest-app/src/components/ReplyInput/ReplyContentInput.js @@ -13,7 +13,7 @@ function ReplyContentInput(props) { @@ -37,7 +39,6 @@ function ReplyInput(props) { @@ -45,7 +46,7 @@ function ReplyInput(props) { variant="contained" color={"primary"} onClick={() => { - onConfirm(); + onConfirm(replyContent); setReplyContent(""); }} > diff --git a/frontend/guest-app/src/components/Reply/ReplyInputContainer.js b/frontend/guest-app/src/components/ReplyInput/ReplyInputContainer.js similarity index 67% rename from frontend/guest-app/src/components/Reply/ReplyInputContainer.js rename to frontend/guest-app/src/components/ReplyInput/ReplyInputContainer.js index d858a97a..622d0eab 100644 --- a/frontend/guest-app/src/components/Reply/ReplyInputContainer.js +++ b/frontend/guest-app/src/components/ReplyInput/ReplyInputContainer.js @@ -1,10 +1,10 @@ -import React, {useContext, useRef} from "react"; +import React, {useRef} from "react"; import PropTypes from "prop-types"; import Card from "@material-ui/core/Card"; import CardContent from "@material-ui/core/CardContent"; -import ReplyInput from "./ReplyInput"; -import {socketClient} from "../../libs/socketIoClientProvider.js"; -import {GuestGlobalContext} from "../../libs/guestGlobalContext.js"; +import ReplyInput from "./ReplyInput.js"; +import {socketClient} from "../../socket.io"; +import useGlobalData from "../../contexts/GlobalData/useGlobalData.js"; const createNewReply = ({ EventId, @@ -23,10 +23,14 @@ const createNewReply = ({ function ReplyInputContainer(props) { const {id} = props; - const {event, guest} = useContext(GuestGlobalContext); + const {event, guest} = useGlobalData(); const userNameRef = useRef(null); const questionRef = useRef(null); - const onConfirmNewReply = () => { + const onConfirmNewReply = reply => { + if (reply.trim() === "") { + return; + } + socketClient.emit( "question/create", createNewReply({ @@ -40,12 +44,10 @@ function ReplyInputContainer(props) { }; return ( - - + + { - onConfirmNewReply(); - }} + onConfirm={onConfirmNewReply} confirmButtonText="댓글달기" userNameRef={userNameRef} questionRef={questionRef} diff --git a/frontend/guest-app/src/components/ReplyPreviewArea/ReplyAddButton.js b/frontend/guest-app/src/components/ReplyPreviewArea/ReplyAddButton.js new file mode 100644 index 00000000..17700c82 --- /dev/null +++ b/frontend/guest-app/src/components/ReplyPreviewArea/ReplyAddButton.js @@ -0,0 +1,23 @@ +import React from "react"; +import Button from "@material-ui/core/Button"; +import {ChatBubbleOutlineOutlined} from "@material-ui/icons"; +import {styled} from "@material-ui/core/styles"; +import {Typography} from "@material-ui/core"; + +const StyledButton = styled(Button)({ + height: "2rem", + marginBottom: "1rem", +}); + +function ReplyAddButton(props) { + const {onClick} = props; + + return ( + + + 댓글달기 + + ); +} + +export default ReplyAddButton; diff --git a/frontend/guest-app/src/components/Reply/CurrentRepliesTextField.js b/frontend/guest-app/src/components/ReplyPreviewArea/ReplyNumber.js similarity index 57% rename from frontend/guest-app/src/components/Reply/CurrentRepliesTextField.js rename to frontend/guest-app/src/components/ReplyPreviewArea/ReplyNumber.js index e58f1853..b54a6884 100644 --- a/frontend/guest-app/src/components/Reply/CurrentRepliesTextField.js +++ b/frontend/guest-app/src/components/ReplyPreviewArea/ReplyNumber.js @@ -6,26 +6,26 @@ import {Typography} from "@material-ui/core"; const TextField = styled(Typography)({ marginLeft: 10, textAlign: "center", - textDecoration: "underline", + textDecoration: "none", cursor: "pointer", "&:hover": { color: "#3f51b5", }, }); -function CurrentRepliesTextField(props) { - const {openReplies} = props; +function ReplyNumber(props) { + const {openReplies, replyCount} = props; return ( - - {props.children} + + {`댓글 ${replyCount}개`} ); } -CurrentRepliesTextField.propTypes = { - children: PropTypes.node, +ReplyNumber.propTypes = { + replyCount: PropTypes.number, openReplies: PropTypes.func, }; -export default CurrentRepliesTextField; +export default ReplyNumber; diff --git a/frontend/guest-app/src/components/ReplyPreviewArea/ReplyPreview.js b/frontend/guest-app/src/components/ReplyPreviewArea/ReplyPreview.js new file mode 100644 index 00000000..fc93760a --- /dev/null +++ b/frontend/guest-app/src/components/ReplyPreviewArea/ReplyPreview.js @@ -0,0 +1,53 @@ +import React from "react"; +import {styled} from "@material-ui/core/styles"; +import Paper from "@material-ui/core/Paper"; +import gray from "@material-ui/core/colors/grey.js"; +import ReplyPreviewAvatar from "./ReplyPreviewAvatar.js"; +import ReplyNumber from "./ReplyNumber.js"; + +const MAX_SHOWING_AVATAR = 5; +const ReplyPreviewStyle = styled(Paper)({ + display: "flex", + height: "3rem", + marginBottom: 15, + alignItems: "center", + backgroundColor: gray[300], +}); + +function extractUniqueReplier(replies) { + const uniqueGuestIdMap = new Map(); + + replies.forEach(reply => { + uniqueGuestIdMap.set(reply.GuestId, reply.guestName); + }); + + return [...uniqueGuestIdMap.values()]; +} + +function ReplyPreview(props) { + const {replies, onClick} = props; + const repliers = extractUniqueReplier(replies); + const repliersNum = repliers.length; + const showingReplierList = repliers.slice(0, MAX_SHOWING_AVATAR); + + return ( + + {showingReplierList.map((userName, idx) => { + if (idx === MAX_SHOWING_AVATAR - 1) { + return ( + + ); + } + + return ; + })} + + + ); +} + +export default ReplyPreview; diff --git a/frontend/guest-app/src/components/ReplyPreviewArea/ReplyPreviewArea.js b/frontend/guest-app/src/components/ReplyPreviewArea/ReplyPreviewArea.js new file mode 100644 index 00000000..ffbe5938 --- /dev/null +++ b/frontend/guest-app/src/components/ReplyPreviewArea/ReplyPreviewArea.js @@ -0,0 +1,34 @@ +import React from "react"; +import PropTypes from "prop-types"; +import AddReplyButton from "./ReplyAddButton.js"; +import useReplies from "../../hooks/useReplies.js"; +import AppDrawer from "../AppDrawer/AppDrawer.js"; +import RepliesContainer from "../ReplyContainer/RepliesContainer.js"; +import ReplyPreview from "./ReplyPreview.js"; + +export default function ReplyPreviewArea(props) { + const {repliesIsOpened, openReplies, closeReplies} = useReplies(); + const {replies} = props; + + return ( + <> + {replies.length !== 0 ? ( + + ) : ( + + )} + + + + + ); +} + +ReplyPreviewArea.propTypes = { + replies: PropTypes.array, +}; diff --git a/frontend/guest-app/src/components/Reply/ReplyAvatar.js b/frontend/guest-app/src/components/ReplyPreviewArea/ReplyPreviewAvatar.js similarity index 92% rename from frontend/guest-app/src/components/Reply/ReplyAvatar.js rename to frontend/guest-app/src/components/ReplyPreviewArea/ReplyPreviewAvatar.js index 016778cc..12ede200 100644 --- a/frontend/guest-app/src/components/Reply/ReplyAvatar.js +++ b/frontend/guest-app/src/components/ReplyPreviewArea/ReplyPreviewAvatar.js @@ -39,7 +39,7 @@ function AnonymousAvatar() { ); } -function ReplyAvatar(props) { +function ReplyPreviewAvatar(props) { const {isAnonymous = false, userName = "Anonymous", remainder} = props; return isAnonymous ? ( @@ -49,10 +49,10 @@ function ReplyAvatar(props) { ); } -ReplyAvatar.propTypes = { +ReplyPreviewAvatar.propTypes = { userName: PropTypes.string, isAnonymous: PropTypes.bool, remainder: PropTypes.any, }; -export default ReplyAvatar; +export default ReplyPreviewAvatar; diff --git a/frontend/guest-app/src/components/SideMenu/EditProfileButton.js b/frontend/guest-app/src/components/SideMenu/EditProfileButton.js deleted file mode 100644 index e5347516..00000000 --- a/frontend/guest-app/src/components/SideMenu/EditProfileButton.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from "react"; -import PersonIcon from "@material-ui/icons/Person.js"; -import useCommonModal from "../CommonComponent/CommonModal/useCommonModal.js"; -import SideMenuItem from "./SideMenuItem.js"; -import EditProfileModal from "../Modals/EditProfileModal.js"; - -function EditProfileButton() { - const modalState = useCommonModal(); - - return ( -
- } - itemText={"내 프로필 변경"} - onClick={modalState.openModal} - /> - -
- ); -} - -export default EditProfileButton; diff --git a/frontend/guest-app/src/components/SideMenu/LogoutButton.js b/frontend/guest-app/src/components/SideMenu/LogoutButton.js index 7c9b6a3f..f19ff921 100644 --- a/frontend/guest-app/src/components/SideMenu/LogoutButton.js +++ b/frontend/guest-app/src/components/SideMenu/LogoutButton.js @@ -2,7 +2,7 @@ import React from "react"; import ExitToAppIcon from "@material-ui/icons/ExitToApp.js"; import useCommonModal from "../CommonComponent/CommonModal/useCommonModal.js"; import SideMenuItem from "./SideMenuItem.js"; -import LogOutModal from "../Modals/LogoutModal.js"; +import LogOutModal from "./LogoutModal.js"; import config from "../../config"; function LogoutButton(props) { diff --git a/frontend/guest-app/src/components/Modals/LogoutModal.js b/frontend/guest-app/src/components/SideMenu/LogoutModal.js similarity index 68% rename from frontend/guest-app/src/components/Modals/LogoutModal.js rename to frontend/guest-app/src/components/SideMenu/LogoutModal.js index e3bed7dc..a5b4568f 100644 --- a/frontend/guest-app/src/components/Modals/LogoutModal.js +++ b/frontend/guest-app/src/components/SideMenu/LogoutModal.js @@ -1,9 +1,10 @@ import React from "react"; import {Grid} from "@material-ui/core"; -import Button from "@material-ui/core/Button"; import Typography from "@material-ui/core/Typography"; import Box from "@material-ui/core/Box"; import CommonModal from "../CommonComponent/CommonModal/CommonModal.js"; +import CancelButton from "../atoms/CancelButton.js"; +import ConfirmButton from "../atoms/ConfirmButton.js"; function LogOutModal({isOpened = false, onCancelClick, onLogout}) { return ( @@ -11,17 +12,8 @@ function LogOutModal({isOpened = false, onCancelClick, onLogout}) { 로그아웃 하시겠습니까? - - - + +
); diff --git a/frontend/guest-app/src/components/SideMenu/MyQuestionButton.js b/frontend/guest-app/src/components/SideMenu/MyQuestionButton.js index 3de72aad..ba7a0df3 100644 --- a/frontend/guest-app/src/components/SideMenu/MyQuestionButton.js +++ b/frontend/guest-app/src/components/SideMenu/MyQuestionButton.js @@ -1,12 +1,12 @@ import React from "react"; import QuestionAnswerIcon from "@material-ui/icons/QuestionAnswer.js"; import SideMenuItem from "./SideMenuItem.js"; -import {useUIControllerContext} from "../UIController/UIController.js"; +import useUIController from "../../contexts/UIController/useUIController.js"; const MY_QUESTION_BUTTON_TEXT = "내 질문들"; function MyQuestionButton() { - const {myQuestionDrawerReducer} = useUIControllerContext(); + const {myQuestionDrawerReducer} = useUIController(); return ( diff --git a/frontend/guest-app/src/components/SideMenu/SideMenuBody.js b/frontend/guest-app/src/components/SideMenu/SideMenuBody.js index 71f121c5..cc6b6bc4 100644 --- a/frontend/guest-app/src/components/SideMenu/SideMenuBody.js +++ b/frontend/guest-app/src/components/SideMenu/SideMenuBody.js @@ -1,10 +1,7 @@ import React from "react"; - import List from "@material-ui/core/List"; import Divider from "@material-ui/core/Divider"; -import EditProfileModal from "../Modals/EditProfileModal.js"; import useSideMenuStyles from "./UseSideMenuStyles.js"; -import EditProfileButton from "./EditProfileButton.js"; import MyQuestionButton from "./MyQuestionButton.js"; import LogoutButton from "./LogoutButton.js"; @@ -15,13 +12,9 @@ function SideMenuBody(props) {
- - - -
); } diff --git a/frontend/guest-app/src/components/SideMenu/SideMenuHeader.js b/frontend/guest-app/src/components/SideMenu/SideMenuHeader.js index 907e5ee3..8cc2fccf 100644 --- a/frontend/guest-app/src/components/SideMenu/SideMenuHeader.js +++ b/frontend/guest-app/src/components/SideMenu/SideMenuHeader.js @@ -1,4 +1,5 @@ import React from "react"; +import moment from "moment" import PropTypes from "prop-types"; import {Grid} from "@material-ui/core"; import {blue} from "@material-ui/core/colors"; @@ -8,12 +9,15 @@ import Typography from "@material-ui/core/Typography"; import useSideMenuStyles from "./UseSideMenuStyles.js"; const style = {background: blue[600]}; +const dateFormat="YYYY년 MM월 DD일 HH시 mm분"; function getDateRangeString(startAt, endAt) { const start = new Date(parseInt(startAt, 10)); const end = new Date(parseInt(endAt, 10)); + const startDate = moment(start).format(dateFormat); + const endDate = moment(end).format(dateFormat); - return `${end.getFullYear()}.${start.getDay()} ~ ${end.getFullYear()}.${end.getDay()}`; + return `${startDate} ~ ${endDate}`; } function SideMenuHeader(props) { diff --git a/frontend/guest-app/src/components/TabGroup/PollTabIcon.js b/frontend/guest-app/src/components/TabGroup/PollTabIcon.js new file mode 100644 index 00000000..c3e50f09 --- /dev/null +++ b/frontend/guest-app/src/components/TabGroup/PollTabIcon.js @@ -0,0 +1,18 @@ +import React from "react"; +import Badge from "@material-ui/core/Badge/Badge.js"; +import PollIcon from "@material-ui/icons/Poll.js"; + +const style = {marginRight: "8px"}; + +function PollTabIcon({showBadge}) { + const props = showBadge ? {color: "error", variant: "dot"} : {}; + + return ( + + + 투표 + + ); +} + +export default PollTabIcon; diff --git a/frontend/guest-app/src/components/TabGroup/TabIcons.js b/frontend/guest-app/src/components/TabGroup/QnATabIcon.js similarity index 54% rename from frontend/guest-app/src/components/TabGroup/TabIcons.js rename to frontend/guest-app/src/components/TabGroup/QnATabIcon.js index 53e1b384..6db3d86e 100644 --- a/frontend/guest-app/src/components/TabGroup/TabIcons.js +++ b/frontend/guest-app/src/components/TabGroup/QnATabIcon.js @@ -1,11 +1,10 @@ import React from "react"; import Badge from "@material-ui/core/Badge/Badge.js"; -import QuestionAnswerIcon from "@material-ui/icons/QuestionAnswer"; -import PollIcon from "@material-ui/icons/Poll"; +import QuestionAnswerIcon from "@material-ui/icons/QuestionAnswer.js"; const style = {marginRight: "8px"}; -export function QnATabIcon({showBadge}) { +function QnATabIcon({showBadge}) { const props = showBadge ? {color: "error", variant: "dot"} : {}; return ( @@ -16,13 +15,4 @@ export function QnATabIcon({showBadge}) { ); } -export function PollTabIcon({showBadge}) { - const props = showBadge ? {color: "error", variant: "dot"} : {}; - - return ( - - - 투표 - - ); -} +export default QnATabIcon; diff --git a/frontend/guest-app/src/components/TabGroup/TabGroup.js b/frontend/guest-app/src/components/TabGroup/TabGroup.js index 0ad87372..905190f0 100644 --- a/frontend/guest-app/src/components/TabGroup/TabGroup.js +++ b/frontend/guest-app/src/components/TabGroup/TabGroup.js @@ -2,12 +2,14 @@ import React from "react"; import Tabs from "@material-ui/core/Tabs"; import Tab from "@material-ui/core/Tab"; import styled from "styled-components"; -import {PollTabIcon, QnATabIcon} from "./TabIcons.js"; -import useTabs from "../../materialUIHooks/useTabs.js"; +import useTabs from "../../hooks/useTabs.js"; import TabBody from "./TabBody.js"; -import QuestionContainer from "../Question/QuestionContainer.js"; -import PollApollo from "../Poll/PollApollo.js"; -import {QuestionsProvider} from "../Question/QuestionsContext.js"; +import QuestionContainer from "../QuestionContainer/QuestionContainer.js"; +import QnATabIcon from "./QnATabIcon.js"; +import PollTabIcon from "./PollTabIcon.js"; +import QuestionsProvider from "../../contexts/Questions/QuestionsProvider.js"; +import PollsProvider from "../../contexts/Polls/PollsProvider.js"; +import PollContainer from "../Poll/PollContainer.js"; const TabGroupStyle = styled.div` position: fixed; @@ -34,7 +36,9 @@ function TabGroup({showQnABadge = true, showPollBadge}) { ); diff --git a/frontend/guest-app/src/components/UIController/UIController.js b/frontend/guest-app/src/components/UIController/UIController.js deleted file mode 100644 index dee874e4..00000000 --- a/frontend/guest-app/src/components/UIController/UIController.js +++ /dev/null @@ -1,35 +0,0 @@ -import React, {createContext, useContext} from "react"; -import useToggleReducer from "../Question/useToggleReducer.js"; - -const UIControlContext = createContext([]); - -const UIControlProvider = UIControlContext.Provider; - -function useUIControlReducers() { - const newQuestionInputDrawer = useToggleReducer(); - const editQuestionInputDrawer = useToggleReducer(); - const questionEditMenuReducer = useToggleReducer(); - const myQuestionDrawerReducer = useToggleReducer(); - - return { - newQuestionInputDrawer, - editQuestionInputDrawer, - questionEditMenuReducer, - myQuestionDrawerReducer, - }; -} - -export function UIController(props) { - const {children} = props; - const UIControlReducer = useUIControlReducers(); - - return ( - - {children} - - ); -} - -export function useUIControllerContext() { - return useContext(UIControlContext); -} diff --git a/frontend/guest-app/src/components/UserAvatar/AnonymousAvatar.js b/frontend/guest-app/src/components/UserAvatar/AnonymousAvatar.js new file mode 100644 index 00000000..360a0882 --- /dev/null +++ b/frontend/guest-app/src/components/UserAvatar/AnonymousAvatar.js @@ -0,0 +1,13 @@ +import Avatar from "@material-ui/core/Avatar"; +import PersonIcon from "@material-ui/icons/Person.js"; +import React from "react"; + +function AnonymousAvatar() { + return ( + + + + ); +} + +export default AnonymousAvatar; diff --git a/frontend/guest-app/src/components/UserAvatar/NamedAvatar.js b/frontend/guest-app/src/components/UserAvatar/NamedAvatar.js new file mode 100644 index 00000000..8d5a5eac --- /dev/null +++ b/frontend/guest-app/src/components/UserAvatar/NamedAvatar.js @@ -0,0 +1,21 @@ +import {makeStyles} from "@material-ui/core"; +import randomMC from "random-material-color"; +import Avatar from "@material-ui/core/Avatar"; +import React from "react"; + +function NamedAvatar({userName}) { + const useStyles = makeStyles({ + avatar: { + margin: 10, + }, + randomAvatar: { + backgroundColor: randomMC.getColor({text: userName}), + }, + }); + const classes = useStyles(); + const inner = userName.slice(0, 1); + + return {inner}; +} + +export default NamedAvatar; diff --git a/frontend/guest-app/src/components/UserAvatar/UserAvatar.js b/frontend/guest-app/src/components/UserAvatar/UserAvatar.js index 2d5d4abf..7d59c0df 100644 --- a/frontend/guest-app/src/components/UserAvatar/UserAvatar.js +++ b/frontend/guest-app/src/components/UserAvatar/UserAvatar.js @@ -1,32 +1,7 @@ import React from "react"; import PropTypes from "prop-types"; -import {makeStyles} from "@material-ui/core"; -import Avatar from "@material-ui/core/Avatar"; -import randomMC from "random-material-color"; -import PersonIcon from "@material-ui/icons/Person"; - -function NamedAvatar({userName}) { - const useStyles = makeStyles({ - avatar: { - margin: 10, - }, - randomAvatar: { - backgroundColor: randomMC.getColor({text: userName}), - }, - }); - const classes = useStyles(); - const inner = userName.slice(0, 1); - - return {inner}; -} - -function AnonymousAvatar() { - return ( - - - - ); -} +import AnonymousAvatar from "./AnonymousAvatar.js"; +import NamedAvatar from "./NamedAvatar.js"; function UserAvatar(props) { const {userNameRef = {current: null}, userName = ""} = props; diff --git a/frontend/guest-app/src/components/CommonComponent/CommonButtons/CancelButton.js b/frontend/guest-app/src/components/atoms/CancelButton.js similarity index 100% rename from frontend/guest-app/src/components/CommonComponent/CommonButtons/CancelButton.js rename to frontend/guest-app/src/components/atoms/CancelButton.js diff --git a/frontend/guest-app/src/components/CommonComponent/CommonButtons/ConfirmButton.js b/frontend/guest-app/src/components/atoms/ConfirmButton.js similarity index 100% rename from frontend/guest-app/src/components/CommonComponent/CommonButtons/ConfirmButton.js rename to frontend/guest-app/src/components/atoms/ConfirmButton.js diff --git a/frontend/guest-app/src/components/Question/QuestionInputArea/PaddingArea.js b/frontend/guest-app/src/components/atoms/PaddingArea.js similarity index 100% rename from frontend/guest-app/src/components/Question/QuestionInputArea/PaddingArea.js rename to frontend/guest-app/src/components/atoms/PaddingArea.js diff --git a/frontend/guest-app/src/components/TopProcessBar.js b/frontend/guest-app/src/components/atoms/TopProcessBar.js similarity index 100% rename from frontend/guest-app/src/components/TopProcessBar.js rename to frontend/guest-app/src/components/atoms/TopProcessBar.js diff --git a/frontend/guest-app/src/contexts/GlobalData/GlobalDataContext.js b/frontend/guest-app/src/contexts/GlobalData/GlobalDataContext.js new file mode 100644 index 00000000..9aa19149 --- /dev/null +++ b/frontend/guest-app/src/contexts/GlobalData/GlobalDataContext.js @@ -0,0 +1,5 @@ +import React from "react"; + +const GlobalDataContext = React.createContext({}); + +export default GlobalDataContext; diff --git a/frontend/guest-app/src/contexts/GlobalData/GlobalDataProvider.js b/frontend/guest-app/src/contexts/GlobalData/GlobalDataProvider.js new file mode 100644 index 00000000..16370ff5 --- /dev/null +++ b/frontend/guest-app/src/contexts/GlobalData/GlobalDataProvider.js @@ -0,0 +1,44 @@ +import React from "react"; +import {useQuery} from "@apollo/react-hooks"; +import {GET_GUEST_APP_GLOBAL_DATA} from "../../apollo/gqlSchemes.js"; +import TopProgressBar from "../../components/atoms/TopProcessBar.js"; +import config from "../../config"; +import {createSocketIOClient, SocketClientProvider} from "../../socket.io"; +import GlobalDataContext from "./GlobalDataContext.js"; + +function GlobalDataProvider(props) { + const {data, loading, error} = useQuery(GET_GUEST_APP_GLOBAL_DATA); + + if (loading) { + return ; + } + + if (error) { + window.location.href = config.inValidGuestRedirectURL; + return
; + } + + const {event, guest} = data.guestInEvent; + const globalData = {event, guest}; + + const client = createSocketIOClient({ + host: config.socketIOHost, + port: config.socketIOPort, + namespace: "event", + room: event.id, + }); + + client.on("connect", () => { + client.emit("joinRoom", {room: event.id}); + }); + + return ( + + + {props.children} + + + ); +} + +export default GlobalDataProvider; diff --git a/frontend/guest-app/src/contexts/GlobalData/useGlobalData.js b/frontend/guest-app/src/contexts/GlobalData/useGlobalData.js new file mode 100644 index 00000000..7b5dbe7e --- /dev/null +++ b/frontend/guest-app/src/contexts/GlobalData/useGlobalData.js @@ -0,0 +1,8 @@ +import {useContext} from "react"; +import GlobalDataContext from "./GlobalDataContext.js"; + +function useGlobalData() { + return useContext(GlobalDataContext); +} + +export default useGlobalData; diff --git a/frontend/guest-app/src/contexts/Polls/PollsContext.js b/frontend/guest-app/src/contexts/Polls/PollsContext.js new file mode 100644 index 00000000..792ac128 --- /dev/null +++ b/frontend/guest-app/src/contexts/Polls/PollsContext.js @@ -0,0 +1,5 @@ +import {createContext} from "react"; + +const PollsContext = createContext({}); + +export default PollsContext; diff --git a/frontend/guest-app/src/contexts/Polls/PollsProvider.js b/frontend/guest-app/src/contexts/Polls/PollsProvider.js new file mode 100644 index 00000000..f4c7754a --- /dev/null +++ b/frontend/guest-app/src/contexts/Polls/PollsProvider.js @@ -0,0 +1,27 @@ +import {useQuery} from "@apollo/react-hooks"; +import React from "react"; +import useGlobalData from "../GlobalData/useGlobalData.js"; +import PollsContext from "./PollsContext.js"; +import {POLL_QUERY} from "../../apollo/gqlSchemes.js"; + +function PollsProvider(props) { + const {event, guest} = useGlobalData(); + const options = { + variables: { + EventId: event.id, + guestId: guest.id, + }, + }; + const {loading, error, data} = useQuery(POLL_QUERY, options); + + if (loading) return

Loading...

; + if (error) return

Error :(

; + + return ( + + {props.children} + + ); +} + +export default PollsProvider; diff --git a/frontend/guest-app/src/contexts/Polls/usePolls.js b/frontend/guest-app/src/contexts/Polls/usePolls.js new file mode 100644 index 00000000..f3af9732 --- /dev/null +++ b/frontend/guest-app/src/contexts/Polls/usePolls.js @@ -0,0 +1,8 @@ +import {useContext} from "react"; +import PollsContext from "./PollsContext.js"; + +function usePolls() { + return useContext(PollsContext); +} + +export default usePolls; diff --git a/frontend/guest-app/src/contexts/Questions/QuestionsContext.js b/frontend/guest-app/src/contexts/Questions/QuestionsContext.js new file mode 100644 index 00000000..cb07e0c5 --- /dev/null +++ b/frontend/guest-app/src/contexts/Questions/QuestionsContext.js @@ -0,0 +1,5 @@ +import {createContext} from "react"; + +const QuestionsContext = createContext([]); + +export default QuestionsContext; diff --git a/frontend/guest-app/src/components/Question/QuestionsContext.js b/frontend/guest-app/src/contexts/Questions/QuestionsProvider.js similarity index 74% rename from frontend/guest-app/src/components/Question/QuestionsContext.js rename to frontend/guest-app/src/contexts/Questions/QuestionsProvider.js index b28d737a..4267458f 100644 --- a/frontend/guest-app/src/components/Question/QuestionsContext.js +++ b/frontend/guest-app/src/contexts/Questions/QuestionsProvider.js @@ -1,14 +1,11 @@ -import React, {createContext, useContext, useEffect, useReducer} from "react"; +import React, {useEffect, useReducer} from "react"; import {useQuery} from "@apollo/react-hooks"; -import {GuestGlobalContext} from "../../libs/guestGlobalContext.js"; -import { - buildQuestions, - QUERY_INIT_QUESTIONS, -} from "../../libs/useQueryQuestions.js"; -import {useSocket} from "../../libs/socketIoClientProvider.js"; -import QuestionsRepliesReducer from "./QuestionsRepliesReducer.js"; - -const QuestionsContext = createContext([]); +import {QUERY_INIT_QUESTIONS} from "../../apollo/gqlSchemes.js"; +import QuestionsRepliesReducer from "../../reducers/QuestionsRepliesReducer.js"; +import {useSocket} from "../../socket.io"; +import buildQuestions from "../../apollo/asembleGetQuestionQuerys.js"; +import QuestionsContext from "./QuestionsContext.js"; +import useGlobalData from "../GlobalData/useGlobalData.js"; const useDataLoadEffect = (dispatch, data) => { useEffect(() => { @@ -67,9 +64,9 @@ const useSocketHandler = (dispatch, guestGlobal) => { }); }; -export function QuestionsProvider(props) { +function QuestionsProvider(props) { const {children} = props; - const {event, guest} = useContext(GuestGlobalContext); + const {event, guest} = useGlobalData(); const {data, loading, error} = useQuery(QUERY_INIT_QUESTIONS, { variables: {EventId: event.id, GuestId: guest.id}, }); @@ -78,7 +75,9 @@ export function QuestionsProvider(props) { useDataLoadEffect(dispatch, data); useSocketHandler(dispatch, guest); - const questions = state.filter(question => (question.QuestionId === null && question.state === "active")); + const questions = state.filter( + question => question.QuestionId === null && question.state === "active", + ); const replies = state.filter(question => question.QuestionId !== null); const value = { @@ -96,6 +95,4 @@ export function QuestionsProvider(props) { ); } -export function useQuestions() { - return useContext(QuestionsContext); -} +export default QuestionsProvider; diff --git a/frontend/guest-app/src/contexts/Questions/useQuestions.js b/frontend/guest-app/src/contexts/Questions/useQuestions.js new file mode 100644 index 00000000..9887dcab --- /dev/null +++ b/frontend/guest-app/src/contexts/Questions/useQuestions.js @@ -0,0 +1,8 @@ +import {useContext} from "react"; +import QuestionsContext from "./QuestionsContext.js"; + +function useQuestions() { + return useContext(QuestionsContext); +} + +export default useQuestions; diff --git a/frontend/guest-app/src/contexts/UIController/UIControllerContext.js b/frontend/guest-app/src/contexts/UIController/UIControllerContext.js new file mode 100644 index 00000000..147261e7 --- /dev/null +++ b/frontend/guest-app/src/contexts/UIController/UIControllerContext.js @@ -0,0 +1,5 @@ +import {createContext} from "react"; + +const UIControlContext = createContext([]); + +export default UIControlContext; diff --git a/frontend/guest-app/src/contexts/UIController/UIControllerProvider.js b/frontend/guest-app/src/contexts/UIController/UIControllerProvider.js new file mode 100644 index 00000000..b66365d3 --- /dev/null +++ b/frontend/guest-app/src/contexts/UIController/UIControllerProvider.js @@ -0,0 +1,16 @@ +import React from "react"; +import UIControlContext from "./UIControllerContext.js"; +import useUIControllerReducers from "./useUIControllerReducers.js"; + +function UIControllerProvider(props) { + const {children} = props; + const UIControlReducer = useUIControllerReducers(); + + return ( + + {children} + + ); +} + +export default UIControllerProvider; diff --git a/frontend/guest-app/src/contexts/UIController/useUIController.js b/frontend/guest-app/src/contexts/UIController/useUIController.js new file mode 100644 index 00000000..2addcc9b --- /dev/null +++ b/frontend/guest-app/src/contexts/UIController/useUIController.js @@ -0,0 +1,8 @@ +import {useContext} from "react"; +import UIControlContext from "./UIControllerContext.js"; + +function useUIController() { + return useContext(UIControlContext); +} + +export default useUIController; diff --git a/frontend/guest-app/src/contexts/UIController/useUIControllerReducers.js b/frontend/guest-app/src/contexts/UIController/useUIControllerReducers.js new file mode 100644 index 00000000..256db977 --- /dev/null +++ b/frontend/guest-app/src/contexts/UIController/useUIControllerReducers.js @@ -0,0 +1,17 @@ +import useToggle from "../../hooks/useToggle.js"; + +function useUIControllerReducers() { + const newQuestionInputDrawer = useToggle(); + const editQuestionInputDrawer = useToggle(); + const questionEditMenuReducer = useToggle(); + const myQuestionDrawerReducer = useToggle(); + + return { + newQuestionInputDrawer, + editQuestionInputDrawer, + questionEditMenuReducer, + myQuestionDrawerReducer, + }; +} + +export default useUIControllerReducers; diff --git a/frontend/guest-app/src/materialUIHooks/useDrawer.js b/frontend/guest-app/src/hooks/useDrawer.js similarity index 100% rename from frontend/guest-app/src/materialUIHooks/useDrawer.js rename to frontend/guest-app/src/hooks/useDrawer.js diff --git a/frontend/guest-app/src/components/Reply/useReplies.js b/frontend/guest-app/src/hooks/useReplies.js similarity index 100% rename from frontend/guest-app/src/components/Reply/useReplies.js rename to frontend/guest-app/src/hooks/useReplies.js diff --git a/frontend/guest-app/src/components/UserAvatar/useStringState.js b/frontend/guest-app/src/hooks/useStringState.js similarity index 100% rename from frontend/guest-app/src/components/UserAvatar/useStringState.js rename to frontend/guest-app/src/hooks/useStringState.js diff --git a/frontend/guest-app/src/materialUIHooks/useTabs.js b/frontend/guest-app/src/hooks/useTabs.js similarity index 100% rename from frontend/guest-app/src/materialUIHooks/useTabs.js rename to frontend/guest-app/src/hooks/useTabs.js diff --git a/frontend/guest-app/src/components/Question/useToggleReducer.js b/frontend/guest-app/src/hooks/useToggle.js similarity index 75% rename from frontend/guest-app/src/components/Question/useToggleReducer.js rename to frontend/guest-app/src/hooks/useToggle.js index 89292f68..6343a07b 100644 --- a/frontend/guest-app/src/components/Question/useToggleReducer.js +++ b/frontend/guest-app/src/hooks/useToggle.js @@ -1,7 +1,7 @@ import {useReducer} from "react"; -import ToggleReducer from "./ToggleReducer.js"; +import ToggleReducer from "../reducers/ToggleReducer.js"; -function useToggleReducer() { +function useToggle() { const [state, dispatch] = useReducer(ToggleReducer, { state: false, data: {}, @@ -17,4 +17,4 @@ function useToggleReducer() { }; } -export default useToggleReducer; +export default useToggle; diff --git a/frontend/guest-app/src/index.css b/frontend/guest-app/src/index.css index fd2c06f6..60a96d27 100644 --- a/frontend/guest-app/src/index.css +++ b/frontend/guest-app/src/index.css @@ -11,3 +11,12 @@ code { font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; } + +/* focus 시 생기는 파란 외각선 제거*/ +input, +textarea, +select, +a, +button:focus { + outline: none; +} diff --git a/frontend/guest-app/src/index.js b/frontend/guest-app/src/index.js index c6c7ea1b..54c3c7c9 100644 --- a/frontend/guest-app/src/index.js +++ b/frontend/guest-app/src/index.js @@ -1,25 +1,11 @@ import "bootstrap/dist/css/bootstrap.css"; import React from "react"; import ReactDOM from "react-dom"; -import Cookies from "js-cookie"; -import {ApolloProvider} from "@apollo/react-hooks"; -import creaetApolloClient from "./libs/createApolloClient"; import "./index.css"; import App from "./App/App.js"; import * as serviceWorker from "./libs/serviceWorker.js"; -import config from "./config"; -const cookieName = "vaagle-guest"; -const token = Cookies.get(cookieName); -const client = creaetApolloClient(config.apolloURI, token); - - -ReactDOM.render( - - - , - document.getElementById("root"), -); +ReactDOM.render(, document.getElementById("root")); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. diff --git a/frontend/guest-app/src/libs/guestGlobalContext.js b/frontend/guest-app/src/libs/guestGlobalContext.js deleted file mode 100644 index f30db7cd..00000000 --- a/frontend/guest-app/src/libs/guestGlobalContext.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from "react"; - -const GuestGlobalContext = React.createContext({}); -const GuestGlobalProvider = GuestGlobalContext.Provider; - -export {GuestGlobalContext, GuestGlobalProvider}; diff --git a/frontend/guest-app/src/libs/useQueryQuestions.js b/frontend/guest-app/src/libs/useQueryQuestions.js deleted file mode 100644 index 71565aa5..00000000 --- a/frontend/guest-app/src/libs/useQueryQuestions.js +++ /dev/null @@ -1,90 +0,0 @@ -import {gql} from "apollo-boost"; -import {JSONNestJoin, JSONNestJoin2} from "./utils.js"; -import _ from "lodash"; - -export function buildQuestions(object) { - const copyData = _.cloneDeep(object); - let {questions, emojis, emojiPicks, guests, didILikes} = copyData; - - questions = JSONNestJoin2(questions, guests, "GuestId", "id", (a, b) => { - a.guestName = b.name; - a.isAnonymous = b.isAnonymous; - - return a; - }); - - questions = questions.map(x => { - x.didILike = false; - return x; - }); - - questions = JSONNestJoin( - questions, - didILikes, - "id", - "QuestionId", - (x, y) => { - x.didILike = true; - return x; - }, - ); - - emojis = emojis.map(x => { - x.key = `${x.QuestionId}_${x.name}`; - x.didIPick = false; - return x; - }); - emojiPicks = emojiPicks.map(x => { - x.key = `${x.QuestionId}_${x.name}`; - return x; - }); - emojis = JSONNestJoin(emojis, emojiPicks, "key", "key", (a, b) => { - a.didIPick = true; - return a; - }); - - questions.map(x => { - x.emojis = []; - return x; - }); - questions = JSONNestJoin(questions, emojis, "id", "QuestionId", (a, b) => { - a.emojis.push(b); - return a; - }); - - return questions; -} - -export const QUERY_INIT_QUESTIONS = gql` - query getQuestions($EventId: ID!, $GuestId: ID!) { - questions(EventId: $EventId) { - id - EventId - GuestId - createdAt - content - state - isStared - likeCount - QuestionId - } - emojis(EventId: $EventId) { - name - count - QuestionId - createdAt - } - emojiPicks(EventId: $EventId, GuestId: $GuestId) { - name - QuestionId - } - guests(EventId: $EventId) { - id - name - isAnonymous - } - didILikes(GuestId: $GuestId) { - QuestionId - } - } -`; diff --git a/frontend/guest-app/src/libs/utils.js b/frontend/guest-app/src/libs/utils.js deleted file mode 100644 index 08f16e9c..00000000 --- a/frontend/guest-app/src/libs/utils.js +++ /dev/null @@ -1,41 +0,0 @@ -function mappingByKey(object, key) { - const mappped = {}; - - object.forEach(x => { - mappped[x[key]] = x; - }); - - return mappped; -} - -function unMappingByKey(object) { - return Object.values(object); -} - -export function JSONNestJoin(parents, childs, parentKey, childKey, func) { - const mapped = mappingByKey(parents, parentKey); - - childs.forEach(child => { - const joinValue = child[childKey]; - - if (mapped[joinValue]) { - const parentElement = mapped[joinValue]; - - mapped[joinValue] = func(parentElement, child); - } - }); - - return unMappingByKey(mapped); -} - -export function JSONNestJoin2(parents, childs, parentKey, childKey, func) { - const mapped = mappingByKey(childs, childKey); - parents.forEach(parent => { - const joinValue = parent[parentKey]; - if (mapped[joinValue]) { - const childElement = mapped[joinValue]; - parent = func(parent, childElement); - } - }); - return parents; -} diff --git a/frontend/guest-app/src/components/Poll/PollReducer.js b/frontend/guest-app/src/reducers/PollsReducer.js similarity index 60% rename from frontend/guest-app/src/components/Poll/PollReducer.js rename to frontend/guest-app/src/reducers/PollsReducer.js index f6ad4a63..c487760b 100644 --- a/frontend/guest-app/src/components/Poll/PollReducer.js +++ b/frontend/guest-app/src/reducers/PollsReducer.js @@ -1,12 +1,16 @@ -import {socketClient} from "../../libs/socketIoClientProvider.js"; +import {socketClient} from "../socket.io"; // allowDuplication == false, 즉, 복수선택이 아닌 경우, 이전에 vote 한 candidate가 있으면 삭제해야 함 const getCandidateToDelete = (items, candidateId) => { - const candidate = items.filter( + const candidates = items.filter( item => item.voted && item.id !== candidateId, - )[0]; + ); - return candidate ? candidate.id : null; + if (candidates.length > 0) { + return candidates[0].id; + } else { + return null; + } }; // 복수선택이 아닌 투표의 경우, 다른 선택된 항목을 uncheck 하는 함수 @@ -30,6 +34,7 @@ const updateItems = (items, number, allowDuplication) => { if (!allowDuplication) { uncheckOtherItems(newItems); } + newItems[number].voted = true; newItems[number].voters++; } @@ -74,7 +79,7 @@ const updateFirstPlace = poll => { let firstPlaceValue = 0; newPoll.nItems.forEach((item, index) => { - if (item.voters == firstPlaceValue) { + if (item.voters === firstPlaceValue) { firstPlaceIndex.push(index); } else if (item.voters > firstPlaceValue) { firstPlaceIndex = []; @@ -92,10 +97,47 @@ const updateFirstPlace = poll => { newPoll.nItems[i].firstPlace = true; }); - console.log("firstPlace", firstPlaceIndex, newPoll.nItems); return newPoll; }; +/** + * + * @param {dispatch의 action에 해당하는 object} vote + * dispatch({ + type: "VOTE", + pollId: id, + candidateId, + number, + GuestId, + }); + * @param {object} poll + type Candidate { + id: Int! + number: Int! + content: String! + voters: Int! + voted: Boolean + firstPlace: Boolean + } + type Poll { + id: Int! + pollName: String! + pollType: String! + selectionType: String! + allowDuplication: Boolean! + state: String! + pollDate: String + totalVoters: Int! + createdAt: String! + nItems: [Candidate]! + ratingValue: Int! + rated: Boolean! + } + * @param {int} candidateToDelete + * 복수선택이 안되는 투표의 경우, + * 이미 다른 candidate에 투표를 한 상황에서 다시 다른 candidate을 투표하는 경우, + * 예전에 투표한 candidate에 투표했다는 정보를 DB에서 삭제하기 위함 + */ const emitVoteData = (vote, poll, candidateToDelete) => { const action = poll.nItems[vote.number].voted ? "on" : "off"; const data = { @@ -106,12 +148,48 @@ const emitVoteData = (vote, poll, candidateToDelete) => { candidateToDelete, }; - // console.log("emitVoteData", data); socketClient.emit(`vote/${action}`, data); }; +/** + * + * @param {dispatch의 action에 해당하는 object} rate + * dispatch({ + type: "RATE", + value, + pollId: id, + GuestId, + }); + * @param {object} poll + type Candidate { + id: Int! + number: Int! + content: String! + voters: Int! + voted: Boolean + firstPlace: Boolean + } + type Poll { + id: Int! + pollName: String! + pollType: String! + selectionType: String! + allowDuplication: Boolean! + state: String! + pollDate: String + totalVoters: Int! + createdAt: String! + nItems: [Candidate]! + ratingValue: Int! + rated: Boolean! + } + * @param {int} candidateId + 투표한 candidate의 id + * @param {int} index + 투표한 candidate가 array에서 위치한 index + */ const emitRateData = (rate, poll, candidateId, index) => { - const action = rate.type === "RATE" ? "on" : "off"; // "on" or "off" + const action = rate.type === "RATE" ? "on" : "off"; const data = { GuestId: rate.GuestId, CandidateId: candidateId, @@ -127,28 +205,39 @@ export default function reducer(polls, action) { let index; let candidateId; - if (action.id) { - thePoll = polls.filter(poll => poll.id === action.id)[0]; + const {pollId} = action; + + if (pollId) { + thePoll = polls.filter(poll => poll.id === pollId)[0]; } switch (action.type) { - case "NOTIFY_OPEN": + // host가 생성한 투표를 host가 open 함 + case "NOTIFY_OPEN": { return [action.poll, ...polls]; - case "NOTIFY_CLOSE": - return polls.filter(poll => poll.id !== action.id); - case "SOMEONE_VOTE": + } + // host가 open한 투표를 close 함 + case "NOTIFY_CLOSE": { + return polls.filter(poll => poll.id !== pollId); + } + // 나 또는 남이 vote(N지선다) 했음을 알려줌 + case "SOMEONE_VOTE": { thePoll.totalVoters = action.poll.totalVoters; thePoll.nItems.forEach((item, index) => { item.voters = action.poll.nItems[index].voters; item.firstPlace = action.poll.nItems[index].firstPlace; }); - // console.log("SOMEONE_VOTE", thePoll); - return polls.map(poll => (poll.id === action.id ? thePoll : poll)); - case "SOMEONE_RATE": + + return polls.map(poll => (poll.id === pollId ? thePoll : poll)); + } + // 나 또는 남이 rate(별점매기기) 했음을 알려줌 + case "SOMEONE_RATE": { thePoll.totalVoters = action.poll.totalVoters; - // console.log("SOMEONE_RATE", thePoll); - return polls.map(poll => (poll.id === action.id ? thePoll : poll)); - case "VOTE": + + return polls.map(poll => (poll.id === pollId ? thePoll : poll)); + } + // 내가 vote(N지선다) 했음 + case "VOTE": { const notVoted = thePoll.nItems.every(item => item.voted === false); let candidateToDelete = null; @@ -158,6 +247,7 @@ export default function reducer(polls, action) { action.candidateId, ); } + thePoll = { ...thePoll, nItems: updateItems( @@ -174,12 +264,15 @@ export default function reducer(polls, action) { thePoll = updateFirstPlace(thePoll); emitVoteData(action, thePoll, candidateToDelete); - return polls.map(poll => (poll.id === action.id ? thePoll : poll)); - case "RATE": + return polls.map(poll => (poll.id === pollId ? thePoll : poll)); + } + // 내가 rate(별점매기기) 했음 + case "RATE": { if (thePoll.rated) { return polls; } + thePoll = { ...thePoll, nItems: updateRatingItem(thePoll.nItems, action.value, true), @@ -187,17 +280,20 @@ export default function reducer(polls, action) { ratingValue: action.value, totalVoters: thePoll.totalVoters + 1, }; - // console.log("RATE", action, thePoll); - index = parseInt(action.value) - 1; + + index = parseInt(action.value, 10) - 1; candidateId = thePoll.nItems[index].id; emitRateData(action, thePoll, candidateId, index); - return polls.map(poll => (poll.id === action.id ? thePoll : poll)); - case "CANCEL_RATING": + return polls.map(poll => (poll.id === pollId ? thePoll : poll)); + } + // 내가 rate(별점매기기)를 취소했음 + case "CANCEL_RATING": { if (!thePoll.rated) { return polls; } - index = parseInt(thePoll.ratingValue) - 1; + + index = parseInt(thePoll.ratingValue, 10) - 1; thePoll = { ...thePoll, nItems: updateRatingItem( @@ -209,10 +305,11 @@ export default function reducer(polls, action) { ratingValue: 0, totalVoters: thePoll.totalVoters - 1, }; - // console.log("CANCEL_RATE", action, thePoll); candidateId = thePoll.nItems[index].id; emitRateData(action, thePoll, candidateId, index); - return polls.map(poll => (poll.id === action.id ? thePoll : poll)); + + return polls.map(poll => (poll.id === pollId ? thePoll : poll)); + } default: throw new Error("Unhandled action."); diff --git a/frontend/guest-app/src/components/Question/QuestionsRepliesReducer.js b/frontend/guest-app/src/reducers/QuestionsRepliesReducer.js similarity index 89% rename from frontend/guest-app/src/components/Question/QuestionsRepliesReducer.js rename to frontend/guest-app/src/reducers/QuestionsRepliesReducer.js index de9ae03f..a36887ff 100644 --- a/frontend/guest-app/src/components/Question/QuestionsRepliesReducer.js +++ b/frontend/guest-app/src/reducers/QuestionsRepliesReducer.js @@ -1,6 +1,6 @@ import _ from "lodash"; -const compareByDate = (a, b) => b.createdAt.localeCompare(a.createdAt); +const compareByDate = (a, b) => a.createdAt.localeCompare(b.createdAt); const compareByLikeCount = (a, b) => b.likeCount - a.likeCount; @@ -16,7 +16,8 @@ const onSortByLikeCount = state => [...state.sort(compareByLikeCount)]; const onQuestionLike = (state, data) => { const guestGlobal = data.guestGlobal; - const newState = state.map(x => { + + return state.map(x => { const {QuestionId, GuestId} = data; if (x.id !== QuestionId) { @@ -33,8 +34,6 @@ const onQuestionLike = (state, data) => { return newX; }); - - return newState; }; const onUndoQuestionLike = (state, data) => { @@ -132,7 +131,8 @@ const onRemoveQuestionEmoji = (state, data) => { return newState; }; -const onRemoveQuestion = (state, data) => _.cloneDeep(state).filter(x => x.id !== data.id); +const onRemoveQuestion = (state, data) => + _.cloneDeep(state).filter(x => x.id !== data.id); const onUpdateQuestion = (state, data) => { const newState = _.cloneDeep(state); @@ -157,13 +157,17 @@ const onMoveQuestion = (state, data) => { if (data.id === "all") { return newState.map(e => { - if (e.state === data.from) { e.state = data.to; } + if (e.state === data.from) { + e.state = data.to; + } return e; }); } return newState.map(e => { - if (e.id === data.id) { e.state = data.to; } + if (e.id === data.id) { + e.state = data.to; + } return e; }); }; @@ -172,7 +176,11 @@ const onToggleStarQuestion = (state, data) => { const newState = _.cloneDeep(state); newState.map(e => { - if (e.id === data.id) { e.isStared = data.isStared; } else { e.isStared = false; } + if (e.id === data.id) { + e.isStared = data.isStared; + } else { + e.isStared = false; + } return e; }); @@ -199,6 +207,7 @@ const QuestionsRepliesReducer = (state, action) => { }; if (!(type in actionTable)) { + // eslint-disable-next-line no-console console.error(`unexpected action.type: ${type}`); return state; } diff --git a/frontend/guest-app/src/reducers/ToggleReducer.js b/frontend/guest-app/src/reducers/ToggleReducer.js new file mode 100644 index 00000000..c01e9bab --- /dev/null +++ b/frontend/guest-app/src/reducers/ToggleReducer.js @@ -0,0 +1,48 @@ +import _ from "lodash"; + +const actionTable = { + toggle: (state, data) => { + const newState = _.cloneDeep(state); + + newState.data = _.cloneDeep(data); + newState.state = !newState.state; + + return newState; + }, + on: (state, data) => { + const newState = _.cloneDeep(state); + + newState.data = _.cloneDeep(data); + newState.state = true; + + return newState; + }, + off: (state, data) => { + const newState = _.cloneDeep(state); + + newState.data = _.cloneDeep(data); + newState.state = false; + + return newState; + }, +}; + +const ToggleReducer = (state, action) => { + const {type, data} = action; + + if (!(type in actionTable)) { + // eslint-disable-next-line no-console + console.error(`unexpected action.type: ${type}`); + return state; + } + + try { + return actionTable[type](state, data); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + return state; + } +}; + +export default ToggleReducer; diff --git a/frontend/guest-app/src/libs/socketIoClientProvider.js b/frontend/guest-app/src/socket.io/index.js similarity index 61% rename from frontend/guest-app/src/libs/socketIoClientProvider.js rename to frontend/guest-app/src/socket.io/index.js index 553cf265..8cd5953d 100644 --- a/frontend/guest-app/src/libs/socketIoClientProvider.js +++ b/frontend/guest-app/src/socket.io/index.js @@ -6,37 +6,50 @@ function combineURL(host, port, nameSpace) { return nameSpace ? `${host}:${port}/${nameSpace}` : `${host}:${port}`; } -export function createSocketIOClient({host, port, namespace, room}) { - const cookieName = "vaagle-guest"; - const token = Cookie.get(cookieName); - const URL = combineURL(host, port, namespace); - const socket = io(URL, {query: {token: token}}); - +function addBoilerplateSocketListener({socket, URL, room}) { socket.on("connect", async () => { + // eslint-disable-next-line no-console console.log( `socket.io client connect to ${URL} as ${process.env.NODE_ENV} mode`, ); }); socket.on("joinRoom", () => { + // eslint-disable-next-line no-console console.log(`join room success at ${room}`); }); socket.on("leaveRoom", () => { + // eslint-disable-next-line no-console console.log(`leave room success at ${room}`); }); - socket.on("disconnect", (reason) => { + socket.on("disconnect", reason => { + // eslint-disable-next-line no-console console.log(`io client disconnected by ${reason}`); }); - socket.on("reconnect", (attemptNumber) => { + socket.on("reconnect", attemptNumber => { + // eslint-disable-next-line no-console console.log(`io reconnect attempt ${attemptNumber}`); }); - socket.on("error", (error) => { + socket.on("error", error => { + // eslint-disable-next-line no-console console.log(`io error raise ${error}`); }); +} + +export function createSocketIOClient({host, port, namespace, room}) { + const cookieName = "vaagle-guest"; + const token = Cookie.get(cookieName); + + const URL = combineURL(host, port, namespace); + const socket = io(URL, {query: {token}}); + + if (process.env.NODE_ENV === "development") { + addBoilerplateSocketListener({socket, URL, room}); + } return socket; } @@ -45,12 +58,11 @@ export let socketClient = null; export let useSocket = null; -export function SocketIoClientProvider(props) { +export function SocketClientProvider(props) { const {client, children} = props; socketClient = client; - useSocket = (eventName = "EMIT", handler = () => { - }) => { + useSocket = (eventName = "EMIT", handler = () => {}) => { socketClient.off(eventName); socketClient.on(eventName, handler); }; @@ -58,8 +70,8 @@ export function SocketIoClientProvider(props) { const emit = socketClient.emit; return ( - ${children} + + ${children} + ); } - - diff --git a/frontend/guest-app/yarn.lock b/frontend/guest-app/yarn.lock index 17005bb6..6d21898a 100644 --- a/frontend/guest-app/yarn.lock +++ b/frontend/guest-app/yarn.lock @@ -7047,6 +7047,11 @@ mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: dependencies: minimist "0.0.8" +moment@^2.24.0: + version "2.24.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" + integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" diff --git a/frontend/host-app/package.json b/frontend/host-app/package.json index 5840158a..61842bfd 100644 --- a/frontend/host-app/package.json +++ b/frontend/host-app/package.json @@ -63,6 +63,7 @@ "random-material-color": "^1.0.5", "react": "^16.11.0", "react-app-polyfill": "^1.0.4", + "react-custom-scrollbars": "^4.2.1", "react-dev-utils": "^9.1.0", "react-dom": "^16.11.0", "react-icons": "^3.8.0", diff --git a/frontend/host-app/public/android-icon-144x144.png b/frontend/host-app/public/android-icon-144x144.png deleted file mode 100644 index b65cbf87..00000000 Binary files a/frontend/host-app/public/android-icon-144x144.png and /dev/null differ diff --git a/frontend/host-app/public/android-icon-192x192.png b/frontend/host-app/public/android-icon-192x192.png deleted file mode 100644 index 6f1148bd..00000000 Binary files a/frontend/host-app/public/android-icon-192x192.png and /dev/null differ diff --git a/frontend/host-app/public/android-icon-36x36.png b/frontend/host-app/public/android-icon-36x36.png deleted file mode 100644 index fc96f1dd..00000000 Binary files a/frontend/host-app/public/android-icon-36x36.png and /dev/null differ diff --git a/frontend/host-app/public/android-icon-48x48.png b/frontend/host-app/public/android-icon-48x48.png deleted file mode 100644 index 5cdde9da..00000000 Binary files a/frontend/host-app/public/android-icon-48x48.png and /dev/null differ diff --git a/frontend/host-app/public/android-icon-72x72.png b/frontend/host-app/public/android-icon-72x72.png deleted file mode 100644 index 6c2dcd4a..00000000 Binary files a/frontend/host-app/public/android-icon-72x72.png and /dev/null differ diff --git a/frontend/host-app/public/android-icon-96x96.png b/frontend/host-app/public/android-icon-96x96.png deleted file mode 100644 index 0ac97f36..00000000 Binary files a/frontend/host-app/public/android-icon-96x96.png and /dev/null differ diff --git a/frontend/host-app/public/apple-icon.png b/frontend/host-app/public/apple-icon.png deleted file mode 100644 index 9d0064a4..00000000 Binary files a/frontend/host-app/public/apple-icon.png and /dev/null differ diff --git a/frontend/host-app/public/favicon-16x16.png b/frontend/host-app/public/favicon-16x16.png deleted file mode 100644 index c0db580c..00000000 Binary files a/frontend/host-app/public/favicon-16x16.png and /dev/null differ diff --git a/frontend/host-app/public/favicon-32x32.png b/frontend/host-app/public/favicon-32x32.png deleted file mode 100644 index 8b271bda..00000000 Binary files a/frontend/host-app/public/favicon-32x32.png and /dev/null differ diff --git a/frontend/host-app/public/favicon-96x96.png b/frontend/host-app/public/favicon-96x96.png deleted file mode 100644 index 0ac97f36..00000000 Binary files a/frontend/host-app/public/favicon-96x96.png and /dev/null differ diff --git a/frontend/host-app/public/logo192.png b/frontend/host-app/public/logo192.png deleted file mode 100644 index fa313abf..00000000 Binary files a/frontend/host-app/public/logo192.png and /dev/null differ diff --git a/frontend/host-app/public/logo512.png b/frontend/host-app/public/logo512.png deleted file mode 100644 index bd5d4b5e..00000000 Binary files a/frontend/host-app/public/logo512.png and /dev/null differ diff --git a/frontend/host-app/public/manifest.json b/frontend/host-app/public/manifest.json index b8ac14a7..144fa033 100644 --- a/frontend/host-app/public/manifest.json +++ b/frontend/host-app/public/manifest.json @@ -6,48 +6,6 @@ "src": "favicon.ico", "sizes": "96x96 32x32 16x16", "type": "image/x-icon" - }, - { - "src": "\/android-icon-36x36.png", - "sizes": "36x36", - "type": "image\/png", - "density": "0.75" - }, - { - "src": "\/android-icon-48x48.png", - "sizes": "48x48", - "type": "image\/png", - "density": "1.0" - }, - { - "src": "\/android-icon-72x72.png", - "sizes": "72x72", - "type": "image\/png", - "density": "1.5" - }, - { - "src": "\/android-icon-96x96.png", - "sizes": "96x96", - "type": "image\/png", - "density": "2.0" - }, - { - "src": "\/android-icon-144x144.png", - "sizes": "144x144", - "type": "image\/png", - "density": "3.0" - }, - { - "src": "\/android-icon-192x192.png", - "sizes": "192x192", - "type": "image\/png", - "density": "4.0" - }, - { - "src": "\/android-icon-192x192.png", - "sizes": "192x192", - "type": "image\/png", - "density": "4.0" } ], "start_url": ".", diff --git a/frontend/host-app/src/App/App.css b/frontend/host-app/src/App/App.css index 54453565..dceb6ab1 100644 --- a/frontend/host-app/src/App/App.css +++ b/frontend/host-app/src/App/App.css @@ -1,26 +1,3 @@ -/* .App { - text-align: center; -} - -.App-logo { - height: 40vmin; -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #09d3ac; -} */ - .App { width: 100%; height: 100%; diff --git a/frontend/host-app/src/App/App.js b/frontend/host-app/src/App/App.js index 74a675a0..c5ee9274 100644 --- a/frontend/host-app/src/App/App.js +++ b/frontend/host-app/src/App/App.js @@ -1,61 +1,73 @@ import React, {useState} from "react"; -import {Skeleton} from "@material-ui/lab"; import {useQuery} from "@apollo/react-hooks"; import "./App.css"; -import Header from "../components/Header"; -import Nav from "../components/Nav"; -import Content from "../components/Content"; -import NewPollModal from "../components/Poll/NewPollModal"; +import Header from "../components/Header/Header"; +import NavBar from "../components/NavBar/NavBar.js"; import {HostProvider} from "../libs/hostContext"; import {getEventsByHost} from "../libs/gql"; -import EmptyContent from "../components/EmptyContent"; import {socketClient} from "../libs/socket.io-Client-wrapper"; -import SkeletonContent from "../components/SkeletonContent"; -import SkeletonTitle from "../components/SkeletionTitle"; +import AppSkeleton from "../components/Skeleton/AppSkeleton"; +import config from "../config"; +import {compareCurrentDateToTarget} from "../libs/utils"; + +const initialValue = ""; + +const initialLoadEvents = (events, initialValue, dispatch, data) => { + if (events === initialValue) { + dispatch(data); + } +}; function App() { - const modal = false; const {data, loading, error} = useQuery(getEventsByHost()); - const [events, setEvents] = useState(""); - let eventNum = 0; + const [events, setEvents] = useState(initialValue); + let activeEventsNum = 0; + let eventsNum = 0; + let activeEvents = []; if (loading) { - return ( - - - - - ); + return ; } else if (error) { - return

error-page...

; - } else { - if (events === "") { - setEvents( - data.init.events, - () => (eventNum = data.init.events.length), - ); - } - const hostInfo = data.init.host; + window.location.href = config.inValidHostRedirectURL; + return
; + } + initialLoadEvents(events, initialValue, setEvents, data.init.events); + + const hostInfo = data.init.host; - eventNum = events.length; - if (eventNum) { - const eventId = events[0].id; + eventsNum = events.length; + if (eventsNum) { + activeEvents = events.filter(event => { + const eventDeadLine = new Date(parseInt(event.endAt)); + if (compareCurrentDateToTarget(eventDeadLine) > 0) { + return event; + } + }); + activeEventsNum = activeEvents.length; + if (activeEventsNum) { + const eventId = activeEvents[0].id; + socketClient.emit("leaveRoom"); socketClient.emit("joinRoom", {room: eventId}); - socketClient.emit("event/initOption", eventId); // dummy Event Id:2 + socketClient.emit("event/initOption", eventId); } - - return ( - -
-
-
-
- ); } + + return ( + +
+
+ +
+
+ ); } export default App; diff --git a/frontend/host-app/src/components/Column.js b/frontend/host-app/src/components/Column.js deleted file mode 100644 index 62d95124..00000000 --- a/frontend/host-app/src/components/Column.js +++ /dev/null @@ -1,40 +0,0 @@ -import React, {useState} from "react"; -import Title from "./Title"; -import QuestionContainer from "./Questions/QuestionContainer"; -import ColumnFooter from "./ColumnFooter"; -import {QuestionStyle, ModerationStyle} from "./ComponentsStyle"; -import {filterStared} from "../libs/utils"; - -function Column({type, state, stateHandler, data, dataHandler, handleStar}) { - const [heightWeight, setHeightWeight] = useState(0); - const ColumnStyle = ((type === "moderation") ? ModerationStyle : QuestionStyle); - - return ( - - - <QuestionContainer - type={type} - datas={filterStared(true, data)} - dataHandler={dataHandler} - handleStar={handleStar} - containerType={"focus"} - /> - <QuestionContainer - type={type} - datas={filterStared(false, data)} - dataHandler={dataHandler} - handleStar={handleStar} - containerType={"unFocus"} - /> - <ColumnFooter data={heightWeight} handler={setHeightWeight}/> - </ColumnStyle> - ); -} - -export default Column; diff --git a/frontend/host-app/src/components/ColumnFooter.js b/frontend/host-app/src/components/ColumnFooter.js deleted file mode 100644 index 15ffef35..00000000 --- a/frontend/host-app/src/components/ColumnFooter.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from "react"; -import {Icon} from "@material-ui/core"; -import {FooterBox, FooterStyle} from "./ComponentsStyle"; -import useStyles from "./Questions/useStyles"; - -function ColumnFooter({data, handler}) { - const classes = useStyles(); - const increaseHeight = () => handler(data + 1); - const decreaseHeight = () => ((data > 0) && handler(data - 1)); - - return ( - <FooterStyle> - <FooterBox> - <Icon className={classes.footerButton} onClick={() => { decreaseHeight(); }}> - remove - </Icon> - <Icon className={classes.footerButton} onClick={() => { increaseHeight(); }}> - add - </Icon> - </FooterBox> - </FooterStyle> - ); -} - -export default ColumnFooter; diff --git a/frontend/host-app/src/components/Content.js b/frontend/host-app/src/components/Content.js deleted file mode 100644 index 4d5a3cd3..00000000 --- a/frontend/host-app/src/components/Content.js +++ /dev/null @@ -1,90 +0,0 @@ -import React, {useState, useContext, useReducer} from "react"; -import Column from "./Column"; -import {socketClient} from "../libs/socket.io-Client-wrapper"; -import useQueryQuestions from "../libs/useQueryQuestions"; -import {HostContext} from "../libs/hostContext"; -import {ContentStyle} from "./ComponentsStyle"; -import QuestionsReducer from "./Questions/QuestionReducer"; -import SkeletonContent from "./SkeletonContent"; -import useSocketHandler from "./useSocketHandler"; - - -function Inner({data, event, option}) { - const SELECTED = true; - const UNSELECTED = false; - const [radioState, setRadioState] = useState([SELECTED, UNSELECTED, UNSELECTED, UNSELECTED]); - const [moderationState, setModeration] = useState(option.moderationOption); - const [questions, dispatch] = useReducer(QuestionsReducer, {questions: data}); - const [pollNumberStatus] = useState(0); - - const handleRadioState = buttonIndex => { - setRadioState([UNSELECTED, UNSELECTED, UNSELECTED, UNSELECTED] - .map((_, idx) => (idx === buttonIndex ? SELECTED : UNSELECTED))); - }; - - const typeMap = { - moderation: {state: moderationState, stateHandler: setModeration}, - newQuestion: {state: radioState, stateHandler: handleRadioState}, - popularQuestion: {state: radioState, stateHandler: handleRadioState}, - completeQuestion: {state: radioState, stateHandler: handleRadioState}, - }; - - useSocketHandler(dispatch); - - const handleQuestionDatas = (id, from, to) => { - const questionData = questions.questions.find(e => e.id === id); - - socketClient.emit("question/move", {id, from, to, data: questionData}); - }; - - const handleStar = id => { - const toggleMsg = questions.questions.reduce((acc, e) => { - if (e.isStared) { acc.from.push({id: e.id, isStared: !e.isStared}); } - if (e.id === id) { acc.to.push({id: e.id, isStared: !e.isStared}); } - return acc; - }, {from: [], to: []}); - - socketClient.emit("question/toggleStar", toggleMsg); - }; - - return ( - <ContentStyle> - {Object.keys(typeMap) - .map(e => ( - <Column - type={e} - state={typeMap[e].state} - stateHandler={typeMap[e].stateHandler} - data={questions} - dataHandler={handleQuestionDatas} - handleStar={handleStar} - /> - ))} - <Column - type="poll" - state={radioState} - stateHandler={handleRadioState} - badgeState={pollNumberStatus} - data={{questions: []}} - /> - </ContentStyle> - ); -} - -function Content({event}) { - const {events} = useContext(HostContext); - const {loading, error, data} = useQueryQuestions({ - variables: {EventId: events[0].id}, - }); - - if (loading) return <SkeletonContent/>; - if (error) return <p>Error :(</p>; - - return ( - <> - <Inner data={data.newData} event={event} option={data.newOption} /> - </> - ); -} - -export default Content; diff --git a/frontend/host-app/src/components/CreateEventModal/ButtonField.js b/frontend/host-app/src/components/CreateEventModal/ButtonField.js index 57a017ce..2c726aa9 100644 --- a/frontend/host-app/src/components/CreateEventModal/ButtonField.js +++ b/frontend/host-app/src/components/CreateEventModal/ButtonField.js @@ -12,33 +12,9 @@ const Container = styled.div` height: 3rem; `; -// const CancelTextButton = styled.div` -// font-size: 1.25rem; -// color: balck; -// :hover { -// font-weight: bold; -// } -// width: auto; -// cursor: pointer; -// margin: 1rem; -// `; - -// const CreateTextButton = styled.div` -// font-size: 1.25rem; -// color: green; -// :hover { -// font-weight: bold; -// } -// width: auto; -// cursor: pointer; -// margin: 1rem; -// `; - function ButtonField({createEvent, onClose}) { return ( <Container> - {/* <CancelTextButton onClick={onClose}>취소</CancelTextButton> - <CreateTextButton onClick={createEvent}>확인</CreateTextButton> */} <Button size="large" variant="contained" diff --git a/frontend/host-app/src/components/CreateEventModal/CreateEventModal.js b/frontend/host-app/src/components/CreateEventModal/CreateEventModal.js index 3588ba2c..41879446 100644 --- a/frontend/host-app/src/components/CreateEventModal/CreateEventModal.js +++ b/frontend/host-app/src/components/CreateEventModal/CreateEventModal.js @@ -1,4 +1,4 @@ -import React, {useReducer, useContext, useState} from "react"; +import React, {useReducer, useContext} from "react"; import {Modal} from "@material-ui/core"; import styled from "styled-components"; import moment from "moment"; @@ -13,7 +13,7 @@ import AlertSnackbar from "./AlertSnackbar"; import {eventModalReducer} from "./eventModalReducer"; import {createEvent, createHashTags} from "../../libs/gql"; import {HostContext} from "../../libs/hostContext"; -import {validEventName, validDate} from "../../libs/eventValidation"; +import useSnackBar from "../../customhook/useSnackBar"; const modalHeight = 38; // 37; const modalWidth = 28.125; @@ -29,6 +29,8 @@ const PopUpLayOutStyle = styled.div` // padding-left: 1.25rem; padding: 0 1.5rem; box-sizing: border-box; + border-radius: 15px; + outline: none; `; const StyledForm = styled.form` @@ -39,15 +41,25 @@ const StyledForm = styled.form` `; const Header = styled.div` - // margin-left: 0; - // margin-top: 2rem; - // margin-bottom: 2rem; margin: 1rem 0 0.5rem 0; font-size: 2rem; color: #139ffb; text-align: center; `; +const initEndDate = (startTime, lastTime) => { + const hour = moment(lastTime).format("HH"); + const minuate = moment(lastTime).format("mm"); + let addedTime = moment(startTime) + .add(hour, "h") + .toDate(); + + addedTime = moment(addedTime) + .add(minuate, "m") + .toDate(); + return addedTime; +}; + function verifyInputData(errorState) { const isInValid = Object.values(errorState).some(inputValue => inputValue); @@ -59,96 +71,49 @@ function formattingDate(date) { } function CreateEventModal({open, handleClose}) { - const initErrorState = { - eventName: true, - startDate: true, - }; + const {hostInfo, events, setEvents, allEvents} = useContext(HostContext); const initialEventInfo = { - eventName: "", + eventName: `${hostInfo.name}님의 이벤트`, startDate: new Date(), - endDate: new Date(), + endDate: initEndDate(new Date(), new Date().setHours(1, 0)), hashTags: [], + errorState: {eventName: false, date: false}, }; - const {hostInfo, events, setEvents} = useContext(HostContext); - const [snackBarOpen, setSnackBarOpen] = useState(false); - const [errorState, setErrorState] = useState(initErrorState); + const {snackBarOpen, snackBarHandleClose, setSnackBarOpen} = useSnackBar(); const [eventInfo, dispatchEventInfo] = useReducer( eventModalReducer, initialEventInfo, ); - const [mutaionEvent, {event}] = useMutation(createEvent(), { - variables: { - info: { - HostId: hostInfo.id, - startAt: formattingDate(eventInfo.startDate), - endAt: formattingDate(eventInfo.endDate), - eventName: eventInfo.eventName, - }, - }, - }); + const [mutaionEvent, {event}] = useMutation(createEvent()); const [mutationHashTags, {hashTags}] = useMutation(createHashTags()); - const snackBarHandleClose = (event, reason) => { - if (reason === "clickaway") { - return; - } - - setSnackBarOpen(false); - }; - - const setEventName = event => { - const isError = !validEventName(event.target.value); - - setErrorState({...errorState, eventName: isError}); - dispatchEventInfo({ - type: "setEventName", - eventName: event.target.value, - }); - }; - - const setStartDate = event => { - const isError = !validDate(event, eventInfo.endDate); - - setErrorState({...errorState, startDate: isError}); + const dispatchHandler = ({type, property, value}) => { dispatchEventInfo({ - type: "setStartDate", - startDate: event, + type, + property, + value, }); }; - const setEndDate = event => { - const isError = !validDate(eventInfo.startDate, event); - - setErrorState({...errorState, startDate: isError}); - dispatchEventInfo({ - type: "setEndDate", - endDate: event, - }); - }; - - const updateHashTag = hashTagList => { - dispatchEventInfo({ - type: "updateHashTags", - hashTags: hashTagList, - }); - }; - - const reset = () => { - dispatchEventInfo({ - type: "reset", - }); - handleClose(); - }; - const sendData = () => { - const isInValid = verifyInputData(errorState); + const isInValid = verifyInputData(eventInfo.errorState); if (isInValid) { setSnackBarOpen(true); return; } - - mutaionEvent() + mutaionEvent({ + variables: { + info: { + HostId: hostInfo.id, + startAt: moment( + formattingDate(eventInfo.startDate), + ).toDate(), + endAt: moment(formattingDate(eventInfo.endDate)).toDate(), + eventName: eventInfo.eventName, + }, + }, + }) .then(res => { const hashTagList = eventInfo.hashTags.map(hashTag => ({ name: hashTag.label, @@ -157,50 +122,54 @@ function CreateEventModal({open, handleClose}) { mutationHashTags({variables: {hashTags: hashTagList}}).catch( e => { - console.log("해쉬태그 생성 실패"); + console.error(`해쉬태그 생성 Error${e}`); + alert("해쉬태그 생성 실패"); }, ); Object.assign(res.data.createEvent, {HashTags: hashTagList}); - setEvents([...events, res.data.createEvent]); + setEvents([...allEvents, res.data.createEvent]); + handleClose(); }) .catch(e => { - console.error("이벤트 생성 실패"); + console.error(`이벤트 생성 Error${e}`); + alert("이벤트 생성 실패"); }); - reset(); }; + return ( <Modal aria-labelledby="createEvent-modal-title" aria-describedby="createEvent-modal-description" open={open} - onClose={reset} + onClose={handleClose} > <PopUpLayOutStyle> <Header id="createEvent-modal-title">이벤트만들기</Header> <StyledForm> <InputEventName - errorState={errorState.eventName} - dispatch={setEventName} + errorState={eventInfo.errorState} + dispatch={dispatchHandler} + eventName={eventInfo.eventName} /> <InputStartDate - errorState={errorState.startDate} - endDate={eventInfo.endDate} + errorState={eventInfo.errorState} startDate={eventInfo.startDate} - dispatch={{setStartDate, setEndDate}} + endDate={eventInfo.endDate} + dispatch={dispatchHandler} /> <EndDateField endDate={eventInfo.endDate} /> <InputHashTag hashTags={eventInfo.hashTags} - dispatch={updateHashTag} + dispatch={dispatchHandler} /> <HashTagsField hashTags={eventInfo.hashTags} - dispatch={updateHashTag} + dispatch={dispatchHandler} /> </StyledForm> - <ButtonField createEvent={sendData} onClose={reset} /> + <ButtonField createEvent={sendData} onClose={handleClose} /> <AlertSnackbar - errorState={errorState} + errorState={eventInfo.errorState} handleClose={snackBarHandleClose} open={snackBarOpen} /> diff --git a/frontend/host-app/src/components/CreateEventModal/EndDateField.js b/frontend/host-app/src/components/CreateEventModal/EndDateField.js index c83722f4..22b3a42c 100644 --- a/frontend/host-app/src/components/CreateEventModal/EndDateField.js +++ b/frontend/host-app/src/components/CreateEventModal/EndDateField.js @@ -13,12 +13,14 @@ function formattingDate(date) { } function EndDateField(props) { + const {endDate} = props; + return ( <CustomTextField - id="eventName" + id="endDateField" label="종료날짜" color="primary" - value={formattingDate(props.endDate)} + value={formattingDate(endDate)} readOnly={true} /> ); diff --git a/frontend/host-app/src/components/CreateEventModal/HashTagsField.js b/frontend/host-app/src/components/CreateEventModal/HashTagsField.js index a0febda6..68a70d62 100644 --- a/frontend/host-app/src/components/CreateEventModal/HashTagsField.js +++ b/frontend/host-app/src/components/CreateEventModal/HashTagsField.js @@ -2,11 +2,15 @@ import React from "react"; import {styled} from "@material-ui/core/styles"; import Chip from "@material-ui/core/Chip"; import Paper from "@material-ui/core/Paper"; -import {BookmarkBorderRounded} from "@material-ui/icons"; +import LocalOfferIcon from "@material-ui/icons/LocalOffer"; +import {Scrollbars} from "react-custom-scrollbars"; const MyPaper = styled(Paper)({ marginTop: 10, overflowY: "auto", + "::-webkit-scrollbar": { + display: "none", + }, width: 400, }); @@ -15,27 +19,34 @@ const CustomChip = styled(Chip)({ }); function HashTagField(props) { - const handleDelete = hashTagToDelete => () => { - const deletedHashTagList = props.hashTags.filter( + const {hashTags, dispatch} = props; + const deleteHashTag = hashTagToDelete => () => { + const deletedHashTagList = hashTags.filter( hashTag => hashTag.key !== hashTagToDelete.key, ); - props.dispatch(deletedHashTagList); + dispatch({ + type: "SET_PROPERTY", + property: "hashTags", + value: deletedHashTagList, + }); }; return ( - <MyPaper> - {props.hashTags.map(data => ( - <CustomChip - icon={<BookmarkBorderRounded />} - color="primary" - variant="outlined" - key={data.key} - label={data.label} - onDelete={handleDelete(data)} - /> - ))} - </MyPaper> + <Scrollbars> + <MyPaper> + {hashTags.map(data => ( + <CustomChip + icon={<LocalOfferIcon />} + color="primary" + variant="outlined" + key={data.key} + label={data.label} + onDelete={deleteHashTag(data)} + /> + ))} + </MyPaper> + </Scrollbars> ); } diff --git a/frontend/host-app/src/components/CreateEventModal/InputEventName.js b/frontend/host-app/src/components/CreateEventModal/InputEventName.js index 1a6df098..f3ab581d 100644 --- a/frontend/host-app/src/components/CreateEventModal/InputEventName.js +++ b/frontend/host-app/src/components/CreateEventModal/InputEventName.js @@ -1,6 +1,7 @@ -import React from "react"; +import React, {useState} from "react"; import {styled} from "@material-ui/core/styles"; import {TextField} from "@material-ui/core"; +import {validEventName} from "../../libs/eventValidation"; const CustomTextField = styled(TextField)({ marginTop: "1rem", @@ -8,15 +9,27 @@ const CustomTextField = styled(TextField)({ }); function InputEventName(props) { - const {eventName, dispatch, errorState} = props; + const {dispatch, errorState, eventName} = props; return ( <CustomTextField id="eventName" label="이벤트 이름을 입력해주세요" color="primary" - error={errorState} - onChange={dispatch} + error={errorState.eventName} + value={eventName} + onChange={event => { + dispatch({ + type: "SET_ERROR_STATE", + property: "eventName", + value: !validEventName(event.target.value), + }); + dispatch({ + type: "SET_PROPERTY", + property: "eventName", + value: event.target.value, + }); + }} autoFocus /> ); diff --git a/frontend/host-app/src/components/CreateEventModal/InputHashTag.js b/frontend/host-app/src/components/CreateEventModal/InputHashTag.js index c4d1fa68..ea61f106 100644 --- a/frontend/host-app/src/components/CreateEventModal/InputHashTag.js +++ b/frontend/host-app/src/components/CreateEventModal/InputHashTag.js @@ -10,23 +10,31 @@ const CustomTextField = styled(TextField)({ }); function InputHashTag(props) { - const prevHashTagList = props.hashTags; + const {hashTags, dispatch} = props; + const prevHashTagList = hashTags; const addHashTag = event => { + const inputValue = event.target.value; if (event.keyCode === ENTER_KEY_CODE) { const hashTag = { key: uuidv1(), - label: event.target.value, + label: inputValue, }; + + if (inputValue.length <= 0 && inputValue.length > 100) return; const hashTagList = [...prevHashTagList, hashTag]; - props.dispatch(hashTagList); + dispatch({ + type: "SET_PROPERTY", + property: "hashTags", + value: hashTagList, + }); event.target.value = ""; } }; return ( <CustomTextField - id="eventName" + id="hashTag" label="해시태그를 입력 후 Enter키를 눌러주세요" // "Press Enter" color="primary" onKeyDown={addHashTag} diff --git a/frontend/host-app/src/components/CreateEventModal/InputStartDate.js b/frontend/host-app/src/components/CreateEventModal/InputStartDate.js index cf7fc0a0..12dcc12f 100644 --- a/frontend/host-app/src/components/CreateEventModal/InputStartDate.js +++ b/frontend/host-app/src/components/CreateEventModal/InputStartDate.js @@ -8,6 +8,7 @@ import {styled} from "@material-ui/core/styles"; import DateFnsUtils from "@date-io/date-fns"; import Container from "@material-ui/core/Container"; import moment from "moment"; +import {validDate} from "../../libs/eventValidation"; const marginTopLength = 20; @@ -29,25 +30,39 @@ const CustomTimePicker = styled(TimePicker)({ }); function InputStartDate(props) { - const {errorState} = props; - const {setStartDate, setEndDate} = props.dispatch; - const [lastTime, handleLastTimeChange] = useState( - new Date().setHours(0, 0), - ); - - const calcEndDate = inputTime => { - const hour = moment(inputTime).format("HH"); - const minuate = moment(inputTime).format("mm"); + const {startDate, dispatch, errorState} = props; + const [lastTime, setLastTime] = useState(new Date().setHours(1, 0)); - let addedDate = moment(props.startDate) + const calcEndDate = (lastTime, startTime = startDate) => { + const hour = moment(lastTime).format("HH"); + const minuate = moment(lastTime).format("mm"); + let addedTime = moment(startTime) .add(hour, "h") .toDate(); - addedDate = moment(addedDate) + addedTime = moment(addedTime) .add(minuate, "m") .toDate(); - setEndDate(moment(addedDate)); - handleLastTimeChange(inputTime); + dispatch({ + type: "SET_PROPERTY", + property: "endDate", + value: moment(addedTime), + }); + setLastTime(lastTime); + dispatch({ + type: "SET_ERROR_STATE", + property: "date", + value: !validDate(startTime, addedTime), + }); + }; + + const setDate = event => { + dispatch({ + type: "SET_PROPERTY", + property: "startDate", + value: event, + }); + calcEndDate(lastTime, event); }; return ( @@ -55,10 +70,10 @@ function InputStartDate(props) { <MuiPickersUtilsProvider utils={DateFnsUtils}> <CustomDateTimePicker label="시작날짜" - value={props.startDate} - error={errorState} + error={errorState.date} + value={startDate} format={"yyyy년 MM월 dd일 HH시 mm분"} - onChange={setStartDate} + onChange={setDate} /> <CustomTimePicker clearable diff --git a/frontend/host-app/src/components/CreateEventModal/eventModalReducer.js b/frontend/host-app/src/components/CreateEventModal/eventModalReducer.js index 1d2f93bc..9ccfd1a9 100644 --- a/frontend/host-app/src/components/CreateEventModal/eventModalReducer.js +++ b/frontend/host-app/src/components/CreateEventModal/eventModalReducer.js @@ -1,18 +1,15 @@ const eventModalReducer = (state, action) => { switch (action.type) { - case "setEventName": { - return {...state, eventName: action.eventName}; + case "SET_PROPERTY": { + return {...state, [action.property]: action.value}; } - case "setStartDate": { - return {...state, startDate: action.startDate}; - } - case "setEndDate": { - return {...state, endDate: action.endDate}; - } - case "updateHashTags": { - return {...state, hashTags: action.hashTags}; + case "SET_ERROR_STATE": { + const errorState = state.errorState; + Object.assign(errorState, {[action.property]: action.value}); + return {...state, errorState: errorState}; } default: { + console.error(action); throw new Error(`unexpected action.type: ${action.type}`); } } diff --git a/frontend/host-app/src/components/EmptyContent.js b/frontend/host-app/src/components/EmptyContent.js deleted file mode 100644 index 23ede774..00000000 --- a/frontend/host-app/src/components/EmptyContent.js +++ /dev/null @@ -1,46 +0,0 @@ -import React from "react"; -import Typography from "@material-ui/core/Typography"; -import EventCreateButton from "./Event/EventCreateButton.js"; -import {Button} from "@material-ui/core"; -import {withStyles} from "@material-ui/core/styles"; -import CreateEventModal from "./CreateEventModal/CreateEventModal"; -import useModal from "../customhook/useModal"; -import {EmptyContentBox, EmptyContentDiv} from "./ComponentsStyle"; - -const StyledButton = withStyles({ - root: { - width: "300px", - fontSize: "1.4rem", - }, -})(Button); - -function EmptyContent() { - const [eventModalOpen, handleOpen, handleClose] = useModal(); - - return ( - <EmptyContentBox> - <EmptyContentDiv> - <Typography>현재 진행중인 이벤트가 없습니다</Typography> - - <EventCreateButton onClick={handleOpen} /> - {/* 현재 진행중인 이벤트가 없습니다 - <StyledButton - size="large" - variant="contained" - color="primary" - onClick={handleOpen} - > - 이벤트 만들기 - </StyledButton> */} - {eventModalOpen && ( - <CreateEventModal - open={eventModalOpen} - handleClose={handleClose} - /> - )} - </EmptyContentDiv> - </EmptyContentBox> - ); -} - -export default EmptyContent; diff --git a/frontend/host-app/src/components/Event/EventCard.js b/frontend/host-app/src/components/Event/EventCard.js index f89cbb28..eb034b76 100644 --- a/frontend/host-app/src/components/Event/EventCard.js +++ b/frontend/host-app/src/components/Event/EventCard.js @@ -1,4 +1,5 @@ import React from "react"; +import {makeStyles} from "@material-ui/core/styles"; import PropTypes from "prop-types"; import {Card, CardContent} from "@material-ui/core"; import Grid from "@material-ui/core/Grid"; @@ -6,11 +7,23 @@ import Box from "@material-ui/core/Box"; import EventIcon from "./EventIcon.js"; import EventCardContent from "./EventCardContent.js"; +const useStyles = makeStyles({ + selected: { + background: "linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)", + }, + non_selected: { + background: "none", + }, +}); + function EventCard(props) { + const className = "none"; + const classes = useStyles(); const {isLive} = props; return ( <Card + className={classes[className]} style={{ width: "100vw", padding: "0rem", @@ -23,7 +36,7 @@ function EventCard(props) { <Grid container direction="row" wrap={"nowrap"}> <EventIcon isLive={isLive} /> <Box p={1} /> - <EventCardContent {...props}/> + <EventCardContent {...props} /> </Grid> </CardContent> </Card> @@ -31,7 +44,7 @@ function EventCard(props) { } EventCard.propTypes = { - eventName: PropTypes.bool, + eventName: PropTypes.string, startAt: PropTypes.string, endAt: PropTypes.string, eventCode: PropTypes.string, diff --git a/frontend/host-app/src/components/Event/EventCardList.js b/frontend/host-app/src/components/Event/EventCardList.js index a33558c6..821764fb 100644 --- a/frontend/host-app/src/components/Event/EventCardList.js +++ b/frontend/host-app/src/components/Event/EventCardList.js @@ -1,15 +1,31 @@ -import React from "react"; +import React, {useContext} from "react"; import EventCard from "./EventCard.js"; +import {HostContext} from "../../libs/hostContext"; +import {compareCurrentDateToTarget} from "../../libs/utils"; + +const dateFormat = date => new Date(parseInt(date, 10)); function EventCardList(props) { - const {events = [{}, {}, {}]} = props; + const {allEvents} = useContext(HostContext); + const {value, index} = props; return ( - <> - {events.map(event => ( - <EventCard {...event} key={event.id} /> - ))} - </> + <div + role="tabpanel" + hidden={value !== index} + id={`hostpage-tabpanel-${index}`} + aria-labelledby={`hostpage-tab-${index}`} + > + {value === index && + allEvents.map(event => { + const isLive = + compareCurrentDateToTarget(dateFormat(event.endAt)) > 0; + + return ( + <EventCard {...event} key={event.id} isLive={isLive} /> + ); + })} + </div> ); } diff --git a/frontend/host-app/src/components/Event/EventDateRage.js b/frontend/host-app/src/components/Event/EventDateRage.js index 38146888..c17000d4 100644 --- a/frontend/host-app/src/components/Event/EventDateRage.js +++ b/frontend/host-app/src/components/Event/EventDateRage.js @@ -1,11 +1,16 @@ import Typography from "@material-ui/core/Typography"; import React from "react"; +import moment from "moment"; + +const dateFormat = "YYYY년 MM월 DD일 HH시 mm분"; function getDateRangeString(startAt, endAt) { const start = new Date(parseInt(startAt, 10)); const end = new Date(parseInt(endAt, 10)); + const startDate = moment(start).format(dateFormat); + const endDate = moment(end).format(dateFormat); - return `${end.getFullYear()}.${start.getDay()} ~ ${end.getFullYear()}.${end.getDay()}`; + return `${startDate} ~ ${endDate}`; } function EventDateRage(props) { diff --git a/frontend/host-app/src/components/EventDashboard/Buttons/CompleteAllQuestionButton.js b/frontend/host-app/src/components/EventDashboard/Buttons/CompleteAllQuestionButton.js new file mode 100644 index 00000000..c759b372 --- /dev/null +++ b/frontend/host-app/src/components/EventDashboard/Buttons/CompleteAllQuestionButton.js @@ -0,0 +1,24 @@ +import React from "react"; +import Tooltip from "@material-ui/core/Tooltip"; +import {Icon} from "@material-ui/core"; +import useStyles from "./useButtonStyles.js"; +import {handleQuestionDatas} from "../../EventEmiter/QuestionSocketEventEmiter.js"; + +function CompleteAllQuestionButton({data}) { + const classes = useStyles(); + + return ( + <> + <Tooltip title="모든 질문 완료"> + <Icon + className={classes.completeAllButton} + onClick={() => handleQuestionDatas(data, "all", "active", "completeQuestion")} + > + launch + </Icon> + </Tooltip> + </> + ); +} +export default CompleteAllQuestionButton; + diff --git a/frontend/host-app/src/components/EventDashboard/Buttons/ModerationButton.js b/frontend/host-app/src/components/EventDashboard/Buttons/ModerationButton.js new file mode 100644 index 00000000..21fcad43 --- /dev/null +++ b/frontend/host-app/src/components/EventDashboard/Buttons/ModerationButton.js @@ -0,0 +1,14 @@ +import Switch from "@material-ui/core/Switch"; +import React from "react"; +import moderationEventEmit from "../../EventEmiter/moderationEventEmiter"; + +function ModerationButton({state, eventId}) { + return ( + <Switch + checked={state} + onClick={() => moderationEventEmit(eventId, state)} + /> + ); +} + +export default ModerationButton; diff --git a/frontend/host-app/src/components/EventDashboard/Buttons/useButtonStyles.js b/frontend/host-app/src/components/EventDashboard/Buttons/useButtonStyles.js new file mode 100644 index 00000000..fced74f6 --- /dev/null +++ b/frontend/host-app/src/components/EventDashboard/Buttons/useButtonStyles.js @@ -0,0 +1,14 @@ +import {makeStyles} from "@material-ui/core"; + +const useStyles = makeStyles(() => ({ + completeAllButton: { + color: "#7f7f7f", + marginLeft: "0.25rem", + "&:hover": { + color: "#EF0046", + }, + }, +} +)); + +export default useStyles; diff --git a/frontend/host-app/src/components/EventDashboard/Column.js b/frontend/host-app/src/components/EventDashboard/Column.js new file mode 100644 index 00000000..084f2b45 --- /dev/null +++ b/frontend/host-app/src/components/EventDashboard/Column.js @@ -0,0 +1,31 @@ +import React from "react"; +import TitleContainer from "./Title/TitleContainer"; +import QuestionContainer from "../Questions/QuestionContainer"; +import {QuestionStyle, ModerationStyle} from "./ComponentsStyle"; +import {filterStared} from "../../libs/utils"; + +function Column({type, state, data}) { + const ColumnStyle = ((type === "moderation") ? ModerationStyle : QuestionStyle); + + return ( + <ColumnStyle state={state}> + <TitleContainer + type={type} + state={state} + data={data} + /> + <QuestionContainer + type={type} + datas={filterStared(true, data)} + containerType={"focus"} + /> + <QuestionContainer + type={type} + datas={filterStared(false, data)} + containerType={"unFocus"} + /> + </ColumnStyle> + ); +} + +export default Column; diff --git a/frontend/host-app/src/components/ComponentsStyle.js b/frontend/host-app/src/components/EventDashboard/ComponentsStyle.js similarity index 70% rename from frontend/host-app/src/components/ComponentsStyle.js rename to frontend/host-app/src/components/EventDashboard/ComponentsStyle.js index efcb7e2d..608a6949 100644 --- a/frontend/host-app/src/components/ComponentsStyle.js +++ b/frontend/host-app/src/components/EventDashboard/ComponentsStyle.js @@ -2,8 +2,8 @@ import styled, {keyframes} from "styled-components"; const Open = keyframes` 0% { - min-width: 8rem; - height: 13%; + min-width: 10rem; + height: 10%; } 100% { min-width: 20rem; @@ -17,8 +17,8 @@ const Close = keyframes` height: 100%; } 100% { - min-width: 8rem; - height: 13%; + min-width: 10rem; + height: 10%; } `; @@ -30,14 +30,6 @@ const TitleBox = styled.div` min-height: 2.5rem; `; -const FooterBox = styled.div` - display: flex; - align-items: center; - width: 100%; - justify-content: space-around; - height: 1rem; -`; - const TitleStyle = styled.div` font-weight: bold; `; @@ -95,7 +87,7 @@ const QuestionStyle = styled.div` background-color: #f1f3f5; border: 1px solid #e9ecef; min-width: 20rem; - height: ${props => props.height || "100%"}; + height: 100%; box-sizing: border-box; margin-left: 8px; `; @@ -104,16 +96,16 @@ const ModerationStyle = styled.div` display: flex; flex-direction: column; flex: 1; - animation: ${props => ((props.state) ? Open : Close)}; - animation-duration: 0.2s; - animation-fill-mode: forwards; + animation: ${props => (props.state ? Open : Close)}; + animation-duration: 0.2s; + animation-fill-mode: forwards; justify-content: flex-start; align-items: center; border-radius: 8px; background-color: #f1f3f5; border: 1px solid #e9ecef; min-width: ${props => (props.state ? "20rem" : "8rem")}; - height: ${props => props.height || "100%"}; + height: 100%; min-height: 2.5rem; box-sizing: border-box; & + & { @@ -121,26 +113,6 @@ const ModerationStyle = styled.div` } `; -const SkeletonColumnStyle = styled.div` - display: flex; - flex-direction: column; - flex: 1; - justify-content: flex-start; - align-items: center; - border-radius: 8px; - min-width: "20rem"; - height: "100%"; - box-sizing: border-box; - & + & { - margin-left: 8px; - } -`; - -const FooterStyle = styled.div` - width: 90%; - margin-top: auto; -`; - export { TitleStyle, TitleBox, @@ -149,8 +121,5 @@ export { EmptyContentDiv, ContentStyle, QuestionStyle, - FooterStyle, - SkeletonColumnStyle, ModerationStyle, - FooterBox, }; diff --git a/frontend/host-app/src/components/EventDashboard/EmptyContent.js b/frontend/host-app/src/components/EventDashboard/EmptyContent.js new file mode 100644 index 00000000..3f369283 --- /dev/null +++ b/frontend/host-app/src/components/EventDashboard/EmptyContent.js @@ -0,0 +1,33 @@ +import React from "react"; +import Typography from "@material-ui/core/Typography"; +import EventCreateButton from "../Event/EventCreateButton.js"; +import CreateEventModal from "../CreateEventModal/CreateEventModal"; +import useModal from "../../customhook/useModal"; +import {EmptyContentBox, EmptyContentDiv} from "./ComponentsStyle"; + +function EmptyContent(props) { + const {value, index} = props; + const [eventModalOpen, handleOpen, handleClose] = useModal(); + + return ( + <> + {index === value && ( + <EmptyContentBox> + <EmptyContentDiv> + <Typography>현재 진행중인 이벤트가 없습니다</Typography> + + <EventCreateButton onClick={handleOpen} /> + {eventModalOpen && ( + <CreateEventModal + open={eventModalOpen} + handleClose={handleClose} + /> + )} + </EmptyContentDiv> + </EmptyContentBox> + )} + </> + ); +} + +export default EmptyContent; diff --git a/frontend/host-app/src/components/EventDashboard/EventDashboard.js b/frontend/host-app/src/components/EventDashboard/EventDashboard.js new file mode 100644 index 00000000..aea9a697 --- /dev/null +++ b/frontend/host-app/src/components/EventDashboard/EventDashboard.js @@ -0,0 +1,55 @@ +import React, {useState, useContext, useReducer} from "react"; +import Column from "./Column"; +import useQueryQuestions from "../../libs/useQueryQuestions"; +import {HostContext} from "../../libs/hostContext"; +import {ContentStyle} from "./ComponentsStyle"; +import QuestionsReducer from "../Questions/QuestionReducer"; +import SkeletonContent from "../Skeleton/SkeletonContent"; +import useQuestionSocketEventHandler from "../EventHandler/useQuestionSocketEventHandler"; +import useModerationEventHandler from "../EventHandler/useModerationEventHandler"; + +function Inner({data, option}) { + const [moderationState, setModeration] = useState(option.moderationOption); + const [questions, dispatch] = useReducer(QuestionsReducer, { + questions: data, + }); + const columnTypes = ["moderation", "newQuestion", "popularQuestion", "completeQuestion"]; + + useQuestionSocketEventHandler(dispatch); + useModerationEventHandler(setModeration); + + return ( + <ContentStyle> + {columnTypes.map((e, i) => ( + <Column + type={e} + state={moderationState} + data={questions} + key={i} + /> + ))} + <Column type="poll" data={{questions: []}} /> + </ContentStyle> + ); +} + +function EventDashboard(props) { + const {value, index} = props; + const {events} = useContext(HostContext); + const {loading, error, data} = useQueryQuestions({ + variables: {EventId: events[0].id}, + }); + + if (loading) return <SkeletonContent />; + if (error) return <p>Error :(</p>; + + return ( + <> + {value === index && ( + <Inner data={data.newData} option={data.newOption} /> + )} + </> + ); +} + +export default EventDashboard; diff --git a/frontend/host-app/src/components/EventDashboard/Title/Title.js b/frontend/host-app/src/components/EventDashboard/Title/Title.js new file mode 100644 index 00000000..c24470ef --- /dev/null +++ b/frontend/host-app/src/components/EventDashboard/Title/Title.js @@ -0,0 +1,30 @@ +import React, {useContext} from "react"; +import {TitleStyle, TitleBox, RightSide} from "../ComponentsStyle"; +import CompleteAllQuestionButton from "../Buttons/CompleteAllQuestionButton"; +import TitleBadge from "./TitleBadge"; +import {HostContext} from "../../../libs/hostContext"; +import titleNameMap from "./titleNameMap"; +import ModerationButton from "../Buttons/ModerationButton"; + +const isActive = type => (type === "newQuestion" || type === "popularQuestion"); +const isModeration = type => (type === "moderation"); + +function Title({data, type, state}) { + const {events} = useContext(HostContext); + const eventId = events[0].id; + + return ( + <> + <TitleBox> + <TitleBadge dataLength={data.questions.length} type={type}/> + <TitleStyle>{titleNameMap[type]}</TitleStyle> + <RightSide> + { isModeration(type) && <ModerationButton state={state} eventId={eventId}/>} + { isActive(type) && <CompleteAllQuestionButton data={data}/>} + </RightSide> + </TitleBox> + </> + ); +} + +export default Title; diff --git a/frontend/host-app/src/components/EventDashboard/Title/TitleBadge.js b/frontend/host-app/src/components/EventDashboard/Title/TitleBadge.js new file mode 100644 index 00000000..12e42433 --- /dev/null +++ b/frontend/host-app/src/components/EventDashboard/Title/TitleBadge.js @@ -0,0 +1,25 @@ +import Badge from "@material-ui/core/Badge/Badge"; +import React from "react"; +import {makeStyles} from "@material-ui/core"; + +const isPoll = type => type === "poll"; +const useStyles = makeStyles(theme => ({ + margin: { + margin: theme.spacing(2), + }, +})); + +function TitleBadge({dataLength, type}) { + const classes = useStyles(); + + return ( + <Badge + color="secondary" + badgeContent={isPoll(type) ? "P" : dataLength} + showZero + className={classes.margin} + children={""}/> + ); +} + +export default TitleBadge; diff --git a/frontend/host-app/src/components/EventDashboard/Title/TitleContainer.js b/frontend/host-app/src/components/EventDashboard/Title/TitleContainer.js new file mode 100644 index 00000000..10b3419b --- /dev/null +++ b/frontend/host-app/src/components/EventDashboard/Title/TitleContainer.js @@ -0,0 +1,25 @@ +import React from "react"; +import Title from "./Title"; +import {filterQuestion} from "../../../libs/utils"; + +function TitleContainer({type, state, data}) { + if (type === "moderation") { + return <Title + state={state} + data={filterQuestion(type, data)} + type={type} + />; + } else if (type === "completeQuestion") { + return <Title + data={filterQuestion(type, data)} + type={type} + />; + } else { + return <Title + data={filterQuestion("active", data)} + type={type} + />; + } +} + +export default TitleContainer; diff --git a/frontend/host-app/src/components/EventDashboard/Title/titleNameMap.js b/frontend/host-app/src/components/EventDashboard/Title/titleNameMap.js new file mode 100644 index 00000000..b766bc6e --- /dev/null +++ b/frontend/host-app/src/components/EventDashboard/Title/titleNameMap.js @@ -0,0 +1,9 @@ +const titleNameMap = { + moderation: "질문 검열", + newQuestion: "최신 질문", + popularQuestion: "인기 질문", + completeQuestion: "완료 질문", + poll: "투표", +}; + +export default titleNameMap; diff --git a/frontend/host-app/src/components/EventEmiter/QuestionSocketEventEmiter.js b/frontend/host-app/src/components/EventEmiter/QuestionSocketEventEmiter.js new file mode 100644 index 00000000..8f59bcf7 --- /dev/null +++ b/frontend/host-app/src/components/EventEmiter/QuestionSocketEventEmiter.js @@ -0,0 +1,20 @@ +import {socketClient} from "../../libs/socket.io-Client-wrapper"; + +const handleQuestionDatas = (questions, id, from, to) => { + const questionData = questions.questions.find(e => e.id === id); + + socketClient.emit("question/move", {id, from, to, data: questionData}); +}; + +const handleStar = (questions, id) => { + const toggleMsg = questions.questions.reduce((acc, e) => { + if (e.isStared) { acc.from.push({id: e.id, isStared: !e.isStared}); } + if (e.id === id) { acc.to.push({id: e.id, isStared: !e.isStared}); } + return acc; + }, {from: [], to: []}); + + socketClient.emit("question/toggleStar", toggleMsg); +}; + +export {handleQuestionDatas, handleStar}; + diff --git a/frontend/host-app/src/components/EventEmiter/moderationEventEmiter.js b/frontend/host-app/src/components/EventEmiter/moderationEventEmiter.js new file mode 100644 index 00000000..ad486c9b --- /dev/null +++ b/frontend/host-app/src/components/EventEmiter/moderationEventEmiter.js @@ -0,0 +1,6 @@ +import {socketClient} from "../../libs/socket.io-Client-wrapper"; + +const moderationEventEmit = (eventId, state) => + socketClient.emit("moderation/toggle", {eventId, state: !state}); + +export default moderationEventEmit; diff --git a/frontend/host-app/src/components/EventHandler/useModerationEventHandler.js b/frontend/host-app/src/components/EventHandler/useModerationEventHandler.js new file mode 100644 index 00000000..fd868541 --- /dev/null +++ b/frontend/host-app/src/components/EventHandler/useModerationEventHandler.js @@ -0,0 +1,9 @@ +import {useSocket} from "../../libs/socket.io-Client-wrapper"; + +const useModerationEventHandler = stateHandler => { + useSocket("moderation/toggle", req => { + stateHandler(req.state); + }); +}; + +export default useModerationEventHandler; diff --git a/frontend/host-app/src/components/EventHandler/useQuestionSocketEventHandler.js b/frontend/host-app/src/components/EventHandler/useQuestionSocketEventHandler.js new file mode 100644 index 00000000..8fad33c5 --- /dev/null +++ b/frontend/host-app/src/components/EventHandler/useQuestionSocketEventHandler.js @@ -0,0 +1,40 @@ +import {useSocket} from "../../libs/socket.io-Client-wrapper.js"; +import {makeNewData} from "../../libs/utils.js"; + +const useQuestionSocketEventHandler = dispatch => { + useSocket("question/create", req => + dispatch({type: "addNewQuestion", data: makeNewData(req)}), + ); + + useSocket("question/toggleStar", req => + dispatch({type: "toggleStar", data: req}), + ); + + useSocket("question/move", req => + dispatch({type: "moveQuestion", data: req}), + ); + + useSocket("question/remove", req => + dispatch({type: "removeQuestion", data: req}), + ); + + useSocket("question/update", req => + dispatch({type: "updateQuestion", data: req}), + ); + + useSocket("questionLike/create", req => + dispatch({ + type: "createLike", + data: {QuestionId: req.QuestionId}, + }), + ); + + useSocket("questionLike/remove", req => + dispatch({ + type: "removeLike", + data: {QuestionId: req.QuestionId}, + }), + ); +}; + +export default useQuestionSocketEventHandler; diff --git a/frontend/host-app/src/components/EventSettingModal/EventSettingModal.js b/frontend/host-app/src/components/EventSettingModal/EventSettingModal.js index 83eca54e..3207cb22 100644 --- a/frontend/host-app/src/components/EventSettingModal/EventSettingModal.js +++ b/frontend/host-app/src/components/EventSettingModal/EventSettingModal.js @@ -15,6 +15,8 @@ const PopUpLayOutStyle = styled.div` width: ${modalWidth}rem; height: ${modalHeight}rem; background-color: white; + border-radius: 15px; + outline: none; `; const AlertLayOut = styled.div` position: relative; @@ -24,6 +26,8 @@ const AlertLayOut = styled.div` height: ${alertHeight}rem; background-color: white; text-align: center; + border-radius: 15px; + outline: none; `; function EventSettingModal(props) { diff --git a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/AdvanceSetting/AdvanceSetting.js b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/AdvanceSetting/AdvanceSetting.js deleted file mode 100644 index aff95fed..00000000 --- a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/AdvanceSetting/AdvanceSetting.js +++ /dev/null @@ -1,103 +0,0 @@ -import React, {useReducer} from "react"; -import styled from "styled-components"; -import {Typography} from "@material-ui/core"; -import TypeTitle from "./TypeTitle"; -import TabHeader from "../TabHeader"; -import ButtonField from "../ButtonField"; -import SettingSwitch from "./SettingSwitch"; -import { - initialAdavanceState, - advanceSettingReducer, -} from "../../settingReducer/settingReducer"; - -const LayOutStyle = styled.div` - display: flex; - flex-direction: column; - background-color: white; -`; - -export default function AdvanceSetting({handleClose}) { - const [advanceSettingState, dispatch] = useReducer( - advanceSettingReducer, - initialAdavanceState, - ); - - const { - allowReply, - anonymousReply, - closeQuestion, - showPollsNum, - showRate, - } = advanceSettingState; - - const setAlloReply = event => { - dispatch({ - type: "setAlloReply", - allowReply: event.target.checked, - }); - }; - - const setAnonymousReply = event => { - dispatch({ - type: "setAnonymousReply", - anonymousReply: event.target.checked, - }); - }; - - const setCloseQuestion = event => { - dispatch({ - type: "setCloseQuestion", - closeQuestion: event.target.checked, - }); - }; - - const setShowPollsNum = event => { - dispatch({ - type: "setShowPollsNum", - showPollsNum: event.target.checked, - }); - }; - - const setShowRate = event => { - dispatch({ - type: "setShowRate", - showRate: event.target.checked, - }); - }; - - const reset = () => { - handleClose(); - dispatch({ - type: "reset", - }); - }; - - const sendData = () => { - console.log(advanceSettingState); - reset(); - }; - - return ( - <LayOutStyle> - <TabHeader type="feature" /> - <TypeTitle type="question">질문</TypeTitle> - <SettingSwitch state={allowReply} dispatch={setAlloReply}> - <Typography>참여자 댓글</Typography> - </SettingSwitch> - <SettingSwitch state={anonymousReply} dispatch={setAnonymousReply}> - <Typography>익명댓글 허용</Typography> - </SettingSwitch> - <SettingSwitch state={closeQuestion} dispatch={setCloseQuestion}> - <Typography>질문닫기</Typography> - </SettingSwitch> - <TypeTitle type="poll">투표</TypeTitle> - <SettingSwitch state={showPollsNum} dispatch={setShowPollsNum}> - <Typography>투표수 보기</Typography> - </SettingSwitch> - <SettingSwitch state={showRate} dispatch={setShowRate}> - <Typography>퍼센트로 표시</Typography> - </SettingSwitch> - <ButtonField submit={sendData} onClose={reset} /> - </LayOutStyle> - ); -} diff --git a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/AdvanceSetting/SettingSwitch.js b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/AdvanceSetting/SettingSwitch.js deleted file mode 100644 index 5ccb46d5..00000000 --- a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/AdvanceSetting/SettingSwitch.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from "react"; -import {styled} from "@material-ui/core/styles"; -import {Switch, Box} from "@material-ui/core"; - -const SwitchBox = styled(Box)({ - marginTop: 20, - display: "flex", -}); - -export default function SettingSwitch(props) { - const {state, dispatch, children} = props; - - return ( - <SwitchBox> - {children} - <Switch - checked={state} - onChange={dispatch} - value={children} - color="primary" - inputProps={{"aria-label": "state checkbox"}} - /> - </SwitchBox> - ); -} diff --git a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/AdvanceSetting/TypeTitle.js b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/AdvanceSetting/TypeTitle.js deleted file mode 100644 index 6e5c3eab..00000000 --- a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/AdvanceSetting/TypeTitle.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from "react"; -import {styled} from "@material-ui/core/styles"; -import {Typography, Box} from "@material-ui/core"; -import QuestionAnswerIcon from "@material-ui/icons/QuestionAnswer"; -import PollIcon from "@material-ui/icons/Poll"; - -const TypographyWithIcon = styled(Box)({ - marginTop: 20, - display: "flex", -}); - -const CustomQuestionAnswerIcon = styled(QuestionAnswerIcon)({ - marginRight: 20, -}); - -const CustomPollIcon = styled(PollIcon)({ - marginRight: 20, -}); - -export default function TypeTitle(props) { - const {children, type} = props; - - return ( - <TypographyWithIcon> - {type === "question" && ( - <CustomQuestionAnswerIcon color="primary" /> - )} - {type === "poll" && <CustomPollIcon color="primary" />} - <Typography color="textPrimary">{children}</Typography> - </TypographyWithIcon> - ); -} diff --git a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/ButtonField.js b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/ButtonField.js index 23afad39..9ad2d1ba 100644 --- a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/ButtonField.js +++ b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/ButtonField.js @@ -37,7 +37,7 @@ function ButtonField({submit, onClose}) { return ( <Container> <CancelTextButton onClick={handleOpen}>취소</CancelTextButton> - <CreateTextButton onClick={submit}>저장</CreateTextButton> + <CreateTextButton onClick={submit}>확인</CreateTextButton> {confirmModalOpen && ( <ConfirmModal open={confirmModalOpen} diff --git a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/ConfirmModal.js b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/ConfirmModal.js index edb35289..8ab84b0b 100644 --- a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/ConfirmModal.js +++ b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/ConfirmModal.js @@ -32,11 +32,13 @@ function ConfirmModal(props) { onClose={props.handleClose} > <div style={modalStyle} className={classes.paper}> - <Typography>정말취소?</Typography> + <Typography> + 취소하시면 현재 입력한 내용이 저장되지 않습니다. 정말 취소 + 하시겠습니까? + </Typography> <Grid container direction={"row"} justify="flex-end"> - <Button onClick={props.handleClose}>취소</Button> <Button color="secondary" onClick={props.reset}> - 버리기 + 확인 </Button> </Grid> </div> diff --git a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/EndDateField.js b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/EndDateField.js index 96327ff8..c5c164ff 100644 --- a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/EndDateField.js +++ b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/EndDateField.js @@ -15,7 +15,7 @@ function formattingDate(date) { function EndDateField(props) { return ( <CustomTextField - id="eventName" + id="endDateField" label="종료날짜" color="primary" value={formattingDate(props.endDate)} diff --git a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/GeneralSetting.js b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/GeneralSetting.js index 3a1d6162..c2074430 100644 --- a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/GeneralSetting.js +++ b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/GeneralSetting.js @@ -8,7 +8,6 @@ import InputStartDate from "./InputStartDate"; import EndDateField from "./EndDateField"; import InputEventCode from "./InputEventCode"; import InputEventLink from "./InputEventLink"; -import InputHashTag from "./InputHashTag"; import HashTagsField from "./HashTagsField"; import {generalSettingReducer} from "../../settingReducer/settingReducer"; import ButtonField from "../ButtonField"; @@ -24,15 +23,17 @@ const PopUpLayOutStyle = styled.div` function convertDataToView(eventInfo) { let eventHashTags = []; + if (eventInfo.HashTags) { - eventHashTags = eventInfo.HashTags.map(hashtag => { - return {key: uuidv1(), label: hashtag.name}; - }); + eventHashTags = eventInfo.HashTags.map(hashtag => ({ + key: uuidv1(), + label: hashtag.name, + })); } return { eventName: eventInfo.eventName, - startDate: new Date(parseInt(eventInfo.startAt)), - endDate: new Date(parseInt(eventInfo.endAt)), + startDate: new Date(parseInt(eventInfo.startAt, 10)), + endDate: new Date(parseInt(eventInfo.endAt, 10)), eventCode: eventInfo.eventCode, hashTags: eventHashTags, eventLink: `${config.url}/${window.btoa(eventInfo.eventCode)}`, @@ -41,7 +42,7 @@ function convertDataToView(eventInfo) { export default function GeneralSetting({handleClose}) { const [mutaionUpdateEvent, {updatedEvent}] = useMutation(updateEvent()); - const {hostInfo, events, setEvents} = useContext(HostContext); + const {hostInfo, events, setEvents, allEvents} = useContext(HostContext); const initialGeneralState = convertDataToView(events[0]); const [generalSettingState, dispatch] = useReducer( generalSettingReducer, @@ -104,7 +105,7 @@ export default function GeneralSetting({handleClose}) { }, }).then(res => { Object.assign(events[0], res.data.updateEvent); - setEvents([...events]); + setEvents([...allEvents]); }); handleClose(); }; @@ -127,7 +128,6 @@ export default function GeneralSetting({handleClose}) { <EndDateField endDate={endDate} /> <InputEventCode eventCode={eventCode} dispatch={setEventCode} /> <InputEventLink eventLink={eventLink} /> - <InputHashTag hashTags={hashTags} dispatch={updateHashTag} /> <HashTagsField hashTags={hashTags} dispatch={updateHashTag} /> <ButtonField submit={sendData} onClose={reset} /> </PopUpLayOutStyle> diff --git a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/HashTagsField.js b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/HashTagsField.js index f7da96d1..3d64efcf 100644 --- a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/HashTagsField.js +++ b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/HashTagsField.js @@ -2,7 +2,14 @@ import React from "react"; import {styled} from "@material-ui/core/styles"; import Chip from "@material-ui/core/Chip"; import Paper from "@material-ui/core/Paper"; -import {BookmarkBorderRounded} from "@material-ui/icons"; +import Typography from "@material-ui/core/Typography"; +import LocalOfferIcon from "@material-ui/icons/LocalOffer"; + +const CustomTypography = styled(Typography)({ + marginTop: "0.6rem", + color: "#A8A8A8", + fontSize: "0.8rem", +}); const MyPaper = styled(Paper)({ marginTop: "0.6rem", @@ -17,27 +24,22 @@ const CustomChip = styled(Chip)({ function HashTagsField(props) { const {hashTags, dispatch} = props; - const handleDelete = hashTagToDelete => () => { - const deletedHashTagList = hashTags.filter( - hashTag => hashTag.key !== hashTagToDelete.key, - ); - - dispatch(deletedHashTagList); - }; return ( - <MyPaper> - {hashTags.map(data => ( - <CustomChip - icon={<BookmarkBorderRounded />} - color="primary" - variant="outlined" - key={data.key} - label={data.label} - onDelete={handleDelete(data)} - /> - ))} - </MyPaper> + <> + <CustomTypography variant="subtitle1">해시태그</CustomTypography> + <MyPaper> + {hashTags.map(data => ( + <CustomChip + icon={<LocalOfferIcon />} + color="primary" + variant="outlined" + key={data.key} + label={data.label} + /> + ))} + </MyPaper> + </> ); } diff --git a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputEventCode.js b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputEventCode.js index cbc10512..034c3e9b 100644 --- a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputEventCode.js +++ b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputEventCode.js @@ -12,7 +12,7 @@ function InputEventCode(props) { return ( <CustomTextField - id="eventName" + id="eventCode" label="이벤트 코드" color="primary" readOnly={true} diff --git a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputEventLink.js b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputEventLink.js index 6f229767..8bac4592 100644 --- a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputEventLink.js +++ b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputEventLink.js @@ -33,7 +33,7 @@ function InputEventLink(props) { <InputWithIcon> <CustomTextField inputRef={linkRef} - id="eventName" + id="eventLink" label="이벤트 링크" color="primary" value={props.eventLink} diff --git a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputEventName.js b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputEventName.js index 2bbbd94c..ff5c32fe 100644 --- a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputEventName.js +++ b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputEventName.js @@ -1,14 +1,17 @@ import React from "react"; import {styled} from "@material-ui/core/styles"; import {TextField} from "@material-ui/core"; +import {validEventName} from "../../../../libs/eventValidation"; const CustomTextField = styled(TextField)({ width: "25rem", }); function InputEventName(props) { + const errorState = !validEventName(props.eventName); return ( <CustomTextField + error={errorState} id="eventName" label="이벤트 이름" color="primary" diff --git a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputHashTag.js b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputHashTag.js deleted file mode 100644 index b747ed00..00000000 --- a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputHashTag.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from "react"; -import {styled} from "@material-ui/core/styles"; -import {TextField} from "@material-ui/core"; -import uuidv1 from "uuid/v1"; - -const ENTER_KEY_CODE = 13; -const CustomTextField = styled(TextField)({ - marginTop: "1.3rem", - width: "25rem", -}); - -function InputHashTag(props) { - const {hashTags, dispatch} = props; - const prevHashTagList = hashTags; - const addHashTag = event => { - if (event.keyCode === ENTER_KEY_CODE) { - const hashTag = { - key: uuidv1(), - label: event.target.value, - }; - const hashTagList = [...prevHashTagList, hashTag]; - - dispatch(hashTagList); - event.target.value = ""; - } - }; - - return ( - <CustomTextField - id="inputHashTag" - label="Press Enter" - color="primary" - onKeyDown={addHashTag} - /> - ); -} - -export default InputHashTag; diff --git a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputStartDate.js b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputStartDate.js index c76e91bb..2a0630cd 100644 --- a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputStartDate.js +++ b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/GeneralSetting/InputStartDate.js @@ -27,16 +27,18 @@ const CustomTimePicker = styled(TimePicker)({ }); function InputStartDate(props) { + const {startDate, endDate} = props; const {setStartDate, setEndDate} = props.dispatch; + const minutes = moment(endDate).diff(moment(new Date()), "minutes") + 1; const [lastTime, handleLastTimeChange] = useState( - new Date().setHours(0, 0), + new Date().setHours(0, minutes), ); const calcEndDate = inputTime => { const hour = moment(inputTime).format("HH"); const minuate = moment(inputTime).format("mm"); - let addedDate = moment(props.startDate) + let addedDate = moment() .add(hour, "h") .toDate(); @@ -53,13 +55,14 @@ function InputStartDate(props) { <CustomDateTimePicker label="시작날짜" format={"yyyy년 MM월 dd일 HH시 mm분"} - value={props.startDate} + value={startDate} onChange={setStartDate} + readOnly={true} /> <CustomTimePicker clearable ampm={false} - label="유효시간" + label="남은시간" value={lastTime} onChange={calcEndDate} minutesStep={5} diff --git a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/TabHeader.js b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/TabHeader.js index 7f99a979..ba7ebeb7 100644 --- a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/TabHeader.js +++ b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/TabHeader.js @@ -11,7 +11,7 @@ const Title = styled.div` `; function TabHeader(props) { - const TITLE = {general: "기본설정", feature: "상세설정"}; + const TITLE = {general: "기본설정"}; const type = props.type; return <Title id="createEvent-modal-title">{TITLE[type]}; diff --git a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/TabNavigation.js b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/TabNavigation.js index aeac6df6..33d8fe8b 100644 --- a/frontend/host-app/src/components/EventSettingModal/EventSettingTab/TabNavigation.js +++ b/frontend/host-app/src/components/EventSettingModal/EventSettingTab/TabNavigation.js @@ -3,7 +3,6 @@ import {Tab, Box, Tabs} from "@material-ui/core"; import {styled} from "@material-ui/core/styles"; import TabContent from "./TabContent"; import GeneralSetting from "./GeneralSetting/GeneralSetting"; -import AdvanceSetting from "./AdvanceSetting/AdvanceSetting"; const MyContainer = styled(Box)({ display: "flex", @@ -37,16 +36,12 @@ export default function TabNavigation({handleClose}) { > / - - + - - - ); } diff --git a/frontend/host-app/src/components/Header.js b/frontend/host-app/src/components/Header/Header.js similarity index 75% rename from frontend/host-app/src/components/Header.js rename to frontend/host-app/src/components/Header/Header.js index 0069dd42..4cee7568 100644 --- a/frontend/host-app/src/components/Header.js +++ b/frontend/host-app/src/components/Header/Header.js @@ -3,10 +3,10 @@ import AppBar from "@material-ui/core/AppBar"; import Toolbar from "@material-ui/core/Toolbar"; import Typography from "@material-ui/core/Typography"; import {makeStyles} from "@material-ui/core/styles"; -import HeaderAccountAvatar from "./HeaderAccountAvata.js"; -import HeaderConfigAvatar from "./HeaderConfigAvata"; -import EventSettingModal from "./EventSettingModal/EventSettingModal"; -import useModal from "../customhook/useModal"; +import HeaderAccountAvatar from "./HeaderAccountAvatar.js"; +import HeaderConfigAvatar from "./HeaderConfigAvatar"; +import EventSettingModal from "../EventSettingModal/EventSettingModal"; +import useModal from "../../customhook/useModal"; const useStyles = makeStyles(() => ({ header: { @@ -21,7 +21,6 @@ const useStyles = makeStyles(() => ({ function Header() { const [settingModalOpen, handleOpen, handleClose] = useModal(); const classes = useStyles(); - const userName = "홍"; return ( @@ -35,7 +34,7 @@ function Header() { handleClose={handleClose} /> )} - +
diff --git a/frontend/host-app/src/components/Header/HeaderAccountAvatar.js b/frontend/host-app/src/components/Header/HeaderAccountAvatar.js new file mode 100644 index 00000000..6a573f13 --- /dev/null +++ b/frontend/host-app/src/components/Header/HeaderAccountAvatar.js @@ -0,0 +1,38 @@ +import React, {useContext} from "react"; +import {makeStyles} from "@material-ui/core"; +import Avatar from "@material-ui/core/Avatar"; +import {HostContext} from "../../libs/hostContext.js"; + +const useStyles = makeStyles({ + headerAvatar: { + backgroundColor: "#FFF", + color: "#212529", + margin: "0.5rem", + "&:hover": { + color: "#FFF", + backgroundColor: "#69747f", + }, + }, + avatarImage: { + width: "2.5rem", + height: "2.5rem", + borderRadius: "15px", + }, +}); + +function HeaderAccountAvatar() { + const {hostInfo} = useContext(HostContext); + const classes = useStyles(); + + return ( + + {"avatar"} + + ); +} + +export default HeaderAccountAvatar; diff --git a/frontend/host-app/src/components/HeaderConfigAvata.js b/frontend/host-app/src/components/Header/HeaderConfigAvatar.js similarity index 64% rename from frontend/host-app/src/components/HeaderConfigAvata.js rename to frontend/host-app/src/components/Header/HeaderConfigAvatar.js index 059b3d68..8bd77821 100644 --- a/frontend/host-app/src/components/HeaderConfigAvata.js +++ b/frontend/host-app/src/components/Header/HeaderConfigAvatar.js @@ -2,18 +2,19 @@ import {makeStyles, Icon} from "@material-ui/core"; import Avatar from "@material-ui/core/Avatar"; import React from "react"; -function HeaderConfigAvatar({onClick}) { - const useStyles = makeStyles({ - headerAvatar: { - backgroundColor: "#FFF", - color: "#212529", - margin: "0.5rem", - "&:hover": { - color: "#FFF", - backgroundColor: "#69747F", - }, +const useStyles = makeStyles({ + headerAvatar: { + backgroundColor: "#FFF", + color: "#212529", + margin: "0.5rem", + "&:hover": { + color: "#FFF", + backgroundColor: "#69747F", }, - }); + }, +}); + +function HeaderConfigAvatar({onClick}) { const classes = useStyles(); return ( diff --git a/frontend/host-app/src/components/HeaderAccountAvata.js b/frontend/host-app/src/components/HeaderAccountAvata.js deleted file mode 100644 index 092b6577..00000000 --- a/frontend/host-app/src/components/HeaderAccountAvata.js +++ /dev/null @@ -1,33 +0,0 @@ -import {makeStyles} from "@material-ui/core"; -import Avatar from "@material-ui/core/Avatar"; -import React, {useContext} from "react"; -import {HostContext} from "../libs/hostContext"; - -function HeaderAccountAvata() { - const {hostInfo} = useContext(HostContext); - const useStyles = makeStyles({ - headerAvatar: { - backgroundColor: "#FFF", - color: "#212529", - margin: "0.5rem", - "&:hover": { - color: "#FFF", - backgroundColor: "#69747f", - }, - }, - avatarImage: { - width: "2.5rem", - height: "2.5rem", - borderRadius: "15px", - }, - }); - const classes = useStyles(); - - return ( - - - - ); -} - -export default HeaderAccountAvata; diff --git a/frontend/host-app/src/components/Nav.js b/frontend/host-app/src/components/Nav.js deleted file mode 100644 index 1ee62777..00000000 --- a/frontend/host-app/src/components/Nav.js +++ /dev/null @@ -1,65 +0,0 @@ -import React from "react"; -import {makeStyles, withStyles} from "@material-ui/core/styles"; -import Tabs from "@material-ui/core/Tabs"; -import Tab from "@material-ui/core/Tab"; - -const AntTabs = withStyles({ - root: { - borderBottom: "1px solid #e8e8e8", - }, - indicator: { - backgroundColor: "rgba(45,48,52,0.96)", - }, -})(Tabs); - -const AntTab = withStyles(theme => ({ - root: { - textTransform: "none", - fontWeight: theme.typography.fontWeightRegular, - marginRight: theme.spacing(4), - "&:hover": { - color: "#69747f", - opacity: 1, - }, - "&$selected": { - color: "#343b40", - fontWeight: theme.typography.fontWeightMedium, - }, - "&:focus": { - color: "#868686", - }, - }, - selected: {}, -}))(props => ); - -const useStyles = makeStyles(theme => ({ - root: { - marginBottom: theme.spacing(2), - }, - navBar: { - backgroundColor: theme.palette.background.paper, - }, -})); - -function Nav() { - const classes = useStyles(); - const [value, setValue] = React.useState(0); - - const handleChange = (event, newValue) => { - setValue(newValue); - }; - - return ( -
-
- - - - - -
-
- ); -} - -export default Nav; diff --git a/frontend/host-app/src/components/NavBar/NavBar.js b/frontend/host-app/src/components/NavBar/NavBar.js new file mode 100644 index 00000000..4a6b3d3c --- /dev/null +++ b/frontend/host-app/src/components/NavBar/NavBar.js @@ -0,0 +1,58 @@ +import React, {useState} from "react"; +import {makeStyles} from "@material-ui/core/styles"; +import PropTypes from "prop-types"; +import NavBarTab from "./NavBarTab.js"; +import NavBarTabs from "./NavBarTabs.js"; +import EventDashboard from "../EventDashboard/EventDashboard"; +import EventCardList from "../Event/EventCardList"; +import EmptyContent from "../EventDashboard/EmptyContent"; + +const useStyles = makeStyles(theme => ({ + root: { + marginBottom: theme.spacing(2), + }, + navBar: { + backgroundColor: theme.palette.background.paper, + }, +})); + +const NAV_BAR_DEFAULT_TAB_IDX = 0; + +function NavBar(props) { + const {eventNum} = props; + const [tabIdx, selectTab] = useState(0); + const classes = useStyles(); + + const onChange = (e, selectedTabIdx) => { + selectTab(selectedTabIdx); + }; + + return ( + <> +
+
+ + + +
+
+ {eventNum ? ( + + ) : ( + + )} + + ); +} + +NavBar.propTypes = { + onChange: PropTypes.func, + tabIdx: PropTypes.number, +}; + +NavBar.defaultProps = { + onChange: undefined, + tabIdx: NAV_BAR_DEFAULT_TAB_IDX, +}; + +export default NavBar; diff --git a/frontend/host-app/src/components/NavBar/NavBarTab.js b/frontend/host-app/src/components/NavBar/NavBarTab.js new file mode 100644 index 00000000..1ae3d720 --- /dev/null +++ b/frontend/host-app/src/components/NavBar/NavBarTab.js @@ -0,0 +1,25 @@ +import {withStyles} from "@material-ui/core/styles"; +import Tab from "@material-ui/core/Tab"; +import React from "react"; + +const NavBarTab = withStyles(theme => ({ + root: { + textTransform: "none", + fontWeight: theme.typography.fontWeightRegular, + marginRight: theme.spacing(4), + "&:hover": { + color: "#69747f", + opacity: 1, + }, + "&$selected": { + color: "#343b40", + fontWeight: theme.typography.fontWeightMedium, + }, + "&:focus": { + color: "#868686", + }, + }, + selected: {}, +}))(props => ); + +export default NavBarTab; diff --git a/frontend/host-app/src/components/NavBar/NavBarTabs.js b/frontend/host-app/src/components/NavBar/NavBarTabs.js new file mode 100644 index 00000000..a4e0d0de --- /dev/null +++ b/frontend/host-app/src/components/NavBar/NavBarTabs.js @@ -0,0 +1,13 @@ +import {withStyles} from "@material-ui/core/styles"; +import Tabs from "@material-ui/core/Tabs"; + +const NavBarTabs = withStyles({ + root: { + borderBottom: "1px solid #e8e8e8", + }, + indicator: { + backgroundColor: "rgba(45,48,52,0.96)", + }, +})(Tabs); + +export default NavBarTabs; diff --git a/frontend/host-app/src/components/Poll/InitialPollData.js b/frontend/host-app/src/components/Poll/InitialPollData.js new file mode 100644 index 00000000..9b1350a1 --- /dev/null +++ b/frontend/host-app/src/components/Poll/InitialPollData.js @@ -0,0 +1,23 @@ +const RECOMMENDED_MAX_STARS = 5; +const now = new Date(); +const initialText = { + value: "", + error: false, + helperText: "", +}; + +const initialPollData = { + pollName: { + value: "", + error: false, + helperText: "", + }, + pollType: "nItems", + selectionType: "text", + texts: [initialText, initialText], + dates: [now, now], + ratingValue: RECOMMENDED_MAX_STARS, + allowDuplication: false, +}; + +export default initialPollData; diff --git a/frontend/host-app/src/components/Poll/Item.js b/frontend/host-app/src/components/Poll/Item.js index f0ad5ff7..f08a79ae 100644 --- a/frontend/host-app/src/components/Poll/Item.js +++ b/frontend/host-app/src/components/Poll/Item.js @@ -11,7 +11,7 @@ const RowWrapper = styled.div` width: 100%; height: 3rem; box-sizing: border-box; - background-color: white; //#f8f9fa; /* Gray1 */ + background-color: #f1f3f5; /* Gray1 */ & + & { margin-top: 0.5rem; } @@ -37,24 +37,21 @@ const GraphWrapper = styled.div` top: 0; left: 0; background-color: ${props => - (props.firstPlace ? "yellow" : "#ced4da")}; /* Gray4 */ + (props.firstPlace ? "yellow" : "#adb5bd")}; /* Gray5 */ height: 100%; width: ${props => props.ratio}; box-sizing: border-box; `; function Item({ - // id, content, voters, voted, totalVoters, firstPlace, - // onVote, - state, }) { return ( - onVote(id, state)} */> +
{voted && }
{content}
diff --git a/frontend/host-app/src/components/Poll/NewPollModal.js b/frontend/host-app/src/components/Poll/NewPollModal.js index 9e602f34..adceb73a 100644 --- a/frontend/host-app/src/components/Poll/NewPollModal.js +++ b/frontend/host-app/src/components/Poll/NewPollModal.js @@ -1,4 +1,4 @@ -import React, {useState, useContext} from "react"; +import React, {useContext, useReducer} from "react"; import styled from "styled-components"; import {Button, Modal} from "@material-ui/core"; import PollName from "./PollName"; @@ -8,6 +8,8 @@ import RatingBlock from "./RatingBlock"; import Duplication from "./Duplication"; import {socketClient} from "../../libs/socket.io-Client-wrapper"; import {HostContext} from "../../libs/hostContext"; +import initialPollData from "./InitialPollData"; +import newPollReducer from "./NewPollReducer"; const ModalWrapper = styled.div` display: flex; @@ -29,156 +31,117 @@ const RowWrapper = styled.div` box-sizing: border-box; `; +// 모달의 스타일 선언 +const modalStyle = { + display: "flex", + justifyContent: "center", + alignItems: "center", +}; + function NewPollModal({open, handleClose}) { // Poll이 속한 EventId const {events} = useContext(HostContext); const EventId = events[0].id; - const initialPollName = { - value: "", - error: false, - helperText: "", - }; + const [pollInfo, dispatch] = useReducer(newPollReducer, initialPollData); + + const { + pollName, + pollType, + selectionType, + texts, + dates, + allowDuplication, + ratingValue, + } = pollInfo; + // Poll 이름 - const [pollName, setPollName] = useState(initialPollName); const onPollNameChange = event => { - setPollName({ - ...pollName, - value: event.target.value, - error: false, - helperText: "", + dispatch({ + type: "POLL_NAME_CHANGE", + value: { + value: event.target.value, + error: false, + helperText: "", + }, }); }; - // createPoll()을 호출하기 전에 입력 항목들이 empty가 아닌지 체크함 - const checkValidity = () => { - let result = true; - - if (pollName.value.length === 0) { - result = false; - } - setPollName( - pollName.value.length === 0 ? - { - ...pollName, - error: true, - helperText: "투표 제목을 입력하세요", - } : - { - ...pollName, - error: false, - helperText: "", - }, - ); - - // 별점매기기의 경우는 투표제목만 입력되면 validity가 통과됨 - if (pollType === "rating") { - return result; - } - - // N지선다형의 date 인 경우, 기본값이 입력되어 있으므로 투표제목만 확인함 - if (selectionType === "date") { - return result; - } - - // 별점이 아닌 N지선다형의 text인경우에만 계속 validity를 진행함 - if (!texts.every(text => text.value.length > 0)) { - result = false; - } - setTexts( - texts.map(text => - (text.value.length === 0 ? - { - ...text, - error: true, - helperText: "항목을 입력하세요", - } : - { - ...text, - error: false, - helperText: "", - }), - ), - ); - - return result; - }; - // Poll 종류 - const [pollType, setPollType] = useState("nItems"); const onPollTypeChange = event => { - setPollType(event.target.value); + dispatch({ + type: "POLL_TYPE_CHANGE", + value: event.target.value, + }); }; // Poll 종류가 N지선다 일때, 항목들의 속성이 text 인지 date 인지 선택 - const [selectionType, setSelectionType] = useState("text"); const onSelectionTypeChange = event => { - setSelectionType(event.target.value); + dispatch({ + type: "SELECTION_TYPE_CHANGE", + value: event.target.value, + }); }; // Poll 종류가 N지선다 이고 항목들의 속성이 text일때 항목들을 관리하는 부분 - const initialText = { - value: "", - error: false, - helperText: "", - }; - const initialTexts = [initialText, initialText]; - const [texts, setTexts] = useState(initialTexts); const onTextChange = (event, id) => { - setTexts( - texts.map((text, index) => - (index === id ? - { - ...text, - value: event.target.value, - error: false, - helperText: "", - } : - text), - ), - ); + dispatch({ + type: "TEXT_CHANGE", + value: event.target.value, + id, + }); }; const onAddText = () => { - setTexts([...texts, initialText]); + dispatch({ + type: "TEXT_ADD", + }); }; const onDeleteText = id => { - setTexts(texts.filter((_, index) => index !== id)); + dispatch({ + type: "TEXT_DELETE", + id, + }); }; // Poll 종류가 N지선다 이고 항목들의 속성이 date일때 항목들을 관리하는 부분 - const now = new Date(); - const initialDates = [now, now]; - - const [dates, setDates] = useState(initialDates); const onDateChange = (newDate, id) => { - setDates(dates.map((date, index) => (index === id ? newDate : date))); + dispatch({ + type: "DATE_CHANGE", + value: newDate, + id, + }); }; const onAddDate = () => { - setDates([...dates, new Date()]); + dispatch({ + type: "DATE_ADD", + }); }; const onDeleteDate = id => { - setDates(dates.filter((_, index) => index !== id)); + dispatch({ + type: "DATE_DELETE", + id, + }); }; // Poll 종류가 별점 일때 - const RECOMMENDED_MAX_STARS = 5; const MAX_STARS = 10; - const [ratingValue, setRatingValue] = useState(RECOMMENDED_MAX_STARS); - // Poll 종류가 N지선다 일때 중복선택 옵션을 체크하는 부분 - const [allowDuplication, setDuplication] = useState(false); - const onDuplicationChange = event => { - setDuplication(event.target.checked); + const onRatingValueChange = newValue => { + dispatch({ + type: "RATING_VALUE_CHANGE", + value: newValue, + }); }; - // 모달의 스타일 선언 - const modalStyle = { - display: "flex", - justifyContent: "center", - alignItems: "center", + // Poll 종류가 N지선다 일때 중복선택 옵션을 체크하는 부분 + const onDuplicationChange = event => { + dispatch({ + type: "DUPLICATION_CHANGE", + value: event.target.checked, + }); }; const getSelectionType = () => @@ -188,6 +151,7 @@ function NewPollModal({open, handleClose}) { if (pollType === "rating") { return new Array(ratingValue); } + if (selectionType === "date") { return dates.map( date => @@ -199,6 +163,55 @@ function NewPollModal({open, handleClose}) { return texts.map(text => text.value); }; + // createPoll()을 호출하기 전에 입력 항목들이 empty가 아닌지 체크함 + const checkValidity = () => { + let result = true; + + if (pollName.value.length === 0) { + result = false; + } + + if (pollName.value.length === 0) { + dispatch({ + type: "POLL_NAME_CHANGE", + value: { + ...pollName, + error: true, + helperText: "투표 제목을 입력하세요", + }, + }); + } else { + dispatch({ + type: "POLL_NAME_CHANGE", + value: { + ...pollName, + error: false, + helperText: "", + }, + }); + } + + // 별점매기기의 경우는 투표제목만 입력되면 validity가 통과됨 + if (pollType === "rating") { + return result; + } + + // N지선다형의 date 인 경우, 기본값이 입력되어 있으므로 투표제목만 확인함 + if (selectionType === "date") { + return result; + } + + // 별점이 아닌 N지선다형의 text인경우에만 계속 validity를 진행함 + if (!texts.every(text => text.value.length > 0)) { + dispatch({ + type: "TEXT_CHECK", + }); + result = false; + } + + return result; + }; + const onCreatePoll = () => { if (!checkValidity()) { return; @@ -244,7 +257,7 @@ function NewPollModal({open, handleClose}) { )} {pollType === "nItems" && ( diff --git a/frontend/host-app/src/components/Poll/NewPollReducer.js b/frontend/host-app/src/components/Poll/NewPollReducer.js new file mode 100644 index 00000000..32bb9e8a --- /dev/null +++ b/frontend/host-app/src/components/Poll/NewPollReducer.js @@ -0,0 +1,126 @@ +const initialText = { + value: "", + error: false, + helperText: "", +}; + +function newPollReducer(poll, action) { + switch (action.type) { + case "POLL_NAME_CHANGE": { + return { + ...poll, + pollName: action.value, + }; + } + case "POLL_TYPE_CHANGE": { + return { + ...poll, + pollType: action.value, + }; + } + case "SELECTION_TYPE_CHANGE": { + return { + ...poll, + selectionType: action.value, + }; + } + case "TEXT_CHANGE": { + const newTexts = poll.texts.map((text, index) => + (index === action.id ? + { + ...text, + value: action.value, + error: false, + helperText: "", + } : + text), + ); + + return { + ...poll, + texts: newTexts, + }; + } + case "TEXT_ADD": { + return { + ...poll, + texts: [...poll.texts, initialText], + }; + } + case "TEXT_DELETE": { + const newTexts = poll.texts.filter( + (_, index) => index !== action.id, + ); + + return { + ...poll, + texts: newTexts, + }; + } + case "TEXT_CHECK": { + const newTexts = poll.texts.map(text => + (text.value.length === 0 ? + { + ...text, + error: true, + helperText: "항목을 입력하세요", + } : + { + ...text, + error: false, + helperText: "", + }), + ); + + return { + ...poll, + texts: newTexts, + }; + } + case "DATE_CHANGE": { + const newDates = poll.dates.map((date, index) => + (index === action.id ? action.value : date), + ); + + return { + ...poll, + dates: newDates, + }; + } + case "DATE_ADD": { + return { + ...poll, + dates: [...poll.dates, new Date()], + }; + } + case "DATE_DELETE": { + const newDates = poll.dates.filter( + (_, index) => index !== action.id, + ); + + return { + ...poll, + dates: newDates, + }; + } + case "RATING_VALUE_CHANGE": { + return { + ...poll, + ratingValue: action.value, + }; + } + case "DUPLICATION_CHANGE": { + return { + ...poll, + allowDuplication: action.value, + }; + } + default: { + // eslint-disable-next-line no-console + console.error("Unhandled action type on newPollReducer"); + return poll; + } + } +} + +export default newPollReducer; diff --git a/frontend/host-app/src/components/Poll/PollCard.js b/frontend/host-app/src/components/Poll/PollCard.js index 2d0372aa..85b5fe4c 100644 --- a/frontend/host-app/src/components/Poll/PollCard.js +++ b/frontend/host-app/src/components/Poll/PollCard.js @@ -14,9 +14,11 @@ const ColumnWrapper = styled.div` box-sizing: border-box; border: 1px solid #dee2e6; /* Gray3 */ width: 100%; - & + & { - margin-top: 1rem; + margin-top: 1rem; + button { + margin-bottom: 1rem; } + background-color: white; `; const RowWrapper = styled.div` @@ -66,12 +68,13 @@ function PollCard(props) { let localePollDate; // socket.io, sequelize, graphQL 을 거치면서 format이 변경되어서 그때그때 처리하기 위함 // pollDate는 poll 생성시 null 임. poll/open에 의해 값이 정해지는데 그 전까지는 일단 이렇게 처리함 + if (pollDate) { localePollDate = pollDate; if (localePollDate.includes("-")) { localePollDate = new Date(localePollDate); } else { - localePollDate = new Date(parseInt(localePollDate)); + localePollDate = new Date(parseInt(localePollDate, 10)); } localePollDate = ` ${localePollDate.getMonth() + 1}월 @@ -105,7 +108,7 @@ function PollCard(props) { {pollType === "rating" && } - {`${parseInt(totalVoters).toLocaleString()} 명 참여`} + {`${parseInt(totalVoters, 10).toLocaleString()} 명 참여`} {state === "standby" && ( <> @@ -122,7 +125,7 @@ function PollCard(props) { <> - {pollData && - pollData.map(poll => )} - {standbyPollData && - standbyPollData.map(poll => ( - - ))} - {closedPollData && - closedPollData.map(poll => ( - - ))} + {runningPolls && + runningPolls.map(poll => )} + {standbyPolls && + standbyPolls.map(poll => )} + {closedPolls && + closedPolls.map(poll => )} {createPollModalOpen && ( } label="Male" />; - function PollType({pollType, onChange}) { return ( diff --git a/frontend/host-app/src/components/Questions/Buttons/ApproveButton.js b/frontend/host-app/src/components/Questions/Buttons/ApproveButton.js new file mode 100644 index 00000000..ec6f436a --- /dev/null +++ b/frontend/host-app/src/components/Questions/Buttons/ApproveButton.js @@ -0,0 +1,23 @@ +import React from "react"; +import Tooltip from "@material-ui/core/Tooltip"; +import {Icon} from "@material-ui/core"; +import useStyles from "./useButtonStyles.js"; +import {handleQuestionDatas} from "../../EventEmiter/QuestionSocketEventEmiter.js"; + +function ApproveButton(props) { + const classes = useStyles(); + + return ( + <> + + handleQuestionDatas(props.data, props.id, props.type, "active")}> + check_circle_outline + + + + ); +} +export default ApproveButton; + diff --git a/frontend/host-app/src/components/Questions/Buttons/QuestionCompleteButton.js b/frontend/host-app/src/components/Questions/Buttons/QuestionCompleteButton.js new file mode 100644 index 00000000..fc40f413 --- /dev/null +++ b/frontend/host-app/src/components/Questions/Buttons/QuestionCompleteButton.js @@ -0,0 +1,23 @@ +import React from "react"; +import Tooltip from "@material-ui/core/Tooltip"; +import {Icon} from "@material-ui/core"; +import useStyles from "./useButtonStyles.js"; +import {handleQuestionDatas} from "../../EventEmiter/QuestionSocketEventEmiter.js"; + +function QuestionCompleteButton(props) { + const classes = useStyles(); + + return ( + <> + + handleQuestionDatas(props.data, props.id, props.type, "completeQuestion")}> + check_circle_outline + + + + ); +} +export default QuestionCompleteButton; + diff --git a/frontend/host-app/src/components/Questions/QuestionMenu.js b/frontend/host-app/src/components/Questions/Buttons/QuestionMenuButton.js similarity index 72% rename from frontend/host-app/src/components/Questions/QuestionMenu.js rename to frontend/host-app/src/components/Questions/Buttons/QuestionMenuButton.js index d0a68648..0c9a991a 100644 --- a/frontend/host-app/src/components/Questions/QuestionMenu.js +++ b/frontend/host-app/src/components/Questions/Buttons/QuestionMenuButton.js @@ -3,12 +3,13 @@ import Tooltip from "@material-ui/core/Tooltip"; import Menu from "@material-ui/core/Menu"; import MenuItem from "@material-ui/core/MenuItem"; import {Icon} from "@material-ui/core"; -import useStyles from "./useStyles"; +import useStyle from "./useButtonStyles"; +import {handleQuestionDatas} from "../../EventEmiter/QuestionSocketEventEmiter.js"; const ITEM_HEIGHT = 48; -export default function QuestionMenu({id, type, handler}) { - const classes = useStyles(); +export default function QuestionMenuButton(props) { + const classes = useStyle(); const [anchorEl, setAnchorEl] = React.useState(null); const open = Boolean(anchorEl); @@ -21,14 +22,14 @@ export default function QuestionMenu({id, type, handler}) { }; const handleDelete = () => { - handler(id, type, "deleted"); + handleQuestionDatas(props.data, props.id, props.type, "deleted"); handleClose(); }; return ( <> - more_vert + more_vert + + handleQuestionDatas(props.data, props.id, props.type, "active")}> + restore + + + + ); +} +export default QuestionRestoreButton; + diff --git a/frontend/host-app/src/components/Questions/Buttons/RejectButton.js b/frontend/host-app/src/components/Questions/Buttons/RejectButton.js new file mode 100644 index 00000000..1d2de29d --- /dev/null +++ b/frontend/host-app/src/components/Questions/Buttons/RejectButton.js @@ -0,0 +1,23 @@ +import React from "react"; +import Tooltip from "@material-ui/core/Tooltip"; +import {Icon} from "@material-ui/core"; +import useStyles from "./useButtonStyles.js"; +import {handleQuestionDatas} from "../../EventEmiter/QuestionSocketEventEmiter.js"; + +function RejectButton(props) { + const classes = useStyles(); + + return ( + <> + + handleQuestionDatas(props.data, props.id, props.type, "delete")}> + highlight_off + + + + ); +} +export default RejectButton; + diff --git a/frontend/host-app/src/components/Questions/ThumbUpButton.js b/frontend/host-app/src/components/Questions/Buttons/ThumbUpButton.js similarity index 82% rename from frontend/host-app/src/components/Questions/ThumbUpButton.js rename to frontend/host-app/src/components/Questions/Buttons/ThumbUpButton.js index a865d9f7..69d40550 100644 --- a/frontend/host-app/src/components/Questions/ThumbUpButton.js +++ b/frontend/host-app/src/components/Questions/Buttons/ThumbUpButton.js @@ -1,11 +1,11 @@ import React from "react"; import {Icon, Typography} from "@material-ui/core"; -import useStyles from "./useStyles"; -import {ThumbUpContainer, ReplyContainer} from "./QuestionStyle"; +import useStyle from "./useButtonStyles"; +import {ThumbUpContainer, ReplyContainer} from "../QuestionStyle"; function ThumbUpButton(props) { - const classes = useStyles(); + const classes = useStyle(); const handleReply = () => props.replyOpenHandler(!props.replyOpenStatus); diff --git a/frontend/host-app/src/components/Questions/Buttons/TopFixButton.js b/frontend/host-app/src/components/Questions/Buttons/TopFixButton.js new file mode 100644 index 00000000..1edc8ab9 --- /dev/null +++ b/frontend/host-app/src/components/Questions/Buttons/TopFixButton.js @@ -0,0 +1,23 @@ +import React from "react"; +import Tooltip from "@material-ui/core/Tooltip"; +import {Icon} from "@material-ui/core"; +import useStyles from "./useButtonStyles.js"; +import {handleStar} from "../../EventEmiter/QuestionSocketEventEmiter"; + +function TopFixButton(props) { + const classes = useStyles(); + + return ( + <> + + handleStar(props.data, props.id)}> + stars + + + + ); +} +export default TopFixButton; + diff --git a/frontend/host-app/src/components/Questions/useStyles.js b/frontend/host-app/src/components/Questions/Buttons/useButtonStyles.js similarity index 53% rename from frontend/host-app/src/components/Questions/useStyles.js rename to frontend/host-app/src/components/Questions/Buttons/useButtonStyles.js index 1e888956..ad216e30 100644 --- a/frontend/host-app/src/components/Questions/useStyles.js +++ b/frontend/host-app/src/components/Questions/Buttons/useButtonStyles.js @@ -1,6 +1,6 @@ import {makeStyles} from "@material-ui/core"; -const useStyles = makeStyles(theme => ({ +const useButtonStyles = makeStyles(() => ({ starButton: { color: "#9e9e9e", marginLeft: "0.25rem", @@ -29,42 +29,16 @@ const useStyles = makeStyles(theme => ({ color: "#EF0046", }, }, - moreButton: { - color: "#7f7f7f", - marginLeft: "0.25rem", - "&:hover": { - color: "#ef0046", - }, - }, - footerButton: { - color: "rgb(121,121,121)", - margin: " 0.1rem", - transform: "scale(0.6)", - "&:hover": { - color: "#ef0046", - }, - }, thumbUpButton: { color: "#7f7f7f", transform: "scale(0.7)", marginLeft: "0.5rem", }, - replyIcon: { + menuButton: { color: "#7f7f7f", transform: "scale(0.7)", }, - staredQuestion: { - backgroundColor: "rgb(242,248,255)", - }, - normalQuestion: { - backgroundColor: "rgba(255,255,255,100)", - }, - cardContentPadding: { - "&:last-child": { - paddingBottom: "0.7rem", - }, - }, } )); -export default useStyles; +export default useButtonStyles; diff --git a/frontend/host-app/src/components/Questions/LiveQuestionCard.js b/frontend/host-app/src/components/Questions/LiveQuestionCard.js deleted file mode 100644 index 7d9840f6..00000000 --- a/frontend/host-app/src/components/Questions/LiveQuestionCard.js +++ /dev/null @@ -1,59 +0,0 @@ -import React, {useState} from "react"; -import Divider from "@material-ui/core/Divider"; -import Card from "@material-ui/core/Card"; -import {CardContent, Icon} from "@material-ui/core"; -import UserAvata from "./UserAvata.js"; -import {QuestionHeader, QuestionBody, QuestionInfo, QuestionMeta, QuestionButtons} from "./QuestionStyle"; -import QuestionDate from "./QuestionDate"; -import QuestionUserName from "./QuestionUserName"; -import useStyles from "./useStyles"; -import QuestionMenu from "./QuestionMenu"; -import ThumbUpButton from "./ThumbUpButton"; -import Replies from "./Replies"; -import Tooltip from "@material-ui/core/Tooltip"; - -function LiveQuestionCard(props) { - const classes = useStyles(); - const [openReplies, setOpenReplies] = useState(false); - - return ( - - - - - - - - - - - - props.handleStar(props.id)}> - stars - - - - props.dataHandler(props.id, props.type, "completeQuestion")}> - check_circle_outline - - - - - - - {props.content} - - - {(openReplies) && } - - - ); -} - -export default LiveQuestionCard; diff --git a/frontend/host-app/src/components/Questions/ModerationQuestionCard.js b/frontend/host-app/src/components/Questions/ModerationQuestionCard.js deleted file mode 100644 index 43789d69..00000000 --- a/frontend/host-app/src/components/Questions/ModerationQuestionCard.js +++ /dev/null @@ -1,48 +0,0 @@ -import React from "react"; -import Card from "@material-ui/core/Card"; -import {CardContent, Icon} from "@material-ui/core"; -import Tooltip from "@material-ui/core/Tooltip"; -import UserAvata from "./UserAvata.js"; -import {QuestionHeader, QuestionBody, QuestionInfo, QuestionMeta, QuestionButtons} from "./QuestionStyle"; -import QuestionDate from "./QuestionDate"; -import QuestionUserName from "./QuestionUserName"; -import useStyles from "./useStyles"; - -function ModerationQuestionCard(props) { - const classes = useStyles(); - - return ( - - - - - - - - - - - - props.dataHandler(props.id, props.type, "active")}> - check_circle_outline - - - - props.dataHandler(props.id, props.type, "deleted")}> - highlight_off - - - - - - {props.content} - - - ); -} - -export default ModerationQuestionCard; diff --git a/frontend/host-app/src/components/Questions/QuestionCard/CompleteQuestionCard.js b/frontend/host-app/src/components/Questions/QuestionCard/CompleteQuestionCard.js new file mode 100644 index 00000000..5b5c6a9f --- /dev/null +++ b/frontend/host-app/src/components/Questions/QuestionCard/CompleteQuestionCard.js @@ -0,0 +1,46 @@ +import React, {useState} from "react"; +import Card from "@material-ui/core/Card"; +import {CardContent} from "@material-ui/core"; +import Divider from "@material-ui/core/Divider"; +import UserAvatar from "./UserAvatar.js"; +import {QuestionHeader, QuestionBody, QuestionInfo, QuestionMeta, QuestionButtons} from "../QuestionStyle"; +import QuestionDate from "./QuestionDate"; +import QuestionUserName from "./QuestionUserName"; +import useQuestionCardStyles from "./useQuestionCardStyles"; +import QuestionMenuButton from "../Buttons/QuestionMenuButton"; +import ThumbUpButton from "../Buttons/ThumbUpButton"; +import Replies from "./Replies"; +import QuestionRestoreButton from "../Buttons/QuestionRestoreButton"; + +function CompleteQuestionCard(props) { + const classes = useQuestionCardStyles(); + const [openReplies, setOpenReplies] = useState(false); + + return ( + + + + + + + + + + + + + + + + {props.content} + + + {(openReplies) && } + + + ); +} + +export default CompleteQuestionCard; diff --git a/frontend/host-app/src/components/Questions/CompleteQuestionCard.js b/frontend/host-app/src/components/Questions/QuestionCard/LiveQuestionCard.js similarity index 59% rename from frontend/host-app/src/components/Questions/CompleteQuestionCard.js rename to frontend/host-app/src/components/Questions/QuestionCard/LiveQuestionCard.js index a69722c7..96d1da9b 100644 --- a/frontend/host-app/src/components/Questions/CompleteQuestionCard.js +++ b/frontend/host-app/src/components/Questions/QuestionCard/LiveQuestionCard.js @@ -1,19 +1,20 @@ import React, {useState} from "react"; -import Card from "@material-ui/core/Card"; -import {CardContent, Icon} from "@material-ui/core"; import Divider from "@material-ui/core/Divider"; -import UserAvata from "./UserAvata.js"; -import {QuestionHeader, QuestionBody, QuestionInfo, QuestionMeta, QuestionButtons} from "./QuestionStyle"; +import Card from "@material-ui/core/Card"; +import {CardContent} from "@material-ui/core"; +import UserAvatar from "./UserAvatar.js"; +import {QuestionHeader, QuestionBody, QuestionInfo, QuestionMeta, QuestionButtons} from "../QuestionStyle"; import QuestionDate from "./QuestionDate"; import QuestionUserName from "./QuestionUserName"; -import useStyles from "./useStyles"; -import QuestionMenu from "./QuestionMenu"; -import ThumbUpButton from "./ThumbUpButton"; +import useQuestionCardStyles from "./useQuestionCardStyles"; +import QuestionMenuButton from "../Buttons/QuestionMenuButton"; +import ThumbUpButton from "../Buttons/ThumbUpButton"; import Replies from "./Replies"; -import Tooltip from "@material-ui/core/Tooltip"; +import TopFixButton from "../Buttons/TopFixButton"; +import QuestionCompleteButton from "../Buttons/QuestionCompleteButton"; -function CompleteQuestionCard(props) { - const classes = useStyles(); +function LiveQuestionCard(props) { + const classes = useQuestionCardStyles(); const [openReplies, setOpenReplies] = useState(false); return ( @@ -21,20 +22,15 @@ function CompleteQuestionCard(props) { - + - - props.dataHandler(props.id, props.type, "active")}> - restore - - - + + + @@ -49,4 +45,4 @@ function CompleteQuestionCard(props) { ); } -export default CompleteQuestionCard; +export default LiveQuestionCard; diff --git a/frontend/host-app/src/components/Questions/QuestionCard/ModerationQuestionCard.js b/frontend/host-app/src/components/Questions/QuestionCard/ModerationQuestionCard.js new file mode 100644 index 00000000..e4bded89 --- /dev/null +++ b/frontend/host-app/src/components/Questions/QuestionCard/ModerationQuestionCard.js @@ -0,0 +1,34 @@ +import React from "react"; +import Card from "@material-ui/core/Card"; +import {CardContent} from "@material-ui/core"; +import UserAvatar from "./UserAvatar.js"; +import {QuestionHeader, QuestionBody, QuestionInfo, QuestionMeta, QuestionButtons} from "../QuestionStyle"; +import QuestionDate from "./QuestionDate"; +import QuestionUserName from "./QuestionUserName"; +import ApproveButton from "../Buttons/ApproveButton"; +import RejectButton from "../Buttons/RejectButton"; + +function ModerationQuestionCard(props) { + return ( + + + + + + + + + + + + + + + + {props.content} + + + ); +} + +export default ModerationQuestionCard; diff --git a/frontend/host-app/src/components/Questions/QuestionDate.js b/frontend/host-app/src/components/Questions/QuestionCard/QuestionDate.js similarity index 100% rename from frontend/host-app/src/components/Questions/QuestionDate.js rename to frontend/host-app/src/components/Questions/QuestionCard/QuestionDate.js diff --git a/frontend/host-app/src/components/Questions/QuestionUserName.js b/frontend/host-app/src/components/Questions/QuestionCard/QuestionUserName.js similarity index 100% rename from frontend/host-app/src/components/Questions/QuestionUserName.js rename to frontend/host-app/src/components/Questions/QuestionCard/QuestionUserName.js diff --git a/frontend/host-app/src/components/Questions/Replies.js b/frontend/host-app/src/components/Questions/QuestionCard/Replies.js similarity index 74% rename from frontend/host-app/src/components/Questions/Replies.js rename to frontend/host-app/src/components/Questions/QuestionCard/Replies.js index 582e8551..1654a931 100644 --- a/frontend/host-app/src/components/Questions/Replies.js +++ b/frontend/host-app/src/components/Questions/QuestionCard/Replies.js @@ -1,21 +1,21 @@ import React from "react"; import Divider from "@material-ui/core/Divider"; import {Icon, Typography} from "@material-ui/core"; -import {ReplyBody, ReplyInfo, QuestionMeta, ReplyContainer} from "./QuestionStyle"; +import {ReplyBody, ReplyInfo, QuestionMeta, ReplyContainer} from "../QuestionStyle"; import QuestionUserName from "./QuestionUserName"; import QuestionDate from "./QuestionDate"; -import useStyles from "./useStyles"; +import useQuestionCardStyles from "./useQuestionCardStyles"; function Replies(props) { - const classes = useStyles(); + const classes = useQuestionCardStyles(); return (
- {props.replies.map(reply => ( - <> + {props.replies.map((reply, i) => ( +
subdirectory_arrow_right @@ -26,11 +26,11 @@ function Replies(props) { - + {reply.content} - ))} +
))}
); } diff --git a/frontend/host-app/src/components/Questions/UserAvata.js b/frontend/host-app/src/components/Questions/QuestionCard/UserAvatar.js similarity index 75% rename from frontend/host-app/src/components/Questions/UserAvata.js rename to frontend/host-app/src/components/Questions/QuestionCard/UserAvatar.js index 73fda976..5ab97bc3 100644 --- a/frontend/host-app/src/components/Questions/UserAvata.js +++ b/frontend/host-app/src/components/Questions/QuestionCard/UserAvatar.js @@ -18,7 +18,7 @@ function NamedAvata({guestName}) { return {inner}; } -function AnonymousAvata() { +function AnonymousAvatar() { return ( @@ -26,8 +26,8 @@ function AnonymousAvata() { ); } -function UserAvata({isAnonymous, guestName}) { - return isAnonymous ? : ; +function UserAvatar({isAnonymous, guestName}) { + return isAnonymous ? : ; } -export default UserAvata; +export default UserAvatar; diff --git a/frontend/host-app/src/components/Questions/QuestionCard/useQuestionCardStyles.js b/frontend/host-app/src/components/Questions/QuestionCard/useQuestionCardStyles.js new file mode 100644 index 00000000..e419e0de --- /dev/null +++ b/frontend/host-app/src/components/Questions/QuestionCard/useQuestionCardStyles.js @@ -0,0 +1,22 @@ +import {makeStyles} from "@material-ui/core"; + +const useQuestionCardStyles = makeStyles(() => ({ + replyIcon: { + color: "#7f7f7f", + transform: "scale(0.7)", + }, + staredQuestion: { + backgroundColor: "rgb(242,248,255)", + }, + normalQuestion: { + backgroundColor: "rgba(255,255,255,100)", + }, + cardContentPadding: { + "&:last-child": { + paddingBottom: "0.7rem", + }, + }, +} +)); + +export default useQuestionCardStyles; diff --git a/frontend/host-app/src/components/Questions/QuestionContainer.js b/frontend/host-app/src/components/Questions/QuestionContainer.js index 06e9cde6..39730ee0 100644 --- a/frontend/host-app/src/components/Questions/QuestionContainer.js +++ b/frontend/host-app/src/components/Questions/QuestionContainer.js @@ -1,17 +1,15 @@ import React from "react"; -import ModerationQuestionCard from "./ModerationQuestionCard.js"; -import LiveQuestionCard from "./LiveQuestionCard"; -import CompleteQuestionCard from "./CompleteQuestionCard"; +import ModerationQuestionCard from "./QuestionCard/ModerationQuestionCard.js"; +import LiveQuestionCard from "./QuestionCard/LiveQuestionCard"; +import CompleteQuestionCard from "./QuestionCard/CompleteQuestionCard"; import PollApollo from "../Poll/PollApollo.js"; import {filterQuestion, filterReplies} from "../../libs/utils"; import {FocusedDiv, UnFocusedDiv} from "./QuestionStyle"; -const compareByCreateAt = (a, b) => - (a.createdAt < b.createdAt ? 1 : a.createdAt > b.createdAt ? -1 : 0); -const compareByLikeCount = (a, b) => - (a.likeCount < b.likeCount ? 1 : a.likeCount > b.likeCount ? -1 : 0); +const compareByCreateAt = (a, b) => b.createdAt.localeCompare(a.createdAt); +const compareByLikeCount = (a, b) => b.likeCount - a.likeCount; -function QuestionContainer({datas, type, dataHandler, handleStar, containerType}) { +function QuestionContainer({datas, type, containerType}) { const QuestionDiv = containerType === "focus" ? FocusedDiv : UnFocusedDiv; return ( @@ -20,10 +18,9 @@ function QuestionContainer({datas, type, dataHandler, handleStar, containerType} filterQuestion("moderation", datas).questions.map(question => ( ))} {type === "popularQuestion" && @@ -32,11 +29,10 @@ function QuestionContainer({datas, type, dataHandler, handleStar, containerType} .map(question => ( ))} {type === "newQuestion" && @@ -45,22 +41,20 @@ function QuestionContainer({datas, type, dataHandler, handleStar, containerType} .map(question => ( ))} {type === "completeQuestion" && filterQuestion("completeQuestion", datas).questions.map(question => ( ))} {(type === "poll" && containerType !== "focus") && } diff --git a/frontend/host-app/src/components/Questions/QuestionDummyData.js b/frontend/host-app/src/components/Questions/QuestionDummyData.js deleted file mode 100644 index 7389de60..00000000 --- a/frontend/host-app/src/components/Questions/QuestionDummyData.js +++ /dev/null @@ -1,64 +0,0 @@ -function DummyData() { - return [ - { - id: 0, - userName: "오랑캐1", - date: new Date(), - question: "오늘 마스터 클래스 좋네요", - isAnonymous: false, - status: "moderation", - isStared: false, - }, { - id: 1, - userName: "오랑캐2", - date: new Date(), - question: "회의실이 부족해요", - isAnonymous: false, - status: "moderation", - isStared: false, - }, { - id: 2, - userName: "오랑캐3", - date: new Date(), - question: - "이 질문은 정말 길어서 끝까지 읽을 수도 없는 정도로 어마어마 무지무지하게 긴 질문입니다 저보다 더 긴 질문을 하시(중략...)", - isAnonymous: false, - status: "newQuestion", - isStared: false, - }, { - id: 3, - userName: "오랑캐4", - date: new Date(), - question: "짧은 질문입니다", - isAnonymous: false, - status: "newQuestion", - isStared: false, - }, { - id: 4, - userName: "Anonymous", - date: new Date(), - question: "점심 뭐먹나요", - isAnonymous: true, - status: "popularQuestion", - isStared: false, - }, { - id: 5, - userName: "Anonymous", - date: new Date(), - question: "해장국이요", - isAnonymous: true, - status: "popularQuestion", - isStared: false, - }, { - id: 6, - userName: "Anonymous", - date: new Date(), - question: "오늘의 질문을 종료합니다", - isAnonymous: true, - status: "completeQuestion", - isStared: false, - }, - ]; -} - -export default DummyData; diff --git a/frontend/host-app/src/components/Questions/QuestionReducer.js b/frontend/host-app/src/components/Questions/QuestionReducer.js index 8ae46635..1f6757b3 100644 --- a/frontend/host-app/src/components/Questions/QuestionReducer.js +++ b/frontend/host-app/src/components/Questions/QuestionReducer.js @@ -1,12 +1,11 @@ const QuestionsReducer = (state, action) => { const actionTable = { - addNewQuestion: () => { - console.log(action.data); - return ({questions: [...state.questions, action.data]}); - }, + addNewQuestion: () => ({questions: [...state.questions, action.data]}), toggleStar: () => { const newData = state.questions.map(e => { - (e.id !== action.data.id) ? (e.isStared = false) : (e.isStared = action.data.isStared); + e.id !== action.data.id ? + (e.isStared = false) : + (e.isStared = action.data.isStared); return e; }); @@ -17,22 +16,49 @@ const QuestionsReducer = (state, action) => { if (action.data.id === "all") { newData = state.questions.map(e => { - if (e.state === action.data.from) { e.state = action.data.to; } + if (e.state === action.data.from) { + e.state = action.data.to; + } + return e; }); } else { newData = state.questions.map(e => { - if (e.id === action.data.id) { e.state = action.data.to; } + if (e.id === action.data.id) { + e.state = action.data.to; + } + return e; }); } return {questions: [...newData]}; }, + updateQuestion: () => { + const newData = state.questions.map(e => { + if (e.id === action.data.id) { e.content = action.data.content; } + return e; + }); + + return {questions: [...newData]}; + }, + removeQuestion: () => { + const newData = state.questions.map(e => { + if (e.id === action.data.id) { + e.state = "deleted"; + } + return e; + }); + + return {questions: [...newData]}; + }, createLike: () => { const targetId = action.data.QuestionId; const newData = state.questions.map(e => { - if (e.id === targetId) e.likeCount++; + if (e.id === targetId) { + e.likeCount++; + } + return e; }); @@ -41,7 +67,10 @@ const QuestionsReducer = (state, action) => { removeLike: () => { const targetId = action.data.QuestionId; const newData = state.questions.map(e => { - if (e.id === targetId) e.likeCount--; + if (e.id === targetId) { + e.likeCount--; + } + return e; }); diff --git a/frontend/host-app/src/components/Questions/QuestionStyle.js b/frontend/host-app/src/components/Questions/QuestionStyle.js index 5ede7636..0cb7c259 100644 --- a/frontend/host-app/src/components/Questions/QuestionStyle.js +++ b/frontend/host-app/src/components/Questions/QuestionStyle.js @@ -32,7 +32,7 @@ const QuestionBody = styled.div` `; const QuestionButtons = styled.div` - margin-left:auto; + margin-left: auto; `; const ThumbUpContainer = styled.div` @@ -43,6 +43,7 @@ const ThumbUpContainer = styled.div` const ReplyContainer = styled.div` margin-left: auto; + cursor: pointer; `; const ReplyBody = styled.div` diff --git a/frontend/host-app/src/components/RadioTitle.js b/frontend/host-app/src/components/RadioTitle.js deleted file mode 100644 index 65a86c73..00000000 --- a/frontend/host-app/src/components/RadioTitle.js +++ /dev/null @@ -1,65 +0,0 @@ -import React from "react"; -import Radio from "@material-ui/core/Radio"; -import Badge from "@material-ui/core/Badge"; -import Tooltip from "@material-ui/core/Tooltip"; -import {makeStyles} from "@material-ui/core/styles"; -import {Icon} from "@material-ui/core"; -import {TitleStyle, TitleBox, RightSide} from "./ComponentsStyle"; - -const useStyles = makeStyles(theme => ({ - margin: { - margin: theme.spacing(2)}, - icon: { - "&:hover": { - color: "#EF0046", - }, - }, -} -)); - -function RadioTitle({titleName, state, stateHandler, idx, data, dataHandler, type}) { - const SELECTED = true; - const classes = useStyles(); - - return idx <= 1 ? ( - - - {titleName} - - - - dataHandler("all", "active", "completeQuestion")} - > - launch - - - - - ) : ( - - - {titleName} - - - ); -} - -export default RadioTitle; diff --git a/frontend/host-app/src/components/Skeleton/AppSkeleton.js b/frontend/host-app/src/components/Skeleton/AppSkeleton.js new file mode 100644 index 00000000..d7bc5b76 --- /dev/null +++ b/frontend/host-app/src/components/Skeleton/AppSkeleton.js @@ -0,0 +1,15 @@ +import React from "react"; +import {Skeleton} from "@material-ui/lab"; +import SkeletonTitle from "./SkeletonTitle.js"; +import SkeletonContent from "./SkeletonContent.js"; + +function AppSkeleton() { + return ( + + + + + ); +} + +export default AppSkeleton; diff --git a/frontend/host-app/src/components/SkeletionTitle.js b/frontend/host-app/src/components/Skeleton/SkeletionTitle.js similarity index 100% rename from frontend/host-app/src/components/SkeletionTitle.js rename to frontend/host-app/src/components/Skeleton/SkeletionTitle.js diff --git a/frontend/host-app/src/components/SkeletonContent.js b/frontend/host-app/src/components/Skeleton/SkeletonContent.js similarity index 84% rename from frontend/host-app/src/components/SkeletonContent.js rename to frontend/host-app/src/components/Skeleton/SkeletonContent.js index 72a1593e..5245be91 100644 --- a/frontend/host-app/src/components/SkeletonContent.js +++ b/frontend/host-app/src/components/Skeleton/SkeletonContent.js @@ -1,10 +1,10 @@ import {Skeleton} from "@material-ui/lab"; import React from "react"; -import {ContentStyle, SkeletonColumnStyle} from "./ComponentsStyle"; +import {SkeletonContentStyle, SkeletonColumnStyle} from "./SkeletonStyle"; function SkeletonContent() { return ( - + @@ -20,7 +20,7 @@ function SkeletonContent() { - + ); } diff --git a/frontend/host-app/src/components/Skeleton/SkeletonStyle.js b/frontend/host-app/src/components/Skeleton/SkeletonStyle.js new file mode 100644 index 00000000..bf081a8e --- /dev/null +++ b/frontend/host-app/src/components/Skeleton/SkeletonStyle.js @@ -0,0 +1,31 @@ +import styled from "styled-components"; + +const SkeletonColumnStyle = styled.div` + display: flex; + flex-direction: column; + flex: 1; + justify-content: flex-start; + align-items: center; + border-radius: 8px; + min-width: "20rem"; + height: "100%"; + box-sizing: border-box; + & + & { + margin-left: 8px; + } +`; + +const SkeletonContentStyle = styled.div` + display: flex; + flex-direction: row; + flex: 1; + overflow: auto; + justify-content: left; + align-items: flex-start; + padding: 4px 8px; + overflow-x: auto; + flex-wrap: nowrap; +`; + +export {SkeletonColumnStyle, SkeletonContentStyle}; + diff --git a/frontend/host-app/src/components/Skeleton/SkeletonTitle.js b/frontend/host-app/src/components/Skeleton/SkeletonTitle.js new file mode 100644 index 00000000..082f4c85 --- /dev/null +++ b/frontend/host-app/src/components/Skeleton/SkeletonTitle.js @@ -0,0 +1,12 @@ +import {Skeleton} from "@material-ui/lab"; +import React from "react"; + +function SkeletonTitle() { + return (<> + + + + ); +} + +export default SkeletonTitle; diff --git a/frontend/host-app/src/components/SwitchTitle.js b/frontend/host-app/src/components/SwitchTitle.js deleted file mode 100644 index 782d6291..00000000 --- a/frontend/host-app/src/components/SwitchTitle.js +++ /dev/null @@ -1,45 +0,0 @@ -import React, {useContext} from "react"; -import Switch from "@material-ui/core/Switch"; -import Badge from "@material-ui/core/Badge"; -import {makeStyles} from "@material-ui/core"; -import {socketClient, useSocket} from "../libs/socket.io-Client-wrapper"; -import {HostContext} from "../libs/hostContext"; -import {TitleStyle, TitleBox} from "./ComponentsStyle"; - -const useStyles = makeStyles(theme => ({ - margin: { - margin: theme.spacing(2), - }, -})); - -function SwitchTitle({titleName, state, stateHandler, data}) { - const {events} = useContext(HostContext); - const classes = useStyles(); - const eventId = events[0].id; // dummyEventId - - const moderationEventEmit = () => - socketClient.emit("moderation/toggle", {eventId, state: !state}); - - useSocket("moderation/toggle", req => { - console.log(req); - stateHandler(req.state); - }); - - return ( - - - {titleName} - moderationEventEmit()} - /> - - ); -} - -export default SwitchTitle; diff --git a/frontend/host-app/src/components/Title.js b/frontend/host-app/src/components/Title.js deleted file mode 100644 index c5990653..00000000 --- a/frontend/host-app/src/components/Title.js +++ /dev/null @@ -1,60 +0,0 @@ -import React from "react"; -import SwitchTitle from "./SwitchTitle"; -import RadioTitle from "./RadioTitle"; -import {filterQuestion} from "../libs/utils"; - -const titleMap = { - moderation: { - titleName: "질문 검열", - columnIndex: null, - }, - newQuestion: { - titleName: "최신 질문", - columnIndex: 0, - }, - popularQuestion: { - titleName: "인기 질문", - columnIndex: 1, - }, - completeQuestion: { - titleName: "완료 질문", - columnIndex: 2, - }, - poll: { - titleName: "투표", - columnIndex: 3, - }, -}; - -function Title({type, state, stateHandler, data, dataHandler}) { - if (type === "moderation") { - return ; - } else if (type === "completeQuestion") { - return ; - } else { - return ; - } -} - -export default Title; diff --git a/frontend/host-app/src/components/useSocketHandler.js b/frontend/host-app/src/components/useSocketHandler.js deleted file mode 100644 index 2a525192..00000000 --- a/frontend/host-app/src/components/useSocketHandler.js +++ /dev/null @@ -1,18 +0,0 @@ -import {useSocket} from "../libs/socket.io-Client-wrapper"; -import {makeNewData} from "../libs/utils"; - -const useSocketHandler = dispatch => { - useSocket("question/create", req => dispatch({type: "addNewQuestion", data: makeNewData(req)})); - useSocket("question/toggleStar", req => dispatch({type: "toggleStar", data: req})); - useSocket("question/move", req => dispatch({type: "moveQuestion", data: req})); - useSocket("questionLike/create", req => dispatch({ - type: "createLike", - data: {QuestionId: req.QuestionId}, - })); - useSocket("questionLike/remove", req => dispatch({ - type: "removeLike", - data: {QuestionId: req.QuestionId}, - })); -}; - -export default useSocketHandler; diff --git a/frontend/host-app/src/config/env.dev.config.js b/frontend/host-app/src/config/env.dev.config.js index ecffd179..91b9f643 100644 --- a/frontend/host-app/src/config/env.dev.config.js +++ b/frontend/host-app/src/config/env.dev.config.js @@ -3,6 +3,7 @@ const config = { websocketHost: "http://127.0.0.1", websocketPort: 4001, apolloURI: "http://localhost:8000/graphql", + inValidHostRedirectURL: "http://localhost:5000", }; export default config; diff --git a/frontend/host-app/src/config/env.prod.config.js b/frontend/host-app/src/config/env.prod.config.js index 06e33e76..dc9c7a4b 100644 --- a/frontend/host-app/src/config/env.prod.config.js +++ b/frontend/host-app/src/config/env.prod.config.js @@ -3,6 +3,7 @@ const config = { websocketHost: "http://www.vaagle.com", websocketPort: 4000, apolloURI: "http://www.vaagle.com:8000/graphql", + inValidGuestRedirectURL: "http://www.vaagle.com", }; export default config; diff --git a/frontend/host-app/src/customhook/useModal.js b/frontend/host-app/src/customhook/useModal.js index 477222c9..e5cf2ef6 100644 --- a/frontend/host-app/src/customhook/useModal.js +++ b/frontend/host-app/src/customhook/useModal.js @@ -1,7 +1,7 @@ import {useState} from "react"; const useModal = () => { - const [open, setOpen] = useState(""); + const [open, setOpen] = useState(false); const handleOpen = () => { setOpen(true); diff --git a/frontend/host-app/src/customhook/useSnackBar.js b/frontend/host-app/src/customhook/useSnackBar.js new file mode 100644 index 00000000..fd783dc4 --- /dev/null +++ b/frontend/host-app/src/customhook/useSnackBar.js @@ -0,0 +1,15 @@ +import {useState} from "react"; + +const useSnackBar = () => { + const [snackBarOpen, setSnackBarOpen] = useState(false); + const snackBarHandleClose = reason => { + if (reason === "clickaway") { + return; + } + setSnackBarOpen(false); + }; + + return {snackBarOpen, snackBarHandleClose, setSnackBarOpen}; +}; + +export default useSnackBar; diff --git a/frontend/host-app/src/index.css b/frontend/host-app/src/index.css index 1a253974..96913445 100644 --- a/frontend/host-app/src/index.css +++ b/frontend/host-app/src/index.css @@ -12,7 +12,6 @@ code { monospace; } -/* hkwon added */ html, body, #root { diff --git a/frontend/host-app/src/libs/createApolloClient.js b/frontend/host-app/src/libs/createApolloClient.js index 2b3a62fe..961bc787 100644 --- a/frontend/host-app/src/libs/createApolloClient.js +++ b/frontend/host-app/src/libs/createApolloClient.js @@ -3,7 +3,7 @@ import { createHttpLink } from "apollo-link-http"; import { InMemoryCache } from "apollo-cache-inmemory"; import { setContext } from "apollo-link-context"; -export default function creaetApolloClient(uri, token) { +export default function createApolloClient(uri, token) { const httpLink = createHttpLink({ uri }); if (token) { const authLink = setContext((_, { headers }) => { diff --git a/frontend/host-app/src/libs/eventValidation.js b/frontend/host-app/src/libs/eventValidation.js index fb96b59f..95f33792 100644 --- a/frontend/host-app/src/libs/eventValidation.js +++ b/frontend/host-app/src/libs/eventValidation.js @@ -8,16 +8,14 @@ function validDate(startDate, endDate) { const isAfterEndDate = moment .duration(moment(endDate).diff(moment(startDate))) .asMinutes(); - if (diffValue < -1 || isAfterEndDate < 0) { - return false; - } - return true; + + return !(diffValue < -1 || isAfterEndDate < 0); } function validEventName(eventName) { const regex = /^[ㄱ-ㅎ|ㅏ-ㅣ|가-힣a-z0-9_\-\*\!\@\&\%\$\#\ ]{1,100}$/gi; - const result = regex.test(eventName); - return result; + + return regex.test(eventName); } export {validEventName, validDate}; diff --git a/frontend/host-app/src/libs/gql.js b/frontend/host-app/src/libs/gql.js index b44f7a23..b125940c 100644 --- a/frontend/host-app/src/libs/gql.js +++ b/frontend/host-app/src/libs/gql.js @@ -2,99 +2,100 @@ import {gql} from "apollo-boost"; function getEventsByHost() { return gql` - query { - init { - events { - id - eventCode - eventName - startAt - endAt - moderationOption - replyOption - HashTags { - id - name - EventId - } - } - host { - oauthId - id - name - image - email - } - } - } + query { + init { + events { + id + eventCode + eventName + startAt + endAt + moderationOption + replyOption + HashTags { + id + name + EventId + } + } + host { + oauthId + id + name + image + email + } + } + } `; } function createEvent() { return gql` - mutation Query($info: EventInfo!) { - createEvent(info: $info) { - id - eventCode - eventName - moderationOption - replyOption - endAt - startAt - HostId - } - } + mutation Query($info: EventInfo!) { + createEvent(info: $info) { + id + eventCode + eventName + moderationOption + replyOption + endAt + startAt + HostId + } + } `; } function createHashTags() { return gql` - mutation Mutation($hashTags: [HashTagInput]!) { - createHashTags(hashTags: $hashTags) { - id - } - } + mutation Mutation($hashTags: [HashTagInput]!) { + createHashTags(hashTags: $hashTags) { + id + } + } `; } function setModerationOptionById(eventId, moderationOption) { return gql` - mutation{ - moderation(eventId: 2, moderationOption: ${moderationOption}) - } -`; + mutation{ + moderation(eventId: 2, moderationOption: ${moderationOption}) + } + `; } function updateEvent() { return gql` - mutation Mutation($event: EventUpdate!) { - updateEvent(event: $event) { - id - eventCode - eventName - moderationOption - replyOption - endAt - startAt - HostId - } - } + mutation Mutation($event: EventUpdate!) { + updateEvent(event: $event) { + id + eventCode + eventName + moderationOption + replyOption + endAt + startAt + HostId + } + } `; } function getQuestionsByEventCodeAndGuestId() { return gql` - { - questions(eventCode: "97st", GuestId: 148) { - content - id - didILiked - isStared - GuestId - state - createdAt - guestName - isStared + { + questions(eventCode: "97st", GuestId: 148) { + content + id + didILiked + isStared + GuestId + state + createdAt + guestName + isStared + } } `; } diff --git a/frontend/host-app/src/libs/serviceWorker.js b/frontend/host-app/src/libs/serviceWorker.js index 1f9e9f45..669d10fb 100644 --- a/frontend/host-app/src/libs/serviceWorker.js +++ b/frontend/host-app/src/libs/serviceWorker.js @@ -133,7 +133,7 @@ function checkValidServiceWorker(swUrl, config) { export function unregister() { if ("serviceWorker" in navigator) { navigator.serviceWorker.ready.then(registration => { - registration.unregister(); + registration.unregister().then(r => {}); }); } } diff --git a/frontend/host-app/src/libs/socket.io-Client-wrapper.js b/frontend/host-app/src/libs/socket.io-Client-wrapper.js index 0dc5b11f..7747f620 100644 --- a/frontend/host-app/src/libs/socket.io-Client-wrapper.js +++ b/frontend/host-app/src/libs/socket.io-Client-wrapper.js @@ -1,11 +1,10 @@ import io from "socket.io-client"; -import {useEffect} from "react"; import Cookie from "js-cookie"; function getSocket(URL) { const cookieName = "vaagle-host"; const token = Cookie.get(cookieName); - const socket = io(URL, { query: { token: token } }); + const socket = io(URL, {query: {token: token}}); socket.on("connect", () => { console.log( diff --git a/frontend/host-app/src/libs/useQueryQuestions.js b/frontend/host-app/src/libs/useQueryQuestions.js index 3c6cdc04..0c6f6df1 100644 --- a/frontend/host-app/src/libs/useQueryQuestions.js +++ b/frontend/host-app/src/libs/useQueryQuestions.js @@ -3,8 +3,6 @@ import {gql} from "apollo-boost"; import {JSONNestJoin, JSONNestJoin2} from "./utils.js"; import _ from "lodash" - - function buildQuestions(object) { const copyData = _.cloneDeep(object); let {questions, emojis, guests,} = copyData; @@ -75,7 +73,6 @@ export default function useQueryQuestions( } ) { const {data, loading, error} = useQuery(QUERY_INIT_QUESTIONS, options); - console.log(data); let newData = undefined; let newOption = undefined; if (data) { diff --git a/frontend/host-app/src/libs/utils.js b/frontend/host-app/src/libs/utils.js index 5873c5cc..bcf74923 100644 --- a/frontend/host-app/src/libs/utils.js +++ b/frontend/host-app/src/libs/utils.js @@ -1,5 +1,7 @@ +import moment from "moment"; + export function makeNewData(req) { - const newData = { + return { Emojis: [], GuestId: req.GuestId, content: req.content, @@ -11,43 +13,47 @@ export function makeNewData(req) { QuestionId: req.QuestionId, isStared: false, }; - return newData; } -export function filterQuestion(option, data){ - return {questions: data.questions.filter(e => e.state === option && e.QuestionId === null)}; +export function filterQuestion(option, data) { + return { + questions: data.questions.filter( + e => e.state === option && e.QuestionId === null, + ), + }; } -export function filterStared(option, data){ - return {questions: data.questions.filter(e => { - if (e.QuestionId !== null) return true; - return e.isStared === option - })}; +export function filterStared(option, data) { + return { + questions: data.questions.filter(e => { + if (e.QuestionId !== null) return true; + return e.isStared === option; + }), + }; } -export function filterReplies(id, data){ - return {questions: data.questions.filter(e => e.QuestionId === id )}; +export function filterReplies(id, data) { + return {questions: data.questions.filter(e => e.QuestionId === id)}; } - function mappingByKey(object, key) { - const mappped = {}; + const mapped = {}; object.forEach(x => { - mappped[x[key]] = x; + mapped[x[key]] = x; }); - return mappped; + return mapped; } function unMappingByKey(object) { return Object.values(object); } -export function JSONNestJoin(parents, childs, parentKey, childKey, func) { +export function JSONNestJoin(parents, children, parentKey, childKey, func) { const mapped = mappingByKey(parents, parentKey); - childs.forEach(child => { + children.forEach(child => { const joinValue = child[childKey]; if (mapped[joinValue]) { @@ -60,8 +66,8 @@ export function JSONNestJoin(parents, childs, parentKey, childKey, func) { return unMappingByKey(mapped); } -export function JSONNestJoin2(parents, childs, parentKey, childKey, func) { - const mapped = mappingByKey(childs, childKey); +export function JSONNestJoin2(parents, children, parentKey, childKey, func) { + const mapped = mappingByKey(children, childKey); parents.forEach(parent => { const joinValue = parent[parentKey]; if (mapped[joinValue]) { @@ -71,3 +77,9 @@ export function JSONNestJoin2(parents, childs, parentKey, childKey, func) { }); return parents; } + +export const compareCurrentDateToTarget = baseDate => { + const endAt = moment(baseDate); + const current = moment(); + return endAt.diff(current, "minute"); +}; diff --git a/frontend/host-app/src/logo.svg b/frontend/host-app/src/logo.svg deleted file mode 100644 index 2e5df0d3..00000000 --- a/frontend/host-app/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/host-app/yarn.lock b/frontend/host-app/yarn.lock index 6f499f0b..9aa3e35d 100644 --- a/frontend/host-app/yarn.lock +++ b/frontend/host-app/yarn.lock @@ -1756,6 +1756,11 @@ acorn@^7.1.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c" integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ== +add-px-to-style@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/add-px-to-style/-/add-px-to-style-1.0.0.tgz#d0c135441fa8014a8137904531096f67f28f263a" + integrity sha1-0ME1RB+oAUqBN5BFMQlvZ/KPJjo= + address@1.1.2, address@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" @@ -3755,6 +3760,15 @@ dom-converter@^0.2: dependencies: utila "~0.4" +dom-css@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/dom-css/-/dom-css-2.1.0.tgz#fdbc2d5a015d0a3e1872e11472bbd0e7b9e6a202" + integrity sha1-/bwtWgFdCj4YcuEUcrvQ57nmogI= + dependencies: + add-px-to-style "1.0.0" + prefix-style "2.0.1" + to-camel-case "1.0.0" + dom-helpers@^5.0.1: version "5.1.3" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.1.3.tgz#7233248eb3a2d1f74aafca31e52c5299cc8ce821" @@ -8567,6 +8581,11 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2 source-map "^0.6.1" supports-color "^6.1.0" +prefix-style@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/prefix-style/-/prefix-style-2.0.1.tgz#66bba9a870cfda308a5dc20e85e9120932c95a06" + integrity sha1-ZrupqHDP2jCKXcIOhekSCTLJWgY= + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" @@ -8652,7 +8671,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.3" -prop-types@^15.5.4, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -8769,7 +8788,7 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== -raf@3.4.1: +raf@3.4.1, raf@^3.1.0: version "3.4.1" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== @@ -8835,6 +8854,15 @@ react-app-polyfill@^1.0.4: regenerator-runtime "0.13.3" whatwg-fetch "3.0.0" +react-custom-scrollbars@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/react-custom-scrollbars/-/react-custom-scrollbars-4.2.1.tgz#830fd9502927e97e8a78c2086813899b2a8b66db" + integrity sha1-gw/ZUCkn6X6KeMIIaBOJmyqLZts= + dependencies: + dom-css "^2.0.0" + prop-types "^15.5.10" + raf "^3.1.0" + react-dev-utils@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-9.1.0.tgz#3ad2bb8848a32319d760d0a84c56c14bdaae5e81" @@ -10261,11 +10289,23 @@ to-arraybuffer@^1.0.0: resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= +to-camel-case@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-camel-case/-/to-camel-case-1.0.0.tgz#1a56054b2f9d696298ce66a60897322b6f423e46" + integrity sha1-GlYFSy+daWKYzmamCJcyK29CPkY= + dependencies: + to-space-case "^1.0.0" + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= +to-no-case@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/to-no-case/-/to-no-case-1.0.2.tgz#c722907164ef6b178132c8e69930212d1b4aa16a" + integrity sha1-xyKQcWTvaxeBMsjmmTAhLRtKoWo= + to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" @@ -10291,6 +10331,13 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" +to-space-case@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-space-case/-/to-space-case-1.0.0.tgz#b052daafb1b2b29dc770cea0163e5ec0ebc9fc17" + integrity sha1-sFLar7Gysp3HcM6gFj5ewOvJ/Bc= + dependencies: + to-no-case "^1.0.0" + toidentifier@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" diff --git a/frontend/main-app/public/android-icon-144x144.png b/frontend/main-app/public/android-icon-144x144.png deleted file mode 100644 index b65cbf87..00000000 Binary files a/frontend/main-app/public/android-icon-144x144.png and /dev/null differ diff --git a/frontend/main-app/public/android-icon-192x192.png b/frontend/main-app/public/android-icon-192x192.png deleted file mode 100644 index 6f1148bd..00000000 Binary files a/frontend/main-app/public/android-icon-192x192.png and /dev/null differ diff --git a/frontend/main-app/public/android-icon-36x36.png b/frontend/main-app/public/android-icon-36x36.png deleted file mode 100644 index fc96f1dd..00000000 Binary files a/frontend/main-app/public/android-icon-36x36.png and /dev/null differ diff --git a/frontend/main-app/public/android-icon-48x48.png b/frontend/main-app/public/android-icon-48x48.png deleted file mode 100644 index 5cdde9da..00000000 Binary files a/frontend/main-app/public/android-icon-48x48.png and /dev/null differ diff --git a/frontend/main-app/public/android-icon-72x72.png b/frontend/main-app/public/android-icon-72x72.png deleted file mode 100644 index 6c2dcd4a..00000000 Binary files a/frontend/main-app/public/android-icon-72x72.png and /dev/null differ diff --git a/frontend/main-app/public/android-icon-96x96.png b/frontend/main-app/public/android-icon-96x96.png deleted file mode 100644 index 0ac97f36..00000000 Binary files a/frontend/main-app/public/android-icon-96x96.png and /dev/null differ diff --git a/frontend/main-app/public/apple-icon.png b/frontend/main-app/public/apple-icon.png deleted file mode 100644 index 9d0064a4..00000000 Binary files a/frontend/main-app/public/apple-icon.png and /dev/null differ diff --git a/frontend/main-app/public/favicon-16x16.png b/frontend/main-app/public/favicon-16x16.png deleted file mode 100644 index c0db580c..00000000 Binary files a/frontend/main-app/public/favicon-16x16.png and /dev/null differ diff --git a/frontend/main-app/public/favicon-32x32.png b/frontend/main-app/public/favicon-32x32.png deleted file mode 100644 index 8b271bda..00000000 Binary files a/frontend/main-app/public/favicon-32x32.png and /dev/null differ diff --git a/frontend/main-app/public/favicon-96x96.png b/frontend/main-app/public/favicon-96x96.png deleted file mode 100644 index 0ac97f36..00000000 Binary files a/frontend/main-app/public/favicon-96x96.png and /dev/null differ diff --git a/frontend/main-app/public/logo192.png b/frontend/main-app/public/logo192.png deleted file mode 100644 index fa313abf..00000000 Binary files a/frontend/main-app/public/logo192.png and /dev/null differ diff --git a/frontend/main-app/public/logo512.png b/frontend/main-app/public/logo512.png deleted file mode 100644 index bd5d4b5e..00000000 Binary files a/frontend/main-app/public/logo512.png and /dev/null differ diff --git a/frontend/main-app/public/manifest.json b/frontend/main-app/public/manifest.json index b8ac14a7..144fa033 100644 --- a/frontend/main-app/public/manifest.json +++ b/frontend/main-app/public/manifest.json @@ -6,48 +6,6 @@ "src": "favicon.ico", "sizes": "96x96 32x32 16x16", "type": "image/x-icon" - }, - { - "src": "\/android-icon-36x36.png", - "sizes": "36x36", - "type": "image\/png", - "density": "0.75" - }, - { - "src": "\/android-icon-48x48.png", - "sizes": "48x48", - "type": "image\/png", - "density": "1.0" - }, - { - "src": "\/android-icon-72x72.png", - "sizes": "72x72", - "type": "image\/png", - "density": "1.5" - }, - { - "src": "\/android-icon-96x96.png", - "sizes": "96x96", - "type": "image\/png", - "density": "2.0" - }, - { - "src": "\/android-icon-144x144.png", - "sizes": "144x144", - "type": "image\/png", - "density": "3.0" - }, - { - "src": "\/android-icon-192x192.png", - "sizes": "192x192", - "type": "image\/png", - "density": "4.0" - }, - { - "src": "\/android-icon-192x192.png", - "sizes": "192x192", - "type": "image\/png", - "density": "4.0" } ], "start_url": ".", diff --git a/frontend/main-app/public/naver.png b/frontend/main-app/public/naver.png deleted file mode 100644 index 1e7bfe69..00000000 Binary files a/frontend/main-app/public/naver.png and /dev/null differ diff --git a/frontend/main-app/src/App.js b/frontend/main-app/src/App.js index 9fd8f53b..897c0f75 100644 --- a/frontend/main-app/src/App.js +++ b/frontend/main-app/src/App.js @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React from "react"; import "./App.css"; import EventForm from "./components/EventForm"; import HostLoginMessage from "./components/HostLoginMessage"; diff --git a/frontend/main-app/src/components/EventForm.js b/frontend/main-app/src/components/EventForm.js index a7d8900a..9030835a 100644 --- a/frontend/main-app/src/components/EventForm.js +++ b/frontend/main-app/src/components/EventForm.js @@ -1,10 +1,10 @@ -import React, { useState } from "react"; +import React, {useState} from "react"; import styled from "styled-components"; import Cookie from "js-cookie"; -import { TextField, Button, Typography } from "@material-ui/core"; +import {TextField, Button, Typography} from "@material-ui/core"; import Menu from "@material-ui/core/Menu"; import MenuItem from "@material-ui/core/MenuItem"; -import { withStyles } from "@material-ui/core/styles"; +import {withStyles} from "@material-ui/core/styles"; import config from "../config"; const EventFormStyle = styled.div` diff --git a/frontend/main-app/src/components/HostLoginMessage.js b/frontend/main-app/src/components/HostLoginMessage.js index d71864a3..80062732 100644 --- a/frontend/main-app/src/components/HostLoginMessage.js +++ b/frontend/main-app/src/components/HostLoginMessage.js @@ -1,23 +1,18 @@ import React from "react"; import styled from "styled-components"; -import { Button } from "@material-ui/core"; -import { withStyles } from "@material-ui/core/styles"; +import {Button} from "@material-ui/core"; +import {withStyles} from "@material-ui/core/styles"; import config from "../config"; const HostLoginMessageStyle = styled.div` display: flex; - flex-direction: row; - justify-content: flex-end; + flex-direction: column; align-items: center; text-align: right; - // padding: 1rem 40px; - margin: 1rem 2rem; - height: 3rem; - span { - cursor: pointer; - &:hover { - text-decoration: underline; - } + margin-bottom: 2rem; + height: 4rem; + div { + margin-bottom: 0.5rem; } `; @@ -33,9 +28,8 @@ const LoginButton = withStyles({ function HostLoginMessage() { return ( - 이벤트를 만드려면, 로그인 +
이벤트를 만들려면, 로그인 해주세요.
- 해주세요.
); }