diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 000000000..66e05759c --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "javascript-picture" + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..855bd1c81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,70 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +firebase-debug.log* +firebase-debug.*.log* + +# Firebase cache +.firebase/ + +# Firebase config + +# Uncomment this if you'd like others to create their own Firebase project. +# For a team working on the same Firebase project(s), it is recommended to leave +# it commented so all members can deploy to the same project(s) in .firebaserc. +# .firebaserc + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + + +# Ignore IDE specific files +.vscode/ diff --git a/README.md b/README.md index a29ee163b..3c21fbd41 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,43 @@ # :camera: 직원 사진 관리 서비스 -직원들의 사진을 관리할 수 있는 사진 관리자 서비스를 만들어 보세요. - -과제 수행 및 리뷰 기간은 별도 공지를 참고하세요! -## [과제 수행 및 제출 방법] -1. 현재 저장소를 로컬에 클론(Clone)합니다. -2. 자신의 본명으로 브랜치를 생성합니다.(구분 가능하도록 본명을 꼭 파스칼케이스로 표시하세요, git branch KDT0_이름) -3. 자신의 본명 브랜치에서 과제를 수행합니다. -4. 과제 수행이 완료되면, 자신의 본명 브랜치를 원격 저장소에 푸시(Push)합니다.(main 브랜치에 푸시하지 않도록 꼭 주의하세요, git push origin KDT0_이름) -5. 저장소에서 main 브랜치를 대상으로 Pull Request 생성하면, 과제 제출이 완료됩니다!(E.g, main <== KDT0_이름) -6. Pull Request 링크를 LMS로도 제출해 주셔야 합니다. -7. main 혹은 다른 사람의 브랜치로 절대 병합하지 않도록 주의하세요! -8. Pull Request에서 보이는 설명을 다른 사람들이 이해하기 쉽도록 꼼꼼하게 작성하세요! -9. Pull Request에서 과제 제출 후 절대 병합(Merge)하지 않도록 주의하세요! -10. 과제 수행 및 제출 과정에서 문제가 발생한 경우, 바로 담당 멘토나 강사에서 얘기하세요! - -## [필수 요구사항] -- “AWS S3 / Firebase 같은 서비스”를 이용하여 사진을 관리할 수 있는 페이지를 구현하세요. -- 프로필 페이지를 개발하세요. -- 스크롤이 가능한 형태의 리스팅 페이지를 개발하세요. -- 전체 페이지 데스크탑-모바일 반응형 페이지를 개발하세요. -- 사진을 등록, 수정, 삭제가 가능해야 합니다. -- 유저 플로우를 제작하여 리드미에 추가하세요. +## 직원들의 사진을 관리할 수 있는 사진 관리자 서비스 +https://javascript-picture.web.app/index.html + +ID: admin@email.com + +PW: 123456 + +- 비로그인시 데이터 조회만 가능합니다. + +- admin@email.com으로 로그인 시 모든 데이터의 등록 / 수정 / 삭제가 가능합니다. 😊 + +- 회원가입/로그인 시 자신이 사진 등록 가능하며, 작성자와 로그인 한 유저가 동일 할 경우에 등록한 사진의 수정, 삭제가 가능합니다. + + +## [구현범위] +- “Firebase 서비스”를 이용하여 사진을 관리할 수 있는 페이지를 구현 +- 프로필 페이지를 개발 +- 스크롤이 가능한 형태의 리스팅 페이지를 개발 +- 전체 페이지 데스크탑-모바일 반응형 페이지를 개발 +- 사진 등록, 수정, 삭제 가능 +- 로그인 / 회원가입 페이지 개발 + * CSS * 애니메이션 구현 * 상대수치 사용(rem, em) * JavaScript * DOM event 조작 -## [선택 요구사항] -- 사진 관리 페이지와 관련된 기타 기능도 고려해 보세요. -- 페이지가 보여지기 전에 로딩 애니메이션이 보이도록 만들어보세요. -- 직원을 등록, 수정, 삭제가 가능하게 해보세요. -- 직원 검색 기능을 추가해 보세요. -- infinity scroll 기능을 추가해 보세요. -- 사진을 편집할 수 있는 기능을 추가해 보세요. +- 직원을 등록, 수정, 삭제가 가능 +- infinity scroll 기능 - LocalStorage 사용 - -## [화면 예시] -![Untitled (1)](https://github.com/KDT1-FE/Y_FE_JAVASCRIPT_PICTURE/assets/38754963/5dda6755-2501-4af4-bc3e-b63a353c44c2) - -![Untitled (2)](https://github.com/KDT1-FE/Y_FE_JAVASCRIPT_PICTURE/assets/38754963/6c1805f1-2b00-453e-a729-2b483612726d) +- 로딩페이지 구현 ## [흐름] -![Untitled](https://github.com/KDT1-FE/Y_FE_JAVASCRIPT_PICTURE/assets/38754963/e2934c05-26f6-4ef6-88d4-beed76aa007a) +![userflow](https://firebasestorage.googleapis.com/v0/b/javascript-picture.appspot.com/o/image%2Fuserflow.JPG?alt=media&token=1164b946-69e6-411b-ad31-93facf55cbab) +## [필요한 작업] +- 검색 / 정렬 기능 +- 데이터 유효성 검사 diff --git a/et HEAD~1 b/et HEAD~1 new file mode 100644 index 000000000..7f2116390 --- /dev/null +++ b/et HEAD~1 @@ -0,0 +1,113 @@ +commit 4808e2bd550a4a6092a626020846f74c45adb156 (HEAD -> KDT0_YunJiYoung) +Author: developer-jyyun +Date: Fri Aug 18 19:36:47 2023 +0900 + + Feat: Add loading page + +commit 7237b5be8b68fa40f023fcaf80bf1540e7c0f6a4 +Author: developer-jyyun +Date: Fri Aug 18 15:12:21 2023 +0900 + + Docs:Add firebase.js + +commit 5931373c1aa1cb86d695c47c20abed21223c3529 +Author: developer-jyyun +Date: Fri Aug 18 14:40:25 2023 +0900 + + Feat: Preview, login check + +commit 90d83226271c8f20ce973ee81f21599e09b15253 +Author: developer-jyyun +Date: Sat Aug 12 17:43:58 2023 +0900 + + Feat: DB delete + +commit 1aeb1d30bfce690babb9755d822b4638fc65cbbd +Author: developer-jyyun +Date: Sat Aug 12 16:36:34 2023 +0900 + + Docs: Change field name + +commit cc9c04f599ecb16b401c3d52cdbb927c2afcc5f9 +Author: developer-jyyun +Date: Sat Aug 12 16:26:42 2023 +0900 + + Feat: Add edit page + +commit 8150a30b0800b613a744fb5a4b0fb1d295b6d445 +Author: developer-jyyun +Date: Fri Aug 11 19:33:01 2023 +0900 + + Feat: Add DB_uid, DB_username + +commit 6113e2a8fd2d51f6721e727529d95f9b5185e8c6 +Author: developer-jyyun +Date: Fri Aug 11 19:31:25 2023 +0900 + + fix: Modify detail path(href) + +commit ad58f9320fc118adb96fd2ef86793144abcd30a6 +Author: developer-jyyun +Date: Fri Aug 11 19:14:28 2023 +0900 + + Feat: Add upload permission + +commit 6ab7483b6516b430e260b8ec9631a847c3fdba17 +Author: developer-jyyun +Date: Fri Aug 11 18:27:35 2023 +0900 + + Feat: Add onAuthStateChanged + +commit 5a28ac19734d92df7bb00dd7187299d1e2cbf24f +Author: developer-jyyun +Date: Thu Aug 10 17:59:55 2023 +0900 + + Design: Modify layout(index,upload) + +commit d831dfd48b61845e469442557bae59e763bd243a +Author: developer-jyyun +Date: Thu Aug 10 17:57:06 2023 +0900 + + Fix: Delete form tag + +commit ec35453339b3b3b491cb4821a7ff4e8173391296 +Author: developer-jyyun +Date: Thu Aug 10 17:50:31 2023 +0900 + + Feat: Add detail page + +commit a491878cb971f6c21454b2017a2304a82fe25f62 +Author: developer-jyyun +Date: Thu Aug 10 13:09:29 2023 +0900 + + Correct: close section tag + +commit 3b85946efe04a52a1f85b739e29bae75e66b0628 +Author: developer-jyyun +Date: Thu Aug 10 12:58:30 2023 +0900 + + Feat: Add upload & login + +commit 2798c60d88b48254cefa3088b0b560c085c7f608 +Author: developer-jyyun +Date: Wed Aug 9 22:26:46 2023 +0900 + + Feat: Add index + +commit 6eb0917c558a06852aef719ab8bb7cbd5067629d (origin/main, origin/HEAD, main) +Author: DT - Career <96465306+DT-Career@users.noreply.github.com> +Date: Tue Aug 8 17:31:50 2023 +0900 + + Update README.md + +commit 61a894eb885fd617a3218008af46cd415871e1b1 +Author: John Ahn +Date: Tue Aug 8 12:23:57 2023 +0900 + + Update README.md + +commit acc67141cdce095352aa4dc28e0e6e1dbd64cdf8 +Author: John Ahn +Date: Tue Aug 8 12:12:26 2023 +0900 + + Initial commit diff --git a/firebase.json b/firebase.json new file mode 100644 index 000000000..13c45df0f --- /dev/null +++ b/firebase.json @@ -0,0 +1,17 @@ +{ + "firestore": { + "rules": "firestore.rules", + "indexes": "firestore.indexes.json" + }, + "hosting": { + "public": "public", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ] + }, + "storage": { + "rules": "storage.rules" + } +} diff --git a/firestore.indexes.json b/firestore.indexes.json new file mode 100644 index 000000000..415027e5d --- /dev/null +++ b/firestore.indexes.json @@ -0,0 +1,4 @@ +{ + "indexes": [], + "fieldOverrides": [] +} diff --git a/firestore.rules b/firestore.rules new file mode 100644 index 000000000..cd1a4346f --- /dev/null +++ b/firestore.rules @@ -0,0 +1,9 @@ +rules_version = '2'; + +service cloud.firestore { + match /databases/{database}/documents { + match /{document=**} { + allow read, write: if false; + } + } +} \ No newline at end of file diff --git a/public/404.html b/public/404.html new file mode 100644 index 000000000..829eda8fd --- /dev/null +++ b/public/404.html @@ -0,0 +1,33 @@ + + + + + + Page Not Found + + + + +
+

