안녕하세요.
아이돌 멤버 관리 서비스 웹 페이지 입니다.
https://fastcampusxyanolja-assginment.web.app/
id : user
password : 1234
2023.08.08 ~ 2023.08.16
선택 요구사항
| 로그인 | 게스트 / 매니저 권한 분리 |
|---|---|
| 기본 타이틀 조작 방지 | 정보 등록 |
| 정보 변경 | 정보 삭제 |
| 모바일 반응형 | |
const loginform = document.getElementById('login-form');
const loginbutton = document.getElementById('idbutton');
const loading = document.querySelector('.spin-container');
var nonvisible = getComputedStyle(loading).display;
var visible = "flex";
loading.style.display=nonvisible;
var down = getComputedStyle(loading).zIndex;
var up = 2;
loading.style.zIndex=down;
loginbutton.addEventListener('click', function (e) {
e.preventDefault();
const id = loginform.id.value;
const password = loginform.password.value;
if (id === "user" && password === "1234") {
sessionStorage.setItem("nums",1);
loading.style.display = visible;
loading.style.zIndex = up;
setTimeout(() => {
loading.style.display = "none";
loading.style.zIndex = 0;
window.location.href = "index.html";
}, 1000);
} else {
e.preventDefault();
login();
}
});
function login(){
Swal.fire({
title: '로그인 오류',
text: "아이디 혹은 비밀번호를 다시 입력하세요.",
icon: 'warning',
confirmButtonColor: '#3085d6',
confirmButtonText: '확인',
})
}
getComputedStyle()를 활용하여 스타일을 가져옴
loginbutton.addEventListener 에서 로그인 버튼 클릭 시, e.preventDefault(); 고유 동작을 중단하고, 로그인 시 사용한 id와 password를 id와 password 변수로 받음
id값이 user, password 값이 각각 user, 1234인 경우 loading.style.display = visible;, loading.style.zIndex = up;을 선언하여 만들어둔 로딩 애니메이션이 보이도록 설정
sessionStorage.setItem("nums",1); 을 통해 SessionStorage에 키 값은 nums이고 해당하는 value값을 1로 설정
setTimeout(() => {
loading.style.display = "none";
loading.style.zIndex = 0;
window.location.href = "index.html";
}, 1000);
1초 후, 로딩 애니메이션의 display를 none으로 바꿔주고 index.html로 이동하도록 설정
id 혹은 password가 다른 경우 login(); 실행
login()은 외부 라이브러리인 SweetAlert 사용
let nums=sessionStorage.getItem("nums");
if(nums!==null) {
checkuser.innerHTML="매니저";
checklogin.innerHTML="로그아웃";
}
else{
checkuser.innerHTML="게스트";
checklogin.innerHTML="로그인";
}
export const innerHTML = checklogin.innerHTML;
SessionStorage 내부의 nums키의 value값을 nums로 받아옴
nums가 null값이면 게스트, null값이 아니면 매니저로 설정
checklogin.addEventListener('click',()=>{
if(checklogin.innerHTML==="로그인"){
loading.style.display = visible;
loading.style.zIndex = up;
setTimeout(() => {
loading.style.display = "none";
loading.style.zIndex = 0;
}, 1000);
setTimeout(() => {
hreflink();
}, 2000);
}
else{
loading.style.display = visible;
loading.style.zIndex = up;
setTimeout(() => {
loading.style.display = "none";
loading.style.zIndex = 0;
checkuser.innerHTML="게스트";
checklogin.innerHTML="로그인";
}, 1000);
setTimeout(() => {
loading.style.display = visible;
loading.style.zIndex = up;
}, 2000);
setTimeout(() => {
hreflink();
}, 3000);
sessionStorage.clear();
}
})
export const innerHTML = checklogin.innerHTML;
버튼에 해당하는 innerHTML값이 로그인일 때 클릭 시, 로딩 애니메이션을 띄우고 2초 뒤, login.html로 이동
버튼에 해당하는 innerHTML값이 로그아웃일 때 클릭 시, 로딩 애니메이션을 띄우면서 게스트, 로그인으로 바꿈
보안을 위해 자동으로 로그인 화면으로 이동
게스트이므로 SessionStorage를 비워줌
import { innerHTML } from "./header.js";
if(innerHTML==="로그아웃"){
insertbutton.addEventListener('click',()=>{
// 모달창 띄우기
modalOn();
})
deletebutton.addEventListener('click',()=>{
deleteBoard();
})
}
else{
insertbutton.addEventListener('click',()=>{
login();
})
deletebutton.addEventListener('click',()=>{
login();
})
}
export function login(){
Swal.fire({
title: '로그인 오류',
text: "로그인 후 사용 가능합니다.",
icon: 'warning',
confirmButtonColor: '#3085d6',
confirmButtonText: '확인',
})
}
header.js의 checklogin.innerHTML를 불러옴
innerHTML값이 로그아웃 즉, 매니저인 경우에만 정보 등록, 정보 삭제 가능
반대인 경우, swal의 login() 실행
const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
if (image && name && group) {
const storage = getStorage();
const storageRef = ref(storage, 'images');
const listResult = await listAll(storageRef);
for (const item of listResult.items) {
const fileName = item.name;
if (fileName === image.name) {
cantupload();
clearInputValues();
setTimeout(() => {
location.reload();
}, 1000);
return;
}
}
이미지 선택, 이름 입력, 그룹 입력 시 listAll()를 사용해 FireStorage image의 모든 파일과 하위 파일을 검색
Firebase 내 동일한 이미지를 등록하려고 하면 2개의 swal을 띄우고 return
const storage = getStorage();
const storageRef = ref(storage, 'images/' + image.name);
const uploadTask = uploadBytes(storageRef, image);
await uploadTask;
const downloadURL = await getDownloadURL(storageRef);
const imagesCollection = collection(db, 'images');
await addDoc(imagesCollection, {
id: id,
name: name,
group: group,
imageUrl: downloadURL
});
modalOff();
images 폴더에는 사진을 업로드
db에는 id와 이름, 그룹, imageurl을 업로드
전부 다 등록 시 모달창 종료
const imageUrl = completeditems.querySelector('.image img').src;
const imagesCollection = collection(db, 'images');
const querySnapshot = await getDocs(imagesCollection);
querySnapshot.forEach(async (doc) => {
const docData = doc.data();
if (docData.imageUrl === imageUrl) {
await deleteDoc(doc.ref);
}
});
// 정보를 포함한 쿼리 문자열 생성
const queryParams = new URLSearchParams({
id: id,
image: image,
name: name,
group: group
});
// 쿼리 매개변수를 다음 페이지 URL에 추가
const nextPageUrl = `profile.html?${queryParams}`;
window.location.href = nextPageUrl;
}
else {
const queryParams = new URLSearchParams({
id: 'example',
image: clickedImage.src,
name: clickedName.textContent,
group: clickedGroup.textContent,
});
const nextPageUrl = `profile.html?${queryParams}`;
window.location.href = nextPageUrl;
}
}
쿼리 문자열을 통해 프로필 페이지로 이동
example의 경우 미리 설정한 default 프로필들에만 해당
const queryid = getQueryParam('id');
const queryimage = getQueryParam('image');
const queryname = getQueryParam('name');
const querygroup = getQueryParam('group');
const listitems = await listAll(storageRef);
const numericId = parseInt(queryid, 10);
const idValues = profiles.map(item => item.id);
// 리스트 내 중복 사진은 수정 불가
for(const item of listitems.items){
const fileName = item.name;
if(fileName == image.name){
cantupload();
clearInputValues();
setTimeout(() => {
location.reload();
}, 1000);
return;
}
}
// Firestorage
if (idValues.includes(numericId)) {
const matchingProfile = profiles.find(profile => profile.id === numericId);
if (matchingProfile) {
const storage = getStorage();
// Firestorage 내의 기존 이미지 삭제
const matchingImage = matchingProfile.image;
const matchingId = matchingProfile.id;
await deleteMatchingImageFromFirestorage(matchingImage);
// 새로운 이미지 Firestorage에 업로드
const newStorageRef = ref(storage, 'images/' + image.name);
const uploadTask = uploadBytes(newStorageRef, image);
await uploadTask;
const newDownloadURL = await getDownloadURL(newStorageRef);
// Firestore 데이터 업데이트
await updateItemFields(matchingId, groupInput.value, nameInput.value, newDownloadURL);
// URL 파라미터 업데이트
const queryParams = new URLSearchParams({
id: queryid,
image: newDownloadURL,
name: nameInput.value,
group: groupInput.value
});
setTimeout(() => {
const newUrl = `${window.location.pathname}?${queryParams.toString()}`;
window.history.pushState(null, '', newUrl);
location.reload();
}, 1500);
}
}
}
else{
uploadError();
modalOff();
}
}
imagecontainer.src = queryimage;
namecontainer.innerHTML = queryname;
groupcontainer.innerHTML = querygroup;
리스트 내 중복 사진은 수정 불가
수정을 위해 삭제하고 등록하는 형식 사용
수정 시 url 파라미터를 업로드 하므로 수정 후 , window.history.pushState와 location.reload() 사용
const checklogin=document.querySelector('.checklogin');
const checkuser=document.querySelector('.checkuser');
let nums=sessionStorage.getItem("nums");
const link='login.html';
const loading = document.querySelector('.spin-container');
const nonvisible = getComputedStyle(document.querySelector('.spin-container')).display;
const down = getComputedStyle(document.querySelector('.spin-container')).zIndex;
const visible = "flex";
loading.style.display=nonvisible;
const up = 2;
loading.style.zIndex=down;
class changeLoading{
constructor(loadingEl) {
this.loadingEl = loadingEl
}
changeDisplay(display, zIndex) {
this.loadingEl.style.display = display;
this.loadingEl.style.zIndex = zIndex;
}
}
checklogin.addEventListener('click',()=>{
let change = new changeLoading(loading)
if(checklogin.innerHTML==="로그인"){
change.changeDisplay(visible, up)
setTimeout(() => {
change.changeDisplay("none",0)
}, 1000);
setTimeout(() => {
hreflink();
}, 2000);
}
else{
change.changeDisplay(visible, up)
setTimeout(() => {
change.changeDisplay("none",0)
checkuser.innerHTML="게스트";
checklogin.innerHTML="로그인";
}, 1000);
setTimeout(() => {
change.changeDisplay(visible, up)
}, 2000);
setTimeout(() => {
hreflink();
}, 3000);
sessionStorage.clear();
}
})
export const innerHTML = checklogin.innerHTML;
const search = document.querySelector('.material-symbols-outlined');
const searchbox = document.querySelector('.searchbox')
var check = false;
search.addEventListener('click', ()=>{
const profileList = document.querySelector('.profile-list')
const names = profileList.querySelectorAll('.item .name')
const allItems = profileList.querySelectorAll('.item');
allItems.forEach(item => {
item.style.display = 'flex';
});
names.forEach(name => {
const item = name.closest('.item');
if(name.innerHTML === searchbox.value){
check=true
}
else{
item.style.display='none';
}
})
if (check === false) {
const allItems = profileList.querySelectorAll('.item');
allItems.forEach(item => {
item.style.display = 'flex';
});
cantsearch();
}
check=false
})
function cantsearch(){
Swal.fire({
title: '결과 없음',
text: "검색된 결과가 없습니다.",
icon: 'warning',
confirmButtonColor: '#3085d6',
confirmButtonText: '확인',
})
}
check의 값은 T/F 방식으로 사용
검색한 값과 일치하면 해당하는 값을 가진 item만 보이고 나머지는 display: 'none'처리
검색한 값이 없다면 alert 띄우고 모든 item들이 보이도록 설정
firebase를 처음으로 사용해 보았는 데 난이도가 꽤 있었고, 휴가로 인해 하루 빨리 제출해야했기에 다른 공부를 못하고 이것만 몰두해 진행했습니다. 결과적으로 데이터 등록, 삭제, 수정 부분이 완벽하게 구현되어 있어 뿌듯했지만, 디자인 적 측면과 검색 기능 구현이 없어 아쉽습니다. 이번 프로젝트를 통해 firebase를 다룰 수 있게 된 점과 js스킬이 향상된 것 같아 의미있었습니다 !