404

+

Page Not Found

+

The specified file was not found on this website. Please check the URL for mistakes and try again.

+

Why am I seeing this?

+

This page was generated by the Firebase Command-Line Interface. To modify it, edit the 404.html file in your project's configured public directory.

+
+ + diff --git a/public/detail/detail.html b/public/detail/detail.html new file mode 100644 index 000000000..daa667f35 --- /dev/null +++ b/public/detail/detail.html @@ -0,0 +1,83 @@ + + + + + + + 🙋‍♀️employee picture + + + + + + +
+

🙋‍♂️---logo--- 🙋‍♀️

+ +
+ + +
+

상세정보

+
+
+ + +
+
+ + + + + + + + + + diff --git a/public/detail/detail.js b/public/detail/detail.js new file mode 100644 index 000000000..997e13411 --- /dev/null +++ b/public/detail/detail.js @@ -0,0 +1,38 @@ +db.collection("member") + .doc(queryString.get("id")) + .get() + .then((result) => { + console.log(result.data()); + + const templateDetail = ` +
+
+
+
+
+
이름 : ${result.data().name}
+
소속팀 : ${result.data().team}
+
직급 : ${result.data().rank}
+
내선번호 : ${result.data().ext}
+
연락처 : ${result.data().phone}
+
이메일 : ${result.data().email}
+
기타 : ${result.data().memo}
+
+
+ `; + document + .querySelector(".detail-area") + .insertAdjacentHTML("beforeend", templateDetail); + }); + +if (localStorage.getItem("user") != null) { + document.querySelector(".edit-btn").addEventListener("click", editBtnClick); + document.querySelectorAll(".delete-btn").forEach(el => { + el.addEventListener("click", deleteBtnClick); +}); + +} else { + document.querySelector(".edit-btn").addEventListener("click", alertLogin); + document.querySelector(".delete-btn").addEventListener("click", alertLogin); + +} diff --git a/public/edit/edit.html b/public/edit/edit.html new file mode 100644 index 000000000..9b478716e --- /dev/null +++ b/public/edit/edit.html @@ -0,0 +1,136 @@ + + + + + + + 🙋‍♀️employee picture + + + + + + +
+

🙋‍♂️---logo--- 🙋‍♀️

+ +
+ +
+

정보 수정

+
+
+ +
+ + + +
+
+ + +
+
+ + + + + + + + + +
+ + +
+
+
+ + + + + + + + + + + + + + diff --git a/public/edit/edit.js b/public/edit/edit.js new file mode 100644 index 000000000..63d0b3257 --- /dev/null +++ b/public/edit/edit.js @@ -0,0 +1,99 @@ +const employeeName = document.getElementById("name"); +const ext = document.getElementById("tel"); +const phone = document.getElementById("phone"); +const email = document.getElementById("email"); +const team = document.getElementById("team"); +const rank = document.getElementById("rank"); +const memo = document.getElementById("memo"); +const deleteImg = document.getElementById("image-delete"); +const previewImg = document.getElementById("preview-image"); +const editBtn = document.querySelector(".edit-btn"); + +db.collection("member") + .doc(queryString.get("id")) + .get() + .then((result) => { + console.log(result.data()); + console.log("수정 전 이미지url::::" + result.data().img); + employeeName.value = result.data().name; + ext.value = result.data().ext; + phone.value = result.data().phone; + email.value = result.data().email; + team.value = result.data().team; + rank.value = result.data().rank; + memo.value = result.data().memo; + deleteImg.value = result.data().img; + previewImg.src = result.data().img; + + // 수정버튼 클릭 시 + editBtn.addEventListener("click", function () { + const editImg = document.querySelector("#image").files[0]; + const imgName = document.querySelector("#image").files.name; + const randomNum = Math.round(Math.random() * 9999); + const storageRef = storage.ref(); + const editImgPath = storageRef.child("image/" + randomNum + imgName); + const updateImg = editImgPath.put(editImg); + + updateImg.on( + "state_changed", + null, + (err) => { + console.log(err); + }, + () => { + updateImg.snapshot.ref.getDownloadURL().then((editUrl) => { + console.log("수정된 경로는", editUrl); + + const editDB = { + name: employeeName.value, + ext: ext.value, + phone: phone.value, + email: email.value, + team: team.value, + rank: rank.value, + memo: memo.value, + img: editUrl, + }; + + if (previewImg.src === result.data().img) { + editDB["img"] = result.data().img; + } + + db.collection("member") + .doc(queryString.get("id")) + .update(editDB) + .then(() => { + alert(editDB.name + "님의 직원 정보가 수정되었습니다:)"); + window.location.href = + "/detail/detail.html?id=" + queryString.get("id"); + }); + }); + } + ); + }); + }); + +//이미지만 삭제 +document + .getElementById("file-delete-btn") + .addEventListener("click", function (e) { + var deleteFilename = document.getElementById("image-delete").value; + var deleteRef = storage.refFromURL(deleteFilename); + // console.log(deleteRef) + + deleteRef + .delete() + .then(function () { + alert("이미지가 삭제되었습니다!"); + window.location.reload(); + }) + .catch(function (error) { + console.error("Error removing file.", error); + }); + }); + +// 삭제 + document.querySelectorAll(".delete-btn").forEach(el => { + el.addEventListener("click", deleteBtnClick); + }); + diff --git a/public/firebase.js b/public/firebase.js new file mode 100644 index 000000000..a6fea73f0 --- /dev/null +++ b/public/firebase.js @@ -0,0 +1,20 @@ +const firebaseConfig = { + apiKey: "AIzaSyBRDdAmcnRV3_Ui_Md_vhbyp9-9-eXqzbw", + authDomain: "javascript-picture.firebaseapp.com", + projectId: "javascript-picture", + storageBucket: "javascript-picture.appspot.com", + messagingSenderId: "487659103783", + appId: "1:487659103783:web:e98479de1137818dfa9002", + measurementId: "G-0RSTQFT7N8" + }; + +// Initialize Firebase +firebase.initializeApp(firebaseConfig) ; + +const db = firebase.firestore(); +const storage = firebase.storage(); + +//url query string에 있던 자료를 object로 변환 +const queryString = new URLSearchParams(window.location.search); +queryString.get('id') + diff --git a/public/index.html b/public/index.html new file mode 100644 index 000000000..96af1c951 --- /dev/null +++ b/public/index.html @@ -0,0 +1,126 @@ + + + + + + + 🙋‍♀️employee picture + + + + + + + + + +
+ + +
+
+

....🙋....

+

....로....

+

....오....

+

....오....

+

....딩....

+

....🌞....

+
+
+
+

🙋‍♂️---logo--- 🙋‍♀️

+ +
+ +
+
+
+
이미지
+
이름
+
소속/직급
+
내선번호
+
상세정보
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + diff --git a/public/loading.css b/public/loading.css new file mode 100644 index 000000000..0e1140db1 --- /dev/null +++ b/public/loading.css @@ -0,0 +1,131 @@ +@charset "utf-8"; + +@font-face { + font-family: 'DungGeunMo'; + src: url("https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_six@1.2/DungGeunMo.woff") format('woff'); + font-weight: normal; + font-style: normal; +} + +* { + box-sizing: border-box; +} +#wrap { + position: relative; +} + +.loading { + position: fixed; + width: 100vw; + height: 100vh; + top: 0; + left: 0; + display: flex; + align-items: center; + background-color: rgb(255, 235, 248); + z-index: 10; + +} + +#loading > div { + margin: 10px auto; + border: 10px solid #aeaefa; + border-radius: 20px; + background-color: rgb(252, 251, 255); + width: 300px; + max-width: 800px; + margin: 0px auto; + max-height: 80vh; + padding: 1rem; + display: inline-block; + text-align: center; + box-shadow: 10px 10px #a4ddd2; + box-sizing: border-box; +} + +#loading .loading_txt { + animation: loading 2.5s infinite step-end; + text-align: center; + color: #aeaefa; + display: block; + font-family: "DungGeunMo"; + font-weight: bold; + font-size: 24px; + letter-spacing: 0.5rem; + margin: 2rem auto; + text-transform: uppercase; +} +#loading .loading_txt .rotate { + display: inline-block; + animation-name: rotate; + animation-duration: 6s; + animation-iteration-count: infinite; + text-shadow: 3px 3px #ffeebc; +} +#loading .loading_txt .hi { + display: inline-block; + animation-duration: 1.5s; + animation-name: hi; + animation-iteration-count: infinite; + text-shadow: 3px 3px #ffeebc; + -webkit-text-stroke: 0.5px #fffae6; +} +#loading .loading_txt:first-child { + animation-delay: 0s; +} + +#loading .loading_txt:nth-child(2) { + animation-delay: 0.1s; +} + +#loading .loading_txt:nth-child(3) { + animation-delay: 0.2s; +} +#loading .loading_txt:nth-child(4) { + animation-delay: 0.3s; +} +#loading .loading_txt:nth-child(5) { + animation-delay: 0.4s; +} +#loading .loading_txt:nth-child(6) { + animation-delay: 0.5s; +} + +@keyframes loading { + 0% { + text-shadow: 1px 1px #fff, 2px 2px #a4ddd2, 3px 3px #a4ddd2, 4px 4px #a4ddd2, + 5px 5px #a4ddd2, 6px 6px #a4ddd2; + } + 60% { + text-shadow: none; + } +} +@keyframes hi { + 0% { + transform: rotate(0deg); + } + 25% { + transform: rotate(-10deg); + } + 50% { + transform: rotate(15deg); + } + 75% { + transform: rotate(-10deg); + } + 100% { + transform: rotate(0deg); + } +} + +@keyframes rotate { + 0% { + transform: rotate(0deg); + } + 45% { + transform: rotate(360deg); + } + 90% { + transform: rotate(0deg); + } +} diff --git a/public/loading.js b/public/loading.js new file mode 100644 index 000000000..408801cc8 --- /dev/null +++ b/public/loading.js @@ -0,0 +1,16 @@ +window.onload = function () { + + const loading = document.getElementById("loading"); + + setTimeout(()=>{ + loading.style.opacity = "0"; + loading.style.transform = "scale(0)"; + loading.style.transition='all 1s'; + + },1200); + + // console.log(loading) +} + + + diff --git a/public/login/login.html b/public/login/login.html new file mode 100644 index 000000000..a0d04aa88 --- /dev/null +++ b/public/login/login.html @@ -0,0 +1,123 @@ + + + + + + + 🙋‍♀️employee picture + + + + + + +
+

🙋‍♂️---logo--- 🙋‍♀️

+ +
+ + +
+
+ + +
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + + +
+
+ +
+
+
+ + + + + + + + + + diff --git a/public/login/login.js b/public/login/login.js new file mode 100644 index 000000000..81b255a3f --- /dev/null +++ b/public/login/login.js @@ -0,0 +1,63 @@ +const joinBtn = document.getElementById("join-btn"); +const loginBtn = document.getElementById("login-btn"); + +//가입하기 버튼 누르면 +joinBtn.addEventListener("click", function () { + const joinName = document.getElementById("join__name").value; + const joinEmail = document.getElementById("join__email").value; + const joinPw = document.getElementById("join__pw").value; + + firebase + .auth() + .createUserWithEmailAndPassword(joinEmail, joinPw) + .then((result) => { + const userInfo = { + name: joinName, + email: joinEmail + }; + + db.collection("user") + .doc(result.user.uid) + .set(userInfo) + .then(() => { + window.location.reload(true); + alert("회원가입이 완료되었습니다.:)"); + // console.log(result); + // console.log(result.user); + result.user.updateProfile({ displayName: joinName }); + }); + }); +}); + +//로그인 버튼 누르면 +loginBtn.addEventListener("click", function () { + const loginEmail = document.getElementById("login__email").value; + const loginPw = document.getElementById("login__pw").value; + + firebase + .auth() + .signInWithEmailAndPassword(loginEmail, loginPw) + .then((result) => { + alert("로그인 완료!"); + window.location.href = "/index.html"; + // console.log(displayName) + }).catch(function(error) { + console.error("Error removing file.", error); + alert('로그인에 실패하였습니다. \n '+error.message) + }); +}); + +//로그인, 회원가입 탭 +const tabBtn = document.querySelectorAll(".tab-btn"); +const tabCon = document.querySelectorAll(".tab-con"); +for (let i = 0; i < tabBtn.length; i++) { + tabBtn[i].addEventListener("click", function (e) { + for (let j = 0; j < tabBtn.length; j++) { + tabBtn[j].classList.remove("act"); + tabCon[j].classList.remove("show"); + } + + tabBtn[i].classList.add("act"); + tabCon[i].classList.add("show"); + }); +} diff --git a/public/login_ck.js b/public/login_ck.js new file mode 100644 index 000000000..3d146e853 --- /dev/null +++ b/public/login_ck.js @@ -0,0 +1,89 @@ + + +if (localStorage.getItem('user') != null) { + console.log("현재로그인중"); + //로컬스토리지에서 유저정보 가져오기 + const localDataUser = localStorage.getItem("user"); + const localName = JSON.parse(localDataUser).displayName; + if(localName==null){ + document.querySelector("#user-name").innerHTML = ""; + }else{ + document.querySelector("#user-name").innerHTML = localName + "님"; + } + document.querySelector(".welcome__logout").classList.remove("none"); + document.querySelector(".welcome__login").classList.add("none"); +} else { + console.log("현재로그아웃중"); + localStorage.removeItem("user"); + document.querySelector(".welcome__logout").classList.add("none"); + document.querySelector(".welcome__login").classList.remove("none"); +} + + +//로그인 상태 확인. 로그인,로그아웃,새로고침 등 유저 인증상태 변경시마다 실행됨 +firebase.auth().onAuthStateChanged((user) => { + if (user) { + // console.log(user); + console.log(user.displayName + "로그인 완료"); + document.querySelector(".welcome__logout").classList.remove("none"); + document.querySelector(".welcome__login").classList.add("none"); + + //로컬스토리지에 유저정보 저장 + localStorage.setItem("user", JSON.stringify(user)); + // userUid = user.uid; + // console.log(userUid) + } else if ((user = null)) { + localStorage.removeItem("user"); + document.querySelector(".welcome__logout").classList.add("none"); + document.querySelector(".welcome__login").classList.remove("none"); + } +}); + +const logoutBtn = document.getElementById("logout-btn"); +//로그아웃 버튼 누르면 +logoutBtn.addEventListener("click", function () { + firebase.auth().signOut(); + localStorage.removeItem("user"); + alert("로그아웃 완료!"); + document.querySelector(".welcome__logout").classList.add("none"); + document.querySelector(".welcome__login").classList.remove("none"); +}); + + +function deleteBtnClick() { + //db정보 삭제 + db.collection("member") + .doc(queryString.get("id")) + .delete() + .then(() => { + alert("삭제되었습니다."); + window.location.href = "/"; + + //이미지 삭제 + var deleteFilename = document.getElementById("image-delete").value; + var deleteRef = storage.refFromURL(deleteFilename); + // console.log(deleteRef) + + deleteRef + .delete() + .then(function () { + console.log("File deleted successfully"); + }) + .catch(function (error) { + console.error("Error removing file.", error); + }); + }); + } + + function editBtnClick(){ + window.location.href='/edit/edit.html?id='+ queryString.get('id') +} + + + function alertLogin(){ + alert('로그인이 필요합니다!') + window.location.href='/login/login.html' + } + + + diff --git a/public/main.css b/public/main.css new file mode 100644 index 000000000..195234199 --- /dev/null +++ b/public/main.css @@ -0,0 +1,423 @@ +/*common */ + +body { + font-size: 14px; + background:#fffefd; +} +* { + font-family: "NanumSquareNeo"; +} + +a, +button { + cursor: pointer; +} + +:root { + --sky: #78a4b8; + --navy: #7888a9; + --yellow: #ffdf81; +} + +h1 { + text-align: center; + font-size: 2rem; + margin: 3rem auto; +} +h2 { + text-align: center; + font-size: 1.4rem; + width: 100%; + padding: 20px; + box-sizing: border-box; + border-radius: 0; + color: #575279; + border: 1px solid #575279; + border-left: none; + border-right: none; +} +.none, +main .profile_title { + display: none; +} + +.txt-c { + text-align: center; +} + +.mr1 { + margin: 1rem auto; +} + +.pos-r { + position: relative; +} + +/* layout */ +.nav .gnb { + display: flex; + justify-content: flex-start; + align-items: center; + flex-wrap: wrap; + width: 100%; + gap: 20px; + margin: 1.4rem auto; + font-size: 1rem; +} +.gnb__welcome { + display: flex; + justify-content: flex-end; + width: 100%; + background: #183153; + color: #fff; + padding: 0 0 0 15px; + position: relative; + text-align: right; +} + +.gnb__welcome button { + position: relative; + left: 0px; + border-radius: 3px; + border: solid #333;background:#eee; + margin-left: 10px; +} + + +.gnb li a { + font-weight: 800; + color: #183153; +} + +.search { + text-align: center; +} +section, +main, +header { + width: 90%; + margin: 0 auto; + max-width: 900px; +} +.input-col { + width: 100%; + max-width: 500px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: 0.5rem; + margin: 40px auto; +} + +.btn { + font-weight: bold; + width: 100%; + height: 50px; + border-radius: 5px; + border: 1px solid #000; + background: var(--yellow); +} +.btn-del { + background: #ffeaea; +} + +.btn-area, +.btn-area-full { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: nowrap; + gap: 1%; + width: 100%; +} +.btn-area .btn { + width: 50%; +} +.btn-area-full .btn { + display: block; + width: 100%; +} + +.btn-area .btn:nth-child(1) { + background: var(--yellow); + color: #000; +} +.btn-area .btn:nth-child(2) { + background: #ddd; + color: #000; +} + +.btn-s { + overflow: hidden; + position: relative; + display: inline-block; + background: #78a4b8; + color: #fff; + border-radius: 5px; + transition: all 0.5s ease; + border: 1px solid #78a4b8; + padding: 5px 10px; + transform: translateY(8px); +} + +.btn-s:hover { + background-color: #78a4b8; + color: #fff; + border-color: #78a4b8; + box-shadow: 0px 3px 3px #b5b8d1; +} + +/* main / detail */ + +.profile { + line-height: 1.6rem; + box-sizing: border-box; +} + +.profile .name { + font-weight: bold; + font-size: 1rem; + line-height: 3rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +#detail .profile .name{ + white-space: initial; +} + +main .profile .tel::before { + content: "📞"; + display: inline-block; + transform: rotate(10deg); +} + +main .profile { + border: 1px solid #000; + border-radius: 20px; + padding: 1rem; + width: 100%; + display: grid; + grid-template-columns: 100%; + margin: 1.4rem auto; + text-align: center; + align-items: center; + box-shadow: 5px 8px 1px var(--navy); + background: #fff; + opacity: 0; + transition: all 1s; + scale: 0.5; + transition-delay: 0.3s; + position: relative; +} +main .profile:first-child { + margin-top: 0; +} + +/* main .profile .ck{position: absolute;} */ +main .profile__dl { + width: 100%; + display: grid; + grid-template-columns: 50% 50%; + grid-template-rows: 20% 20% 20% 40%; + align-items: center; +} +main .ck { + margin-left: -10px; + display: none; +} + +main .profile.active { + opacity: 1; + scale: 1; + background: #fffcf3; +} + +main .profile:nth-child(odd) { + box-shadow: 5px 8px 1px #adbbd5; + background: #f9ffff; +} + +.thumbnail { + width: 100%; + margin: 0 auto; + border-radius: 10px; + background-size: cover; + background-position: center; +} + +main .ck, +main .thumbnail { + grid-row: span 4; +} +main .thumbnail { + max-width: 130px; + max-height: 130px; + height: 100%; + background-position: center; + border-radius: 100px; +} + + +/* upload, edit input */ + +.image-container { + width: 100%; + margin: 0 auto; + text-align: center; + background: #f2f5fb; + border-radius: 10px; + border: 1px solid #ccc; + padding: 10px; + box-sizing: border-box; +} +.image-container img { + width: 100%; +} + +#upload .image-container { + border-radius: 0; +} + +#detail .profile { + display: flex; + flex-direction: column; + line-height: 2rem; + box-sizing: border-box; +} +#detail .profile .col-1 { + border: 1px solid var(--navy); + border-radius: 10px; + margin-top: 1rem; +} +#detail .thumbnail { + width: 100%; + height: 28vh; +} + +.form-input, +.form-text { + width: 100%; + height: 35px; + border-radius: 5px; + box-sizing: border-box; + border: 1px solid #ccc; +} + +.upload-area .form-file { + display: none; +} +.upload-area .custom-upload { + background: var(--navy); + color: #fff; + padding: 1.5rem 0; + border-radius: 5px 0 0 5px; + cursor: pointer; + width: 30%; + position: relative; + box-sizing: border-box; + border-right: 1px solid #000; +} +.upload-area .custom-upload span { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: 100%; + text-align: center; + line-height: 1.2rem; + font-size: 0.8rem; +} + +#upload .file-data { + position: absolute; +} +#edit .filedata { + font-size: 0.5rem; + position: absolute; + right: 5%; + bottom: 5%; + transform: translateY(-50%); + color: #fff; +} + +.upload-area { + width: 100%; + margin: 0.5rem 0; + box-sizing: border-box; +} +.upload-area { + display: flex; + justify-content: flex-start; + background: #fff; + border-radius: 5px; + border: 1px solid #000; +} + +.edit__del { + position: relative; +} + +.edit__del input[type="text"] { + display: none; +} + +.tab-wrap { + max-width: 500px; +} + +.tab-wrap .btn { + border-color: var(--navy); + color: #333a49; +} +.tab-wrap .tab-btn-area { + display: flex; + position: relative; +} +.tab-wrap .tab-btn { + text-align: center; + background: transparent; +} +.tab-wrap .tab-btn { + border: none; + border-bottom: 2px solid var(--navy); + width: 100%; + height: 50px; +} + +.tab-wrap .tab-con { + display: none; + width: 100%; + margin: 0px auto; +} + +.tab-wrap .tab-btn.act { + background-color: var(--sky); + border: 1px solid var(--navy); + border-bottom: none; + border-radius: 10px 10px 0 0; + color: #fff; +} + +.tab-con.show { + display: flex; + flex-direction: column; +} +.tab-wrap .input-col { + margin: 40px auto 20px; +} +.tab-wrap .form-input { + height: 45px; +} + +/* 숨김 */ +#gnb__delete-btn, +.ck { + display: none; +} +.nav .gnb li:nth-child(1) .nav .gnb li:nth-child(2) { + width: 20%; +} +/* .nav .gnb li.welcome__login, .nav .gnb li.welcome__logout{width: 100%;} */ diff --git a/public/main.js b/public/main.js new file mode 100644 index 000000000..cfb227549 --- /dev/null +++ b/public/main.js @@ -0,0 +1,70 @@ +// import { firebaseConfig } from '/firebase.js'; +const firebaseConfig = { + apiKey: "AIzaSyBRDdAmcnRV3_Ui_Md_vhbyp9-9-eXqzbw", + authDomain: "javascript-picture.firebaseapp.com", + projectId: "javascript-picture", + storageBucket: "javascript-picture.appspot.com", + messagingSenderId: "487659103783", + appId: "1:487659103783:web:e98479de1137818dfa9002", + measurementId: "G-0RSTQFT7N8", +}; + +// Initialize Firebase +firebase.initializeApp(firebaseConfig); + +const db = firebase.firestore(); +const storage = firebase.storage(); + +//url query string에 있던 자료를 object로 변환 +const queryString = new URLSearchParams(window.location.search); +queryString.get("id"); + +db.collection("member") + .get() + .then((결과) => { + console.log(결과); + 결과.forEach((doc) => { + console.log(doc.data()); + + const template = ` + + `; + + const wrap = document.querySelector(".basic-container"); + wrap.insertAdjacentHTML("beforeend", template); + }); + }) + .then(() => { + // intersectionObserver + + const io = new IntersectionObserver((entries, observer) => { + entries.forEach((entry) => { + // 주시 대상이 뷰포트 안으로 들어오면 active 클래스 추가 + if (entry.intersectionRatio > 0) { + entry.target.classList.add("active"); + } + + }); + }); + + // 주시 대상 선언, 주시 시작 + const boxElList = document.querySelectorAll(".profile"); + boxElList.forEach((el) => { + io.observe(el); + }); + }); diff --git a/public/media.css b/public/media.css new file mode 100644 index 000000000..213b30c06 --- /dev/null +++ b/public/media.css @@ -0,0 +1,98 @@ +@charset "utf-8"; + +/* Mobile Device */ +@media (min-width: 576px) { + /* layout */ + header, + main, + section { + max-width: 80%; + margin: 0 auto; + } + .nav .gnb { + flex-wrap: nowrap; + justify-content: space-between; + } + .gnb__welcome { + display: flex; + justify-content: flex-end; + width: initial; + } + .tab-wrap { + margin-top: 3rem; + max-width: 500px; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + } + + h1 { + font-size: 2.8rem; + } + + /* :hover */ + main .profile_title { + display: block; + } + main .profile { + width: 100%; + margin: 3rem auto; + } + main .profile_title { + padding: 10px; + box-sizing: border-box; + margin: 3.4rem auto 1.4rem; + } + + /* 체크박스 생기면 주석 해제 */ + /* main .profile{ grid-template-columns: 10% 90%;} */ + main .profile_title, + main .profile__dl { + display: grid; + align-items: center; + text-align: center; + grid-template-columns: 20% 20% 20% 20% 20%; + grid-template-rows: 100%; + } + main .profile .thumbnail { + width: 100%; + height: 130px; + } + main .profile .btn-s { + padding: 10px; + } + main .profile .btn-s:hover { + transform: translateY(0px); + } + .profile .name { + font-size: 1.4rem; + } + + #detail .profile { + flex-direction: row; + flex-wrap: nowrap; + gap: 5%; + } + #detail .profile .col-1 { + width: 30%; + } + #detail .profile .col-2 { + width: 70%; + } + + .custom-upload:hover { + background: #ffc107; + color: #183153; + } +} + +/* Tablet & Desktop Device */ +@media all and (min-width: 768px) { + header, + section, + main { + max-width: 800px; + margin: 0 auto; + } +} diff --git a/public/preview.js b/public/preview.js new file mode 100644 index 000000000..803c29e0f --- /dev/null +++ b/public/preview.js @@ -0,0 +1,32 @@ + +function readImage(input) { + // 인풋 태그에 파일이 있는 경우 + if (input.files && input.files[0]) { + // FileReader 인스턴스 생성 + const reader = new FileReader(); + // 이미지가 로드가 된 경우 + reader.onload = (e) => { + const previewImage = document.getElementById("preview-image"); + previewImage.src = e.target.result; + }; + // reader가 이미지 읽도록 하기 + reader.readAsDataURL(input.files[0]); + } +} + +// input file change 이벤트 부여 +const inputImage = document.getElementById("image"); +inputImage.addEventListener("change", (e) => { + let file = e.target.files[0]; + let fileName = file.name; + const fileData = document.querySelector(".filedata"); + + if (fileName) { + fileData.style.display = "block"; + } + fileData.innerHTML = fileName; + console.log(file, fileName); + + readImage(e.target); + +}); \ No newline at end of file diff --git a/public/reset.css b/public/reset.css new file mode 100644 index 000000000..b0ef7a8ae --- /dev/null +++ b/public/reset.css @@ -0,0 +1,81 @@ +@charset "utf-8"; + + +html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary, time, mark, audio, video { + margin:0; + padding:0; + border:0; + outline:0; + font-size:100%; + vertical-align:baseline; + background:transparent; +} + +body { + line-height:1; +} + +h1{text-align: center + ;} +article,aside,details,figcaption,figure, footer,header,hgroup,menu,nav,section { + display:block; +} + +ul { + list-style:none; +} + +blockquote, q { + quotes:none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content:''; + content:none; +} +a { + margin:0; + padding:0; + font-size:100%; + vertical-align:baseline; + background:transparent; + color:#000; + text-decoration:none; +} +/* change colors to suit your needs */ +ins { + background-color:#ff9; + color:#000; + text-decoration:none; +} +/* change colors to suit your needs */ +mark { + background-color:#ff9; + color:#000; + font-style:italic; + font-weight:bold; +} +del { + text-decoration: line-through; +} +abbr[title], dfn[title] { + border-bottom:1px dotted; + cursor:help; +} +table { + border-collapse:collapse; + border-spacing:0; +} +/* change border color to suit your needs */ +hr { + display:block; + height:1px; + border:0; + border-top:1px solid #cccccc; + margin:1em 0; + padding:0; +} +input, select { + vertical-align:middle; +} + diff --git a/public/upload/upload.html b/public/upload/upload.html new file mode 100644 index 000000000..504a8cd86 --- /dev/null +++ b/public/upload/upload.html @@ -0,0 +1,122 @@ + + + + + + + 🙋‍♀️employee picture + + + + + + +
+

🙋‍♂️---logo--- 🙋‍♀️

+ +
+
+

직원 정보 업로드

+
+ + + + + + + + +
+ + +
+ + +
+
+ +
+ +
+
+
+ + + + + + + + + + + + + diff --git a/public/upload/upload.js b/public/upload/upload.js new file mode 100644 index 000000000..c1b78f024 --- /dev/null +++ b/public/upload/upload.js @@ -0,0 +1,74 @@ +//비로그인시 업로드 불가 +if (localStorage.getItem("user") == null) { + document.querySelectorAll("input").forEach((el) => { + el.addEventListener("focus", function () { + alert("로그인 후 업로드 가능합니다!"); + window.location.href='/login/login.html' + this.blur(); + }); + }); +} + +// 올리기 버튼을 누르면 입력한 정보 db저장 +document.getElementById("send").addEventListener("click", function () { + if (localStorage.getItem("user") != null) { + //스토리지에 이미지 저장 + + const file = document.querySelector("#image").files[0]; + const storageRef = storage.ref(); + const imgPath = storageRef.child( + "image/" + Math.round(Math.random() * 9999) + file + ); + + const upload = imgPath.put(file); + + //이미지 업로드 성공,실패 확인 + upload.on( + "state_changed", + // 변화시 동작하는 함수 + null, + //에러시 동작하는 함수 + (error) => { + console.error("실패사유는", error); + }, + // 성공시 동작하는 함수 + () => { + upload.snapshot.ref.getDownloadURL().then((url) => { + //이미지 url 가져오기 + console.log("업로드된 경로는", url); + + const saveData = { + name: document.getElementById("name").value, + ext: document.getElementById("tel").value, + team: document.getElementById("team").value, + rank: document.getElementById("rank").value, + phone: document.getElementById("phone").value, + email: document.getElementById("email").value, + memo: document.getElementById("memo").value, + img: url, + uid: JSON.parse(localStorage.getItem("user")).uid, + username: JSON.parse(localStorage.getItem("user")).displayName, + }; + + console.log(name, phone, email, rank, memo); + + db.collection("member") + .add(saveData) + .then((result) => { + console.log(result) + // window.location.href = + // "/detail/detail.html?id=" + saveData.uid; + window.location.href = "/index.html"; + console.log(result); + }) + .catch((err) => { + console.log(err); + }); + }); + } + ); + } else { + alert("로그인 후 업로드 가능합니다!"); + window.location.href='/login/login.html' + } +}); diff --git a/storage.rules b/storage.rules new file mode 100644 index 000000000..f08744f03 --- /dev/null +++ b/storage.rules @@ -0,0 +1,12 @@ +rules_version = '2'; + +// Craft rules based on data in your Firestore database +// allow write: if firestore.get( +// /databases/(default)/documents/users/$(request.auth.uid)).data.isAdmin; +service firebase.storage { + match /b/{bucket}/o { + match /{allPaths=**} { + allow read, write: if false; + } + } +} diff --git a/userflow.JPG b/userflow.JPG new file mode 100644 index 000000000..49f7d3583 Binary files /dev/null and b/userflow.JPG differ