diff --git a/cypress/e2e/Integration/ArtworkPage.cy.ts b/cypress/e2e/Integration/ArtworkPage.cy.ts index 048e59a1..014a2260 100644 --- a/cypress/e2e/Integration/ArtworkPage.cy.ts +++ b/cypress/e2e/Integration/ArtworkPage.cy.ts @@ -1,6 +1,6 @@ import { login } from "cypress/support/hooks"; -describe('Artwork-아트워크를 확인한다.',()=>{ +describe('Artwork-PA 아트워크를 확인한다.',()=>{ beforeEach(()=>{ login() }); @@ -70,9 +70,168 @@ describe('Artwork-아트워크를 확인한다.',()=>{ }); it('필수 예외) 관리 페이지에서 api 요청 결과가 500인 경우.',()=>{ + cy.intercept('GET','**/api/projects',{ + statusCode:500, + body: { message: 'Internal Server Error' } + }) + cy.visit('/promotion-admin/artwork') + cy.wait(10000) + cy.contains('500').should('be.visible') + }); + + it('권장 예외) 네트워크 연결이 안 되는 경우.',()=>{ + cy.intercept('GET','**/api/projects',{ forceNetworkError: true }) + cy.visit('/promotion-admin/artwork') + cy.wait(10000) + cy.contains('Network Error').should('be.visible') + }) +}); + +describe('Artwork-PA 아트워크 순서 변경을 확인한다.',()=>{ + beforeEach(()=>{ + login() + }); + + it('관리 페이지에서 메인 순서 관리를 누르고 아트워크가 있는 것을 확인한다.',()=>{ + cy.intercept('GET', '**/api/projects', { + statusCode: 200, + body: { + code: 200, + status: "OK", + message: "프로젝트 목록을 성공적으로 조회했습니다.", + data: [{ + "id": 1, + "department": "", + "category": "Entertainment", + "name": "이준호와 임윤아의 킹더랜드 인터뷰", + "client": "NETFLIX Korea Youtube", + "date": "2021-11-30T15:00:00.000Z", + "link": "https://www.youtube.com/watch?v=fJZV0jzTD3M", + "overView": "쉿!!\uD83E\uDD2B 소음을 내면 호텔의 별점이 내려가요! 배우 이준호와 임윤아의 조용조용 ASMR 인터뷰가 시작됩니다.", + "projectType": "main", + "isPosted": true, + "mainImg": "cypress/fixtures/Artwork/킹더랜드-main.png", + "mainImgFileName": "킹더랜드-main.png", + "responsiveMainImg": null, + "responsiveMainImgFileName": null, + "sequence": 1, + "mainSequence": 999, + "projectImages": [ + { + "id": 2, + "imageUrlList": "cypress/fixtures/Artwork/킹더랜드-detail1.png", + "fileName": "킹더랜드-detail1.png" + }, + { + "id": 3, + "imageUrlList": "cypress/fixtures/Artwork/킹더랜드-detail2.png", + "fileName": "킹더랜드-detail2.png" + } + ] + }] + } + }) + cy.visit('/promotion-admin/artwork') + cy.wait(2000) + cy.contains('메인 순서 관리').click() + const child=['title','client','category','type'] + child.forEach((child)=>{ + cy.get(`[data-cy="PA_artworksequence_${child}"]`).should('exist') + }) + }); + + it('필수 예외) 메인 순서 관리에서 api 요청 결과가 200인데 데이터가 null인 경우',()=>{ + cy.intercept('GET', '**/api/projects', { + statusCode: 200, + body: [] + }) + cy.visit('/promotion-admin/artwork') + cy.contains('메인 순서 관리').click() + cy.contains('😊 아트워크 데이터가 존재하지 않습니다.'); + }); + it('필수 예외) 메인 순서 관리에서 api 요청 결과가 500인 경우.',()=>{ + cy.intercept('GET','**/api/projects',{ + statusCode:500, + body: {} + }) + cy.visit('/promotion-admin/artwork') + cy.contains('메인 순서 관리').click() + cy.wait(10000) + cy.contains('500').should('be.visible') }); + it('관리 페이지에서 전체 순서 관리를 누르고 아트워크가 있는 것을 확인한다.',()=>{ + cy.intercept('GET', '**/api/projects', { + statusCode: 200, + body: { + code: 200, + status: "OK", + message: "프로젝트 목록을 성공적으로 조회했습니다.", + data: [{ + "id": 1, + "department": "", + "category": "Entertainment", + "name": "이준호와 임윤아의 킹더랜드 인터뷰", + "client": "NETFLIX Korea Youtube", + "date": "2021-11-30T15:00:00.000Z", + "link": "https://www.youtube.com/watch?v=fJZV0jzTD3M", + "overView": "쉿!!\uD83E\uDD2B 소음을 내면 호텔의 별점이 내려가요! 배우 이준호와 임윤아의 조용조용 ASMR 인터뷰가 시작됩니다.", + "projectType": "main", + "isPosted": true, + "mainImg": "cypress/fixtures/Artwork/킹더랜드-main.png", + "mainImgFileName": "킹더랜드-main.png", + "responsiveMainImg": null, + "responsiveMainImgFileName": null, + "sequence": 1, + "mainSequence": 1, + "projectImages": [ + { + "id": 2, + "imageUrlList": "cypress/fixtures/Artwork/킹더랜드-detail1.png", + "fileName": "킹더랜드-detail1.png" + }, + { + "id": 3, + "imageUrlList": "cypress/fixtures/Artwork/킹더랜드-detail2.png", + "fileName": "킹더랜드-detail2.png" + } + ] + }] + } + }) + cy.visit('/promotion-admin/artwork') + cy.wait(2000) + cy.contains('전체 순서 관리').click() + const child=['title','client','category','type'] + child.forEach((child)=>{ + cy.get(`[data-cy="PA_artworksequence_${child}"]`).should('exist') + }) + }); + + it('필수 예외) 전체 순서 관리에서 api 요청 결과가 200인데 데이터가 null인 경우',()=>{ + cy.intercept('GET', '**/api/projects', { + statusCode: 200, + body: [] + }) + cy.visit('/promotion-admin/artwork') + cy.contains('전체 순서 관리').click() + cy.contains('😊 아트워크 데이터가 존재하지 않습니다.'); + }); + + it('필수 예외) 전체 순서 관리에서 api 요청 결과가 500인 경우',()=>{ + cy.intercept('GET','**/api/projects',{ + statusCode:500, + body: {} + }) + cy.visit('/promotion-admin/artwork') + cy.contains('전체 순서 관리').click() + cy.wait(10000) + cy.contains('500').should('be.visible') + }); +}); + +describe('Artwork-PP 아트워크를 확인한다',()=>{ it('프로모션 페이지에서 아트워크가 있는 것을 확인하고 클릭을 한다.',()=>{ cy.intercept('GET', '**/api/projects', { // '**/api/projects/**'은 안 됨 statusCode: 200, @@ -149,13 +308,28 @@ describe('Artwork-아트워크를 확인한다.',()=>{ it('필수 예외) 프로모션 페이지에서 api 요청 결과가 500인 경우.',()=>{ cy.intercept('GET','**/api/projects',{ - statusCode:500, - body: {} + statusCode: 500, + message: "Internal Server Error", + response: { + data: { + statusText: "Internal Server Error", + message: "Test error from Cypress", + } + } }) cy.visit('/artwork') cy.wait(10000) - cy.contains('Network Error').should('be.visible') + cy.contains('서버에서 문제가 발생했습니다! 잠시 후 다시 시도해주세요.').should('be.visible') + cy.get('[data-cy="PP_artwork_nullComment"]').should('be.visible') + cy.get('[data-cy="PP_artwork_nullContact"]').should('be.visible') + }); + + it('권장 예외) 네트워크 연결이 안 되는 경우.',()=>{ + cy.intercept('GET','**/api/projects',{ forceNetworkError: true }) + cy.visit('/artwork') + cy.wait(10000) + cy.contains('네트워크 에러가 발생했어요! 네트워크 환경을 확인해주세요.').should('be.visible') cy.get('[data-cy="PP_artwork_nullComment"]').should('be.visible') cy.get('[data-cy="PP_artwork_nullContact"]').should('be.visible') }); - }); \ No newline at end of file +}); \ No newline at end of file diff --git a/cypress/e2e/Integration/LoginPage.cy.ts b/cypress/e2e/Integration/LoginPage.cy.ts index 0bd616c9..62311474 100644 --- a/cypress/e2e/Integration/LoginPage.cy.ts +++ b/cypress/e2e/Integration/LoginPage.cy.ts @@ -1,6 +1,4 @@ -import { login } from '@/apis/PromotionAdmin/login'; import { MSG } from '@/constants/messages'; -import { PA_ROUTES } from '@/constants/routerConstants'; describe('로그인 통합 테스트', () => { beforeEach(() => { diff --git a/cypress/e2e/Integration/MainPage.cy.ts b/cypress/e2e/Integration/MainPage.cy.ts index 16569814..c1651c0b 100644 --- a/cypress/e2e/Integration/MainPage.cy.ts +++ b/cypress/e2e/Integration/MainPage.cy.ts @@ -144,10 +144,9 @@ describe('MainPage - Intro 섹션을 확인한다.', () => { login(); }); - it('관리 페이지에서 로딩 확인 후 mainOverview, commitment가 있는 것을 확인한다.', () => { + it('관리 페이지에서 mainOverview, commitment가 있는 것을 확인한다.', () => { cy.visit('/promotion-admin/dataEdit'); cy.get('[data-cy="nav-btn-company"]').click(); - cy.contains('Loading...').should('be.visible'); cy.wait(3000); cy.intercept('GET', '**/api/company/information', { statusCode: 200, diff --git a/cypress/e2e/System/AboutPage/Introduction.cy.ts b/cypress/e2e/System/AboutPage/Introduction.cy.ts index 7d5a932e..bebbf83a 100644 --- a/cypress/e2e/System/AboutPage/Introduction.cy.ts +++ b/cypress/e2e/System/AboutPage/Introduction.cy.ts @@ -1,40 +1,13 @@ -import { aboutPageAttributes, dataEditCompanyPageAttributes } from '@/constants/dataCyAttributes'; -import { MSG } from '@/constants/messages'; -import { confirmAndCheckCompletion, login } from 'cypress/support/hooks'; +import { login, normalizeHtml } from 'cypress/support/hooks'; describe('1. 회사 소개를 관리한다', () => { beforeEach(() => { login(); - cy.intercept('GET', '/api/company/information', { - statusCode: 200, - body: { - code: 200, - status: 'OK', - message: '전체 회사 정보를 성공적으로 조회하였습니다.', - data: { - id: 1, - mainOverview: '

STUDIO EYE IS THE BEST NEW MEDIA

PRODUCTION BASED ON OTT & YOUTUBE

', - commitment: '

우리는 급변하는 뉴 미디어 시대를 반영한 콘텐츠 제작을 위해 끊임없이 고민하고 변화합니다.

', - address: '서울시 성동구 광나루로 162 BS성수타워 5층', - addressEnglish: '5F 162, Gwangnaru-ro, Seongdong-gu, Seoul, Republic of Korea', - phone: '02-2038-2663', - fax: '02-2038-2663', - introduction: '초기 테스트 입력', - sloganImageFileName: null, - sloganImageUrl: null, - detailInformation: [ - { - id: 1, - key: '문의하기', - value: '사이트를 통해 간편하게 문의하세요.', - }, - ], - }, - }, - }).as('getCompanyInfo'); + cy.intercept('GET', '/api/company/information').as('getCompanyInfo'); + cy.visit('/promotion-admin/dataEdit/company'); + cy.wait('@getCompanyInfo', { timeout: 10000 }); cy.visit('/promotion-admin/dataEdit/company'); // 회사 정보 편집 페이지로 이동 - cy.wait('@getCompanyInfo', { timeout: 10000 }); }); it('PP에서 로그인 후 PA의 회사 정보 편집 페이지로 이동한다', () => { @@ -42,225 +15,99 @@ describe('1. 회사 소개를 관리한다', () => { cy.url().should('include', '/promotion-admin/dataEdit/company'); }); - // it('초기 회사 소개 텍스트를 생성하고 확인한다', () => { - // cy.fixture('AboutPage/introductionData.json').then((data) => { - // // 기본 데이터 입력 - // cy.get(`[data-cy="${aboutPageAttributes.CREATE_INTRO}"]`).type(data.initialIntroduction); - // cy.get(`[data-cy="${dataEditCompanyPageAttributes.SUBMIT_BUTTON}"]`).click(); - - // // 등록 확인 프롬프트 및 완료 메시지 확인 - // confirmAndCheckCompletion(MSG.CONFIRM_MSG.POST, MSG.ALERT_MSG.POST); // 메시지 확인 - - // cy.visit('/about'); - // cy.contains(data.initialIntroduction).should('exist'); - // }); - // }); - it('PA에서 회사 소개 텍스트를 수정하고 PP에서 변경된 내용을 확인한다', () => { - cy.fixture('AboutPage/introductionData.json').then((data) => { - // 수정 API 모킹 - cy.intercept('PUT', '/api/company/introduction', (req) => { - const expectedData = { - mainOverview: '

STUDIO EYE IS THE BEST NEW MEDIA

PRODUCTION BASED ON OTT & YOUTUBE

', - commitment: '

우리는 급변하는 뉴 미디어 시대를 반영한 콘텐츠 제작을 위해 끊임없이 고민하고 변화합니다.

', - introduction: data.updatedIntroduction, - }; - - // HTML 태그와 줄바꿈을 제거하고 비교 - const stripHtml = (str: string) => str.replace(/<[^>]*>/g, '').trim(); - const normalizeText = (str: string) => str.replace(/\n/g, '').trim(); - chai - .expect(normalizeText(stripHtml(req.body.introduction))) - .to.include(normalizeText(stripHtml(expectedData.introduction))); - - req.reply({ - statusCode: 200, - body: { - message: 'Company Introduction updated successfully', - }, - }); - }).as('updateIntroduction'); - - // GET 요청에 대한 모킹 추가: 페이지 이동 후 확인을 위해 - cy.intercept('GET', '/api/company/information', { - statusCode: 200, - body: { - code: 200, - status: 'OK', - data: { - id: 1, - mainOverview: '

STUDIO EYE IS THE BEST NEW MEDIA

PRODUCTION BASED ON OTT & YOUTUBE

', - commitment: - '

우리는 급변하는 뉴 미디어 시대를 반영한 콘텐츠 제작을 위해 끊임없이 고민하고 변화합니다.

', - address: '서울시 성동구 광나루로 162 BS성수타워 5층', - addressEnglish: '5F 162, Gwangnaru-ro, Seongdong-gu, Seoul, Republic of Korea', - phone: '02-2038-2663', - fax: '02-2038-2663', - introduction: data.updatedIntroduction, - sloganImageFileName: null, - sloganImageUrl: null, - detailInformation: [ - { - id: 1, - key: '문의하기', - value: '사이트를 통해 간편하게 문의하세요.', - }, - ], - }, - }, - }).as('getUpdatedCompanyInfo'); - - // MODIFY_INTRO_TITLE 요소가 존재하고 보이는지 확인 - cy.get('[data-cy="MODIFY_INTRO_TITLE"]', { timeout: 20000 }).should('exist').and('be.visible'); - - cy.log('MODIFY_INTRO_TITLE 요소 확인 완료'); - - // 수정하기 버튼 클릭 - cy.get(`[data-cy="dataEdit-Button"]`, { timeout: 20000 }) - .should('exist') - .and('be.visible') - .eq(2) // 세 번째 요소 선택 - .click({ force: true }); - - cy.log('수정하기 버튼 클릭 완료'); - - // 에디터가 존재하는지 확인 후 텍스트 입력 - cy.wait(1000); - cy.get('.ql-editor', { timeout: 20000 }) - .should('exist') - .and('be.visible') - .eq(2) // 세 번째 요소만 선택 - .invoke('html', ''); // 기존 내용 삭제 + // 기존 데이터 확인 + cy.wait('@getCompanyInfo').then((interception) => { + const response = interception.response?.body; + chai.expect(response).to.have.property('code', 200); + chai.expect(response.data).to.have.property('introduction'); + }); - cy.get('.ql-editor').eq(2).type(data.updatedIntroduction, { force: true }); + // 수정하기 버튼 클릭 + cy.get(`[data-cy="dataEdit-Button"]`).eq(2).click({ force: true }); - cy.log('텍스트 입력 완료'); + // 텍스트 입력 + cy.get('.ql-editor') + .eq(2) + .invoke('html', '') // 기존 내용 제거 + .type('

수정된 회사 소개 텍스트

', { parseSpecialCharSequences: false, force: true }); - // "저장하기"라는 텍스트를 포함한 버튼 클릭 - cy.contains('button', '저장하기', { timeout: 20000 }).should('exist').and('be.visible').click({ force: true }); + // 요청 가로채기 및 확인 + cy.intercept('PUT', '/api/company/introduction').as('updateIntroduction'); + cy.contains('button', '저장하기').click({ force: true }); - // 모킹된 API 요청 대기 - cy.wait('@updateIntroduction'); + cy.wait('@updateIntroduction', { timeout: 10000 }).then((interception) => { + const request = interception.request?.body; - cy.log('저장하기 버튼 클릭 완료 및 API 호출 완료'); + // HTML 태그 제거 후 순수 텍스트 비교 + chai.expect(normalizeHtml(request.introduction)).to.equal('수정된 회사 소개 텍스트'); - // 수정된 내용 확인을 위한 GET 요청 대기 - cy.wait('@getUpdatedCompanyInfo'); + const response = interception.response?.body; + chai.expect(response.message).to.equal('회사 소개 정보를 성공적으로 수정했습니다.'); + }); - // 수정된 내용 확인 - cy.visit('/about'); + // 수정된 데이터 확인 + cy.intercept('GET', '/api/company/information').as('getUpdatedCompanyInfo'); + cy.wait('@getUpdatedCompanyInfo').then((interception) => { + const response = interception.response?.body; - // 수정된 내용 확인을 위해 페이지를 천천히 스크롤하며 요소 렌더링 확인 - cy.visit('/about'); - for (let i = 0; i < 8; i++) { - cy.scrollTo(0, i * 300, { duration: 500 }); - cy.wait(500); // 스크롤 후 대기 시간 추가 - } - cy.get('[data-cy="about-content"]').should('contain', data.updatedIntroduction); + // HTML 태그 제거 후 순수 텍스트 비교 + chai.expect(normalizeHtml(response.data.introduction)).to.equal('수정된 회사 소개 텍스트'); }); + + cy.visit('/about'); + cy.get('[data-cy="about-content"]').should('contain', '수정된 회사 소개 텍스트'); }); it('PA에서 회사 소개 텍스트를 삭제하고 PP에서 기본 데이터를 확인한다', () => { - cy.fixture('AboutPage/introductionData.json').then((data) => { - // API 요청 모킹 설정 - cy.intercept('PUT', '/api/company/introduction', (req) => { - const expectedData = { - mainOverview: '

STUDIO EYE IS THE BEST NEW MEDIA

PRODUCTION BASED ON OTT & YOUTUBE

', - commitment: '

우리는 급변하는 뉴 미디어 시대를 반영한 콘텐츠 제작을 위해 끊임없이 고민하고 변화합니다.

', - introduction: '', // 삭제된 상태 - }; - - // HTML 태그와 줄바꿈을 제거하고 비교 - const stripHtml = (str: string) => str.replace(/<[^>]*>/g, '').trim(); - const normalizeText = (str: string) => str.replace(/\n/g, '').trim(); - chai - .expect(normalizeText(stripHtml(req.body.introduction))) - .to.include(normalizeText(stripHtml(expectedData.introduction))); + cy.fixture('AboutPage/introductionData.json').then(() => { + // 기존 데이터 가져오기 확인 + cy.wait('@getCompanyInfo').then((interception) => { + const response = interception.response?.body; + chai.expect(response).to.have.property('code', 200); + chai.expect(response.data).to.have.property('introduction'); + }); - req.reply({ - statusCode: 200, - body: {}, - }); - }).as('deleteIntroduction'); + // 수정하기 버튼 클릭 + cy.get(`[data-cy="dataEdit-Button"]`).eq(2).click({ force: true }); - // GET 요청에 대한 모킹 추가: 페이지 이동 후 확인을 위해 - cy.intercept('GET', '/api/company/information', { - statusCode: 200, - body: { - code: 200, - status: 'OK', - data: { - id: 1, - mainOverview: '

STUDIO EYE IS THE BEST NEW MEDIA

PRODUCTION BASED ON OTT & YOUTUBE

', - commitment: - '

우리는 급변하는 뉴 미디어 시대를 반영한 콘텐츠 제작을 위해 끊임없이 고민하고 변화합니다.

', - address: '서울시 성동구 광나루로 162 BS성수타워 5층', - addressEnglish: '5F 162, Gwangnaru-ro, Seongdong-gu, Seoul, Republic of Korea', - phone: '02-2038-2663', - fax: '02-2038-2663', - introduction: - '

2010년에 설립된 스튜디오 아이는 다양한 장르를 소화할 수 있는 PD들이 모여

클라이언트 맞춤형 콘텐츠 제작 운영 대책 서비스를 제공하고 있으며

드라마 애니메이션 등을 전문으로 하는 여러 계열사들과도 협력하고 있습니다.

', - sloganImageFileName: null, - sloganImageUrl: null, - detailInformation: [ - { - id: 1, - key: '문의하기', - value: '사이트를 통해 간편하게 문의하세요.', - }, - ], - }, - }, - }).as('getUpdatedCompanyInfo'); + // 에디터에 빈값 입력 + cy.get('.ql-editor') + .eq(2) + .invoke('html', '') // 기존 내용 삭제 + .type('{selectall}{backspace}', { force: true }); // 모든 내용 지우기 - // MODIFY_INTRO_TITLE 요소가 존재하고 보이는지 확인 - cy.get('[data-cy="MODIFY_INTRO_TITLE"]', { timeout: 20000 }).should('exist').and('be.visible'); + // 저장하기 버튼 클릭 + cy.intercept('PUT', '/api/company/introduction').as('updateIntroduction'); + cy.contains('button', '저장하기').click({ force: true }); - cy.log('MODIFY_INTRO_TITLE 요소 확인 완료'); // 디버깅용 로그 + // 삭제 요청 확인 + cy.wait('@updateIntroduction', { timeout: 10000 }).then((interception) => { + const request = interception.request?.body; - // 수정하기 버튼 클릭 - cy.get(`[data-cy="dataEdit-Button"]`, { timeout: 20000 }) - .should('exist') - .and('be.visible') - .eq(2) // 세 번째 요소 선택 - .click({ force: true }); + // "


"를 빈값으로 간주 + const normalizeIntroduction = (introduction: string) => (introduction === '


' ? '' : introduction); - cy.log('수정하기 버튼 클릭 완료'); // 디버깅용 로그 + chai.expect(normalizeIntroduction(request.introduction)).to.equal(''); // 빈값 확인 - // 에디터가 존재하는지 확인 후 텍스트 삭제 - cy.wait(1000); // 요소 로딩 대기 - cy.get('.ql-editor', { timeout: 20000 }) // 클래스명을 사용해 요소 찾기 - .should('exist') - .and('be.visible') - .eq(2) // 세 번째 요소만 선택 - .invoke('html', ''); // 내용 삭제 + const response = interception.response?.body; + chai.expect(response.message).to.equal('회사 소개 정보를 성공적으로 수정했습니다.'); + }); - // "저장하기"라는 텍스트를 포함한 버튼 클릭 - cy.contains('button', '저장하기', { timeout: 20000 }).should('exist').and('be.visible').click({ force: true }); + // 기본 데이터 확인 + cy.intercept('GET', '/api/company/information').as('getUpdatedCompanyInfo'); + cy.wait('@getUpdatedCompanyInfo').then((interception) => { + const response = interception.response?.body; - // 모킹된 API 요청 대기 - cy.wait('@deleteIntroduction'); + // "


"를 빈값으로 간주하여 확인 + const normalizeIntroduction = (introduction: string) => (introduction === '


' ? '' : introduction); - // 기본 정보 확인을 위해 GET 요청 대기 - cy.wait('@getUpdatedCompanyInfo'); + chai.expect(normalizeIntroduction(response.data.introduction)).to.equal(''); + }); - // 페이지를 천천히 스크롤하며 요소 렌더링 확인 + // /about 페이지 이동 후 기본 값 확인 cy.visit('/about'); - for (let i = 0; i < 8; i++) { - cy.scrollTo(0, i * 100, { duration: 500 }); // 100픽셀씩 천천히 스크롤 - cy.wait(500); // 스크롤 후 대기 시간 추가 - } - cy.get('[data-cy="about-content"]') - .invoke('text') - .then((text) => { - const expectedText = - '2010년에 설립된 스튜디오 아이는 다양한 장르를 소화할 수 있는 PD들이 모여클라이언트 맞춤형 콘텐츠 제작과 운영 대책 서비스를 제공하고 있으며드라마 애니메이션 등을 전문으로 하는 여러 계열사들과도 협력하고 있습니다.'; - - // 모든 공백을 단일 공백으로 정규화하고 문자열 앞뒤 공백 제거 - const normalizeText = (str: string) => str.replace(/\s+/g, ' ').trim(); - - chai.expect(normalizeText(text)).to.include(normalizeText(expectedText)); - }); + cy.get('[data-cy="about-content"]').should('not.contain', '수정된 회사 소개 텍스트'); // 수정된 텍스트가 없는지 확인 }); }); }); diff --git a/cypress/e2e/System/AboutPage/Slogan.cy.ts b/cypress/e2e/System/AboutPage/Slogan.cy.ts index ee6e60ad..2338c622 100644 --- a/cypress/e2e/System/AboutPage/Slogan.cy.ts +++ b/cypress/e2e/System/AboutPage/Slogan.cy.ts @@ -3,37 +3,15 @@ import { login } from 'cypress/support/hooks'; describe('2. 회사 슬로건을 관리한다.', () => { beforeEach(() => { login(); - // 회사 정보 모킹 - cy.intercept('GET', '/api/company/information', { - statusCode: 200, - body: { - code: 200, - status: 'OK', - message: '전체 회사 정보를 성공적으로 조회하였습니다.', - data: { - id: 1, - mainOverview: '

STUDIO EYE IS THE BEST NEW MEDIA

PRODUCTION BASED ON OTT & YOUTUBE

', - commitment: '

우리는 급변하는 뉴 미디어 시대를 반영한 콘텐츠 제작을 위해 끊임없이 고민하고 변화합니다.

', - address: '서울시 성동구 광나루로 162 BS성수타워 5층', - addressEnglish: '5F 162, Gwangnaru-ro, Seongdong-gu, Seoul, Republic of Korea', - phone: '02-2038-2663', - fax: '02-2038-2663', - introduction: '초기 테스트 입력', - sloganImageFileName: null, - sloganImageUrl: 'https://studio-eye-gold-bucket.s3.ap-northeast-2.amazonaws.com/Slogan.png', - detailInformation: [ - { - id: 1, - key: '문의하기', - value: '사이트를 통해 간편하게 문의하세요.', - }, - ], - }, - }, - }).as('getCompanyInfo'); + cy.intercept('GET', '/api/company/information').as('getCompanyInfo'); cy.visit('/promotion-admin/dataEdit/company'); // 회사 정보 편집 페이지로 이동 - cy.wait('@getCompanyInfo', { timeout: 10000 }); + cy.wait('@getCompanyInfo', { timeout: 10000 }).then((interception) => { + const response = interception.response?.body; + chai.expect(response).to.have.property('code', 200); + chai.expect(response.data).to.have.property('sloganImageUrl'); + }); + cy.intercept('PUT', '/api/company/slogan').as('updateSlogan'); }); it('PP에서 로그인 후 PA의 회사 정보 편집 페이지로 이동한다', () => { @@ -42,6 +20,9 @@ describe('2. 회사 슬로건을 관리한다.', () => { }); it('PA에서 회사 슬로건 이미지를 수정하고 PP에서 변경된 이미지를 확인한다', () => { + // PUT 요청 인터셉트 + cy.intercept('PUT', '/api/company/slogan').as('updateSlogan'); + // 두 번째 수정하기 버튼 클릭 cy.get(`[data-cy="dataEdit-Button"]`).eq(1).should('exist').and('be.visible').click({ force: true }); @@ -51,14 +32,13 @@ describe('2. 회사 슬로건을 관리한다.', () => { // 저장하기 버튼 클릭 cy.contains('button', '저장하기', { timeout: 20000 }).should('exist').and('be.visible').click({ force: true }); + // 저장 확인 알림 처리 + cy.on('window:confirm', (text) => { + return true; + }); + // /about 페이지 방문 후 변경된 슬로건 이미지 확인 cy.visit('/about'); - cy.wait(2000); // 이미지 반영을 기다리기 위한 추가 대기 시간 - - cy.get('img').then(($img) => { - const imgSrc = $img.attr('src'); - cy.log('Image src:', imgSrc); // 로그 출력 - chai.expect(imgSrc).to.contain('studioeyeyellow'); - }); + cy.get('[data-cy="mission-image"]').should('have.attr', 'src').and('contain', 'Slogan.png'); // 이미지 파일 이름에 "Slogan.png" 포함 확인 }); }); diff --git a/cypress/e2e/System/AboutPage/WhatWeDo.cy.ts b/cypress/e2e/System/AboutPage/WhatWeDo.cy.ts index 73619c34..3c905757 100644 --- a/cypress/e2e/System/AboutPage/WhatWeDo.cy.ts +++ b/cypress/e2e/System/AboutPage/WhatWeDo.cy.ts @@ -4,40 +4,8 @@ import { login } from 'cypress/support/hooks'; describe('3. WHAT WE DO를 관리한다', () => { beforeEach(() => { login(); - - cy.intercept('GET', `/api/company/information`, { - statusCode: 200, - body: { - code: 200, - status: 'OK', - message: '전체 회사 정보를 성공적으로 조회하였습니다.', - data: { - id: 9999, - mainOverview: '

스튜디오 아이와 함께 영상물 퀄리티 UP 

', - commitment: '

최고의 경험을 선사하는 스튜디오 아이의 작업과 함께하세요.

', - address: '서울시 성동구 광나루로 162 BS성수타워 5층', - phone: '02-2038-2663', - detailInformation: [ - { - id: 5555, - key: 'MCN 2.0', - value: '뉴미디어 콘텐츠에 체화된 플래디만의 독보적인 제작 역량을 바탕으로 크리에이터와 함께 성장합니다', - }, - { - id: 6666, - key: 'Digital Operator', - value: '다양한 고객의 Youtube, Instagram, TikTok 채널을 운영 대행합니다', - }, - { - id: 7777, - key: 'PD Group', - value: '예능, 드라마, 다큐멘터리, 애니메이션까지 전 장르의 디지털 콘텐츠를 기획, 제작합니다', - }, - ], - }, - }, - }).as('getCompanyInfo'); - + cy.intercept('GET', `/api/company/information`).as('getCompanyInfo'); + cy.intercept('GET', '/api/company/detail').as('getDetailForEdit'); cy.visit('/promotion-admin/dataEdit/company'); cy.wait('@getCompanyInfo'); // 초기 데이터가 로드될 때까지 대기 }); @@ -47,65 +15,19 @@ describe('3. WHAT WE DO를 관리한다', () => { }); it('PA에서 기존에 존재하는 WHAT WE DO를 수정하고 PP에서 변경된 내용을 확인한다', () => { - // 수정하기 버튼 클릭 cy.get(`[data-cy="dataEdit-Button"]`, { timeout: 20000 }) .should('exist') .and('be.visible') .eq(3) .click({ force: true }); - // 수정 페이지에서 GET 요청 모킹 - cy.intercept('GET', '/api/company/detail', { - statusCode: 200, - body: { - code: 200, - status: 'OK', - message: '회사 상세 정보를 성공적으로 조회하였습니다.', - data: [ - { - id: 5555, - key: 'MCN 2.0', - value: '뉴미디어 콘텐츠에 체화된 플래디만의 독보적인 제작 역량을 바탕으로 크리에이터와 함께 성장합니다', - }, - { - id: 6666, - key: 'Digital Operator', - value: '다양한 고객의 Youtube, Instagram, TikTok 채널을 운영 대행합니다', - }, - { - id: 7777, - key: 'PD Group', - value: '예능, 드라마, 다큐멘터리, 애니메이션까지 전 장르의 디지털 콘텐츠를 기획, 제작합니다', - }, - ], - }, - }).as('getDetailForEdit'); - - // PUT 요청 모킹 - 세부 정보를 업데이트하는 API - cy.intercept('PUT', '/api/company/detail', (req) => { - const { detailInformation } = req.body; - detailInformation.forEach((detail: { key: string; value: string }, index: number) => { - // 공백과 개행 문자를 제거한 후 비교 - chai.expect(detail.key.trim()).to.equal(['MCN 2.0', 'Digital Operator', 'PD Group'][index].trim()); - chai - .expect(detail.value.replace(/\s+/g, ' ').trim()) - .to.equal( - [ - '정말로 다양하고 멋진 작업물을 문의해보세요', - '정말로 다양하고 멋진 다양한 고객의 Youtube, Instagram, TikTok 채널을 운영 대행합니다', - '예능, 드라마, 다큐멘터리, 애니메이션까지 전 장르의 디지털 콘텐츠를 기획, 제작합니다', - ][index] - .replace(/\s+/g, ' ') - .trim(), - ); - }); - req.reply({ - statusCode: 200, - body: {}, - }); - }).as('updateCompanyInfo'); + // 수정하기 버튼 클릭 후 요청 대기 + cy.wait('@getDetailForEdit', { timeout: 10000 }).then((interception) => { + const response = interception.response?.body; + chai.expect(response).to.have.property('code', 200); + chai.expect(response.data).to.be.an('array'); + }); - // 기존 Detail 수정 const updatedDetails = [ { key: 'MCN 2.0', @@ -120,188 +42,92 @@ describe('3. WHAT WE DO를 관리한다', () => { updatedDetails.forEach((detail, index) => { cy.get(`[data-cy="${aboutPageAttributes.DETAIL_KEY_INPUT}"]`) .eq(index) - .should('exist') // 존재 여부 확인 - .scrollIntoView() // 요소를 보이도록 스크롤 - .should('be.visible') // 보이는지 확인 - .clear({ force: true }) // 강제로 클리어 - .type(detail.key) // 새로운 키 입력 - .should('have.value', detail.key); // 입력된 값 확인 - - cy.wait(500); + .clear({ force: true }) + .type(detail.key, { force: true }); cy.get(`[data-cy="${aboutPageAttributes.DETAIL_VALUE_INPUT}"]`) .eq(index) - .should('exist') // 존재 여부 확인 - .scrollIntoView() // 요소를 보이도록 스크롤 - .should('be.visible') // 보이는지 확인 - .clear({ force: true }) // 강제로 클리어 - .type(detail.value) // 새로운 값 입력 - .should('have.value', detail.value); // 입력된 값 확인 + .clear({ force: true }) + .type(detail.value, { force: true }); }); - // "저장하기" 버튼 클릭 - cy.contains('button', '저장하기', { timeout: 20000 }).should('exist').and('be.visible').click({ force: true }); - cy.wait('@updateCompanyInfo'); // 업데이트 요청 대기 + // 저장하기 버튼 클릭 후 PUT 요청 가로채기 + cy.intercept('PUT', '/api/company/detail').as('updateCompanyInfo'); + cy.contains('button', '저장하기', { timeout: 20000 }).should('exist').click({ force: true }); - cy.visit('/about'); + cy.wait('@updateCompanyInfo', { timeout: 10000 }).then((interception) => { + const request = interception.request?.body; - // /about 페이지에서 수정된 항목 확인 - cy.intercept('GET', `/api/company/detail`, { - statusCode: 200, - body: { - code: 200, - status: 'OK', - message: '회사 상세 정보를 성공적으로 조회하였습니다.', - data: [ - { - id: 5555, - key: 'MCN 2.0', - value: '정말로 다양하고 멋진 작업물을 문의해보세요', - }, - { - id: 6666, - key: 'Digital Operator', - value: '정말로 다양하고 멋진 다양한 고객의 Youtube, Instagram, TikTok 채널을 운영 대행합니다', - }, - { - id: 7777, - key: 'PD Group', - value: '예능, 드라마, 다큐멘터리, 애니메이션까지 전 장르의 디지털 콘텐츠를 기획, 제작합니다', - }, - ], - }, - }).as('getUpdatedCompanyInfo'); + // 요청 데이터 검증 + const requestIncludesDetail = (detail: { key: any; value: any }) => + request.detailInformation.some( + (info: { key: any; value: any }) => info.key === detail.key && info.value === detail.value, + ); + updatedDetails.forEach((detail) => chai.expect(requestIncludesDetail(detail)).to.be.true); - cy.wait('@getUpdatedCompanyInfo'); + const response = interception.response?.body; + chai.expect(response).to.have.property('status', 'OK'); + chai.expect(response.message).to.equal('회사 5가지 상세 정보를 성공적으로 수정했습니다.'); + }); - // 변경된 내용 확인 - updatedDetails.forEach((detail) => { - cy.contains(detail.value, { timeout: 10000 }).should('exist'); // 업데이트된 내용 확인 + // /about 페이지로 이동하여 응답 데이터 확인 + cy.visit('/about'); + cy.intercept('GET', `/api/company/detail`).as('getUpdatedCompanyDetail'); // 경로 수정 + cy.wait('@getUpdatedCompanyDetail', { timeout: 10000 }).then((interception) => { + const response = interception.response?.body; + + // 응답 데이터 검증 + const responseIncludesDetail = (detail: { key: any; value: any }) => + response.data.some((info: { key: any; value: any }) => info.key === detail.key && info.value === detail.value); + updatedDetails.forEach((detail) => chai.expect(responseIncludesDetail(detail)).to.be.true); }); }); it('PA에서 기존에 존재하는 WHAT WE DO 항목을 삭제하고 PP에서 변경된 내용을 확인한다', () => { - // 수정하기 버튼 클릭 cy.get(`[data-cy="dataEdit-Button"]`, { timeout: 20000 }) .should('exist') .and('be.visible') .eq(3) .click({ force: true }); - // PUT 요청 모킹 - 세부 정보를 업데이트하는 API - cy.intercept('PUT', '/api/company/detail', (req) => { - const { detailInformation } = req.body; - detailInformation.forEach((detail: { key: string; value: string }, index: number) => { - chai.expect(detail.key.trim()).to.equal(['Digital Operator', 'PD Group'][index].trim()); - chai - .expect(detail.value.replace(/\s+/g, ' ').trim()) - .to.equal( - [ - '다양한 고객의 Youtube, Instagram, TikTok 채널을 운영 대행합니다', - '예능, 드라마, 다큐멘터리, 애니메이션까지 전 장르의 디지털 콘텐츠를 기획, 제작합니다', - ][index] - .replace(/\s+/g, ' ') - .trim(), - ); - }); - req.reply({ - statusCode: 200, - body: { - code: 200, - status: 'OK', - message: '세부 정보가 성공적으로 업데이트되었습니다.', - data: [ - { - id: 6666, - key: 'Digital Operator', - value: '다양한 고객의 Youtube, Instagram, TikTok 채널을 운영 대행합니다', - }, - { - id: 7777, - key: 'PD Group', - value: '예능, 드라마, 다큐멘터리, 애니메이션까지 전 장르의 디지털 콘텐츠를 기획, 제작합니다', - }, - ], - }, - }); - }).as('deleteCompanyInfo'); - - // 첫 번째 항목 삭제 - cy.get(`[data-cy="${aboutPageAttributes.DELETE_DETAIL}"]`).eq(0).click({ force: true }); // 첫 번째 삭제 버튼 클릭 - - // 프롬프트 창 자동 확인 처리 + cy.get(`[data-cy="${aboutPageAttributes.DELETE_DETAIL}"]`).first().click({ force: true }); cy.on('window:confirm', () => true); - cy.contains('button', '저장하기', { timeout: 20000 }).should('exist').and('be.visible').click({ force: true }); - - // 업데이트 요청 대기 - cy.wait('@deleteCompanyInfo'); + cy.intercept('PUT', '/api/company/detail').as('deleteCompanyInfo'); - // 로그는 체이닝과 분리하여 별도로 실행 - cy.log('저장하기 버튼 클릭 완료'); + cy.contains('button', '저장하기', { timeout: 20000 }).should('exist').click({ force: true }); - // /about 페이지에서 수정된 항목 확인 - cy.intercept('GET', `/api/company/information`, { - statusCode: 200, - body: { - code: 200, - status: 'OK', - message: '전체 회사 정보를 성공적으로 조회하였습니다.', - data: { - id: 9999, - mainOverview: '

스튜디오 아이와 함께 영상물 퀄리티 UP 

', - commitment: '

최고의 경험을 선사하는 스튜디오 아이의 작업과 함께하세요.

', - address: '서울시 성동구 광나루로 162 BS성수타워 5층', - addressEnglish: '5F 162, Gwangnaru-ro, Seongdong-gu, Seoul, Republic of Korea', - phone: '02-2038-2663', - fax: '02-2038-2663', - introduction: '

스튜디오 아이는 편집 및 애니메이팅을 하고 있는 영상 매체 작업 전문 기업입니다.

', - sloganImageFileName: 'Slogan.png', - sloganImageUrl: 'https://studio-eye-gold-bucket.s3.ap-northeast-2.amazonaws.com/Slogan.png', - detailInformation: [ - { - id: 6666, - key: 'Digital Operator', - value: '다양한 고객의 Youtube, Instagram, TikTok 채널을 운영 대행합니다', - }, - { - id: 7777, - key: 'PD Group', - value: '예능, 드라마, 다큐멘터리, 애니메이션까지 전 장르의 디지털 콘텐츠를 기획, 제작합니다', - }, - ], - }, - }, - }).as('getDeletedCompanyInfo'); + cy.wait('@deleteCompanyInfo').then((interception) => { + const request = interception.request?.body; + chai.expect(request.detailInformation).to.have.length.above(0); // 최소 1개 남아있음 + }); cy.visit('/about'); - cy.wait('@getDeletedCompanyInfo'); + cy.intercept('GET', `/api/company/information`).as('getDeletedCompanyInfo'); + + cy.wait('@getDeletedCompanyInfo').then((interception) => { + const response = interception.response?.body; + chai.expect(response.data.detailInformation).to.not.deep.include({ + key: 'MCN 2.0', + }); + }); cy.contains('MCN 2.0').should('not.exist'); }); - it('PA에서 WHAT WE DO 항목을 삭제할 때 최소 하나 이상 남아있어야 한다는 알림을 확인한다.', () => { - // 수정하기 버튼 클릭 + it('필수 예외) PA에서 WHAT WE DO 항목을 삭제할 때 최소 하나 이상 남아있어야 한다는 알림을 확인한다.', () => { cy.get(`[data-cy="dataEdit-Button"]`, { timeout: 20000 }) .should('exist') .and('be.visible') .eq(3) .click({ force: true }); - cy.get(`[data-cy="${aboutPageAttributes.DELETE_DETAIL}"]`, { timeout: 10000 }).should('exist'); + cy.get(`[data-cy="${aboutPageAttributes.DELETE_DETAIL}"]`).then(($buttons) => { if ($buttons.length === 1) { - // 항목이 하나만 남아있을 때 삭제 버튼 클릭 cy.wrap($buttons.eq(0)).click({ force: true }); - - // 프롬프트 창 자동 확인 처리 cy.on('window:confirm', () => true); - // 최소 1개 이상 등록되어 있어야 한다는 알림이 표시되는지 확인 cy.contains('최소 1개 이상은 등록되어 있어야 합니다.').should('be.visible'); - } else { - // 여러 개가 남아있을 때는 일반 삭제 시나리오 실행 - cy.wrap($buttons.eq(0)).click({ force: true }); - cy.on('window:confirm', () => true); } }); }); diff --git a/cypress/e2e/System/ArtworkPage.cy.ts b/cypress/e2e/System/ArtworkPage.cy.ts index 4d2af56d..1e30a42e 100644 --- a/cypress/e2e/System/ArtworkPage.cy.ts +++ b/cypress/e2e/System/ArtworkPage.cy.ts @@ -12,14 +12,14 @@ describe('Artwork-아트워크를 만들고, 만든 아트워크를 확인한다 cy.fixture('Artwork/artwork_data.json').then((data) => { testData=data[0] requiredFields = [ - { name: '제목',selector: '[data-cy="create_artwork_title"]', value: data[0].title, type:'type' }, - { name: '설명',selector: '[data-cy="create_artwork_overview"]', value: data[0].overview, type:'type' }, - { name: '고객사',selector: '[data-cy="create_artwork_customer"]', value: data[0].customer, type:'type' }, + { name: '제목',selector: '[data-cy="create_artwork_title"]', value: data[0].title, type:'type', maxLength:30 }, + { name: '설명',selector: '[data-cy="create_artwork_overview"]', value: data[0].overview, type:'type', maxLength:120 }, + { name: '고객사',selector: '[data-cy="create_artwork_customer"]', value: data[0].customer, type:'type', maxLength:30 }, { name: '제작 일시',selector: '[data-cy="create_artwork_date"]', value: data[0].date, type:'type' }, { name: '카테고리',selector: '[data-cy="create_artwork_category"]', value: data[0].category, type: 'dropdown' }, - { name: '링크',selector: '[data-cy="create_artwork_link"]', value: data[0].link, type:'type' }, - { name: '타입',selector: '[data-cy="create_artwork_artworkType"]', value: data[0].artworkType, type: 'select' }, - { name: '공개 여부',selector: '[data-cy="create_artwork_isOpened"]', value: data[0].isOpened, type: 'select' }, + { name: '링크',selector: '[data-cy="create_artwork_link"]', value: data[0].link, type:'type', maxLength:300 }, + { name: '타입',selector: '[data-cy="create_artwork_artworkType"]', value: data[0].artworkType, type: 'select-type' }, + { name: '공개 여부',selector: '[data-cy="create_artwork_isOpened"]', value: data[0].isOpened, type: 'select-open' }, ]; }); }); @@ -33,7 +33,9 @@ describe('Artwork-아트워크를 만들고, 만든 아트워크를 확인한다 if(field.type==='dropdown'){ cy.get(field.selector).click({force:true}) cy.get('[data-cy="create_artwork_category_dropdown"]').contains(field.value).click({force:true}) - } else if (field.type === 'select') { + } else if (field.type==='select-type') { + cy.get(field.selector).contains(field.value==='Top'?'대표':field.value==='Main'?'메인':'기본').click({force:true}); + } else if (field.type==='select-open') { cy.get(field.selector).contains(field.value).click({force:true}); } else { cy.get(field.selector).type(field.value) @@ -61,9 +63,11 @@ describe('Artwork-아트워크를 만들고, 만든 아트워크를 확인한다 if (f.type === 'dropdown') { cy.get(f.selector).click({force:true}); cy.get('[data-cy="create_artwork_category_dropdown"]').contains(f.value).click({force:true}); - } else if (f.type === 'select') { + } else if (f.type==='select-type') { + cy.get(f.selector).contains(f.value==='Top'?'대표':f.value==='Main'?'메인':'기본').click({force:true}); + } else if (f.type==='select-open') { cy.get(f.selector).contains(f.value).click({force:true}); - }else{ + } else { if (f.selector !== field.selector) { cy.get(f.selector).type(f.value); } @@ -93,9 +97,27 @@ describe('Artwork-아트워크를 만들고, 만든 아트워크를 확인한다 cy.contains('외부 연결 링크는 http 혹은 https로 시작해야합니다.').should('be.exist') //fixed position이라 PA_artwork_createBox내에선 확인 못할것으로 보임 }); + it('권장 예외) 관리 페이지에서 새로운 아트워크를 추가하나, 글자수가 오버될 경우.',()=>{ + const filteredRequiredFields = requiredFields.filter(field => field.type==='type'); + cy.visit('/promotion-admin/artwork'); + filteredRequiredFields.forEach((field) => { + cy.contains('아트워크 생성하기').click(); + cy.wait(100); + cy.get('[data-cy="PA_artwork_createBox"]').within(()=>{ + if (field.maxLength!==undefined) { + const longText = 'A'.repeat(field.maxLength + 10); + const expectedText = 'A'.repeat(field.maxLength); + cy.get(field.selector).type(longText); + cy.get(field.selector).should('have.value', expectedText); + } + cy.reload() + }) + }) + }); + it('관리 페이지에서 추가한 아트워크가 정상적으로 나타나는지 확인한다.',()=>{ cy.visit('/promotion-admin/artwork'); - cy.get('[data-cy="PA_artwork_list"]').contains(testData.title).click(); + cy.get('[data-cy="PA_artwork_list"]').contains(testData.title.slice(0,10)).click(); // data 객체의 모든 키-값 쌍을 순회하며 각 값이 페이지에 존재하는지 확인 cy.get('[data-cy="PP_artwork_detail"]').within(()=>{ Object.entries(testData).forEach(([key, value]) => { @@ -134,17 +156,17 @@ describe('Artwork-아트워크를 수정하고, 수정한 아트워크가 반영 { name: '제작 일시',selector: '[data-cy="create_artwork_date"]', value: data[1].date, type:'type' }, { name: '카테고리',selector: '[data-cy="create_artwork_category"]', value: data[1].category, type: 'dropdown' }, { name: '링크',selector: '[data-cy="create_artwork_link"]', value: data[1].link, type:'type' }, - { name: '타입',selector: '[data-cy="create_artwork_artworkType"]', value: data[1].artworkType, type: 'select' }, - { name: '공개 여부',selector: '[data-cy="create_artwork_isOpened"]', value: data[1].isOpened, type: 'select' }, + { name: '타입',selector: '[data-cy="create_artwork_artworkType"]', value: data[1].artworkType, type: 'select-type' }, + { name: '공개 여부',selector: '[data-cy="create_artwork_isOpened"]', value: data[1].isOpened, type: 'select-open' }, ]; }); }); - it('권장 예외) PA 페이지에서 아트워크를 수정하나, 안 채운 값이 있을 경우.',()=>{ + it('권장 예외) 관리 페이지에서 아트워크를 수정하나, 안 채운 값이 있을 경우.',()=>{ const filteredRequiredFields = requiredFields.filter(field => field.type==='type') cy.visit('/promotion-admin/artwork') filteredRequiredFields.forEach((field) => { - cy.get('[data-cy="PA_artwork_list"]').contains('이준호와 임윤아의 킹더랜드 인터뷰').click() + cy.get('[data-cy="PA_artwork_list"]').contains('이준호와 임윤아의').click() cy.get('[data-cy="modify_artwork_submit"]').click() cy.wait(1000) cy.get('[data-cy="PP_artwork_detail"]').within(()=>{ @@ -172,9 +194,9 @@ describe('Artwork-아트워크를 수정하고, 수정한 아트워크가 반영 }) }); - it('필수 예외) PA 페이지에서 아트워크를 수정하나, 링크가 잘못됐을 경우.',()=>{ + it('필수 예외) 관리 페이지에서 아트워크를 수정하나, 링크가 잘못됐을 경우.',()=>{ cy.visit('/promotion-admin/artwork') - cy.get('[data-cy="PA_artwork_list"]').contains('이준호와 임윤아의 킹더랜드 인터뷰').click() + cy.get('[data-cy="PA_artwork_list"]').contains('이준호와 임윤아의').click() cy.get('[data-cy="modify_artwork_submit"]').click() cy.wait(1000) cy.get('[data-cy="PP_artwork_detail"]').within(()=>{ @@ -185,9 +207,9 @@ describe('Artwork-아트워크를 수정하고, 수정한 아트워크가 반영 } ); - it('PA 페이지에서 추가했던 아트워크를 수정한다.',()=>{ + it('관리 페이지에서 추가했던 아트워크를 수정한다.',()=>{ cy.visit('/promotion-admin/artwork') - cy.get('[data-cy="PA_artwork_list"]').contains('이준호와 임윤아의 킹더랜드 인터뷰').click() + cy.get('[data-cy="PA_artwork_list"]').contains('이준호와 임윤아의').click() cy.get('[data-cy="modify_artwork_submit"]').click() cy.wait(1000) cy.get('[data-cy="PP_artwork_detail"]').within(()=>{ @@ -195,9 +217,11 @@ describe('Artwork-아트워크를 수정하고, 수정한 아트워크가 반영 if(field.type==='dropdown'){ cy.get(field.selector).click({force:true}) cy.get('[data-cy="create_artwork_category_dropdown"]').contains(field.value).click({force:true}) - } else if (field.type === 'select') { - cy.get(field.selector).contains(field.value).click({force:true}) - } else { + } else if (field.type==='select-type') { + cy.get(field.selector).contains(field.value==='Top'?'대표':field.value==='Main'?'메인':'기본').click({force:true}); + } else if (field.type==='select-open') { + cy.get(field.selector).contains(field.value).click({force:true}); + } else { cy.get(field.selector).clear({force:true}) cy.get(field.selector).type(field.value) } @@ -210,9 +234,9 @@ describe('Artwork-아트워크를 수정하고, 수정한 아트워크가 반영 }) }); - it('PA 페이지에서 수정한 아트워크가 정상적으로 나타나는지 확인한다.',()=>{ + it('관리 페이지에서 수정한 아트워크가 정상적으로 나타나는지 확인한다.',()=>{ cy.visit('/promotion-admin/artwork') - cy.get('[data-cy="PA_artwork_list"]').contains(testData.title).click() + cy.get('[data-cy="PA_artwork_list"]').contains(testData.title.slice(0,10)).click() cy.wait(1000) // data 객체의 모든 키-값 쌍을 순회하며 각 값이 페이지에 존재하는지 확인 cy.get('[data-cy="PP_artwork_detail"]').within(()=>{ @@ -280,7 +304,7 @@ describe('Artwork-아트워크를 삭제하고, 삭제한 아트워크가 뜨지 it('관리 페이지에서 아트워크를 삭제한다.',()=>{ cy.visit('/promotion-admin/artwork') - cy.get('[data-cy="PA_artwork_list"]').contains(testData.title).click() + cy.get('[data-cy="PA_artwork_list"]').contains(testData.title.slice(0,10)).click() cy.contains('삭제하기').click() cy.on('window:confirm',()=>true) }); diff --git a/cypress/e2e/System/MainPage.cy.ts b/cypress/e2e/System/MainPage.cy.ts index 783ae6cf..28f0f90c 100644 --- a/cypress/e2e/System/MainPage.cy.ts +++ b/cypress/e2e/System/MainPage.cy.ts @@ -1,191 +1,229 @@ import { getCompanyData } from '@/apis/PromotionAdmin/dataEdit'; import { ARTWORKLIST_DATA, INTRO_DATA } from '@/constants/introdutionConstants'; import { login } from 'cypress/support/hooks'; -import { ArtworkData, PPArtworkData, IntroData, ClientData } from 'cypress/support/types'; +import { ArtworkData, PPArtworkData, IntroData, ClientData, ArtworkRequiredField } from 'cypress/support/types'; let PPintroTestData: IntroData; let PPartworkTestData: PPArtworkData; let PAartworkTestData: ArtworkData; let PAIntroTestData: IntroData; let PAClientTestData: ClientData[]; -const mainTestImage1 = 'cypress/fixtures/MainPage/main1.jpg'; -const mainTestImage2 = 'cypress/fixtures/MainPage/main2.png'; -const mainTestImage3 = 'cypress/fixtures/MainPage/main3.png'; +const mainTestImage1 = 'cypress/fixtures/MainPage/디피-main.jpg'; +const mainTestImage2 = 'cypress/fixtures/MainPage/디피-detail1.png'; +const mainTestImage3 = 'cypress/fixtures/MainPage/디피-detail2.png'; const clientTestImages = ['cypress/fixtures/MainPage/Netflix_Logo.png', 'cypress/fixtures/MainPage/CJ_ENM_Logo.png']; -describe('Artwork 데이터 생성, 수정, 삭제 및 확인', () => { +describe('Artwork 데이터 생성, 수정, 삭제 및 확인한다.', () => { + let requiredFields: ArtworkRequiredField[]; beforeEach(() => { + cy.viewport(1920, 1080); login(); - cy.fixture('MainPage/PA_artwork_data.json').then((data) => { - PAartworkTestData = data; + cy.fixture('Artwork/artwork_data.json').then((data) => { + PAartworkTestData = data[3]; + requiredFields = [ + { name: '제목', selector: '[data-cy="create_artwork_title"]', value: data[3].title, type: 'type' }, + { name: '설명', selector: '[data-cy="create_artwork_overview"]', value: data[3].overview, type: 'type' }, + { name: '고객사', selector: '[data-cy="create_artwork_customer"]', value: data[3].customer, type: 'type' }, + { name: '제작 일시', selector: '[data-cy="create_artwork_date"]', value: data[3].date, type: 'type' }, + { + name: '카테고리', + selector: '[data-cy="create_artwork_category"]', + value: data[3].category, + type: 'dropdown', + }, + { name: '링크', selector: '[data-cy="create_artwork_link"]', value: data[3].link, type: 'type' }, + { + name: '타입', + selector: '[data-cy="create_artwork_artworkType"]', + value: data[3].artworkType, + type: 'select', + }, + ]; }); }); - it('Artwork 데이터를 추가한다.', () => { + it('관리 페이지에서 Artwork 데이터를 추가한다.', () => { cy.visit('/promotion-admin/artwork'); cy.contains('아트워크 생성하기').click(); cy.wait(500); cy.get('[data-cy="PA_artwork_createBox"]').within(() => { - cy.get('[data-cy="create_artwork_title"]').type(PAartworkTestData.title); - cy.get('[data-cy="create_artwork_overview"]').type(PAartworkTestData.overview); - cy.get('[data-cy="create_artwork_customer"]').type(PAartworkTestData.customer); - cy.get('[data-cy="create_artwork_date"]').type(PAartworkTestData.date); - cy.get('[data-cy="create_artwork_link"]').type(PAartworkTestData.link); - cy.get('[data-cy="create_artwork_artworkType"]').contains(PAartworkTestData.artworkType).click(); - - // 드롭다운 클릭 후 선택 - cy.get('[data-cy="create_artwork_category"]').click(); - cy.get('[data-cy="create_artwork_category_dropdown"]').contains(PAartworkTestData.category).click(); + requiredFields.forEach((field, idx) => { + if (field.type === 'dropdown') { + cy.get(field.selector).click({ force: true }); + cy.get('[data-cy="create_artwork_category_dropdown"]').contains(field.value).click({ force: true }); + } else if (field.type === 'select') { + cy.get(field.selector).contains(field.value).click({ force: true }); + } else { + cy.get(field.selector).type(field.value); + } + }); cy.get('[data-cy="create_main_image"]').selectFile(mainTestImage1, { force: true }); - cy.get('[data-cy="create_detail_image"]').selectFile(mainTestImage3, { force: true }); - cy.scrollTo('bottom'); + cy.get('[data-cy="create_detail_image"]').selectFile(mainTestImage2, { force: true }); + cy.get('[data-cy="create_responsiveMain_image"]').selectFile(mainTestImage3, { force: true }); + cy.scrollTo('bottom'); cy.get('[data-cy="create_artwork_submit"]').click(); cy.wait(1000); }); }); - it('Artwork 데이터를 확인한다.', () => { + it('홍보 페이지에서 Artwork 데이터를 확인한다.', () => { cy.visit('/'); - cy.get('[data-cy="artwork-section"]').each(($section) => { - // 각 요소에 대해 - cy.wrap($section).within(() => { - cy.contains(PAartworkTestData.title).should('exist'); - cy.contains(PAartworkTestData.overview).should('exist'); - cy.contains(PAartworkTestData.customer).should('exist'); - }); + cy.get('[data-cy="artworklist-section"]').within(() => { + cy.get('[data-cy="artwork_name"]').should('contain', PAartworkTestData.title); + cy.get('[data-cy="artwork_client"]').should('contain', PAartworkTestData.customer); + cy.get('[data-cy="artwork_overview"]').should('contain', PAartworkTestData.overview); }); }); - it('Artwork 데이터를 수정한다.', () => { + it('관리 페이지에서 Artwork 데이터를 수정한다.', () => { cy.visit('/promotion-admin/artwork'); - cy.wait(500); cy.contains(PAartworkTestData.title).click({ force: true }); + cy.get('[data-cy="PA_artwork_list"]').contains('D.P. 시즌 2 비하인드 코멘터리').click(); + cy.wait(500); cy.get('[data-cy=modify_artwork_submit]').click({ force: true }); - - cy.get('[data-cy="create_artwork_title"]').click({ force: true }).type('{end}').type(' - 수정'); - - cy.get('[data-cy="create_artwork_overview"]').click({ force: true }).type('{end}').type(` - 수정`); - cy.get('[data-cy="create_artwork_customer"]').click({ force: true }).type('{end}').type(` - 수정`); - cy.scrollTo('bottom'); - - - cy.get('[data-cy="create_main_image"]').selectFile(mainTestImage1, { force: true }); - cy.get('[data-cy="create_detail_image"]').selectFile(mainTestImage3, { force: true }); + + cy.get('[data-cy="PP_artwork_detail"]').within(()=>{ + requiredFields.forEach((field, idx) => { + if (field.type === 'dropdown') { + cy.get(field.selector).click({ force: true }); + cy.get('[data-cy="create_artwork_category_dropdown"]').contains(field.value).click({ force: true }); + } else if (field.type === 'select') { + cy.get(field.selector).contains(field.value).click({ force: true }); + } else { + if (field.name === '제목') { + // 제목만 수정 + cy.get(field.selector).clear({ force: true }); + cy.get(field.selector).type(field.value + ' - 수정'); + } else { + // 다른 필드는 원본 그대로 + cy.get(field.selector).clear({ force: true }); + cy.get(field.selector).type(field.value); + } + } + }); + + cy.get('[data-cy="create_main_image"]').selectFile(mainTestImage3, { force: true }); + cy.get('[data-cy="create_detail_image"]').selectFile(mainTestImage1, { force: true }); + cy.get('[data-cy="create_responsiveMain_image"]').selectFile(mainTestImage2, { force: true }); + cy.scrollTo('bottom'); cy.get('[data-cy="modify_artwork_finish"]').click(); cy.wait(1000); - }); + }); + }); - it('수정된 Artwork 데이터 확인', () => { + it('홍보 페이지에서 수정된 Artwork 데이터를 확인한다.', () => { cy.visit('/'); - cy.get('[data-cy="artwork-section"]').within(() => { - cy.contains(`${PAartworkTestData.title} - 수정`).should('exist'); - cy.contains(`${PAartworkTestData.overview} - 수정`).should('exist'); - cy.contains(`${PAartworkTestData.customer} - 수정`).should('exist'); + cy.get('[data-cy="artworklist-section"]').within(() => { + cy.get('[data-cy="artwork_name"]').should('contain', PAartworkTestData.title); }); }); - it('Artwork 데이터를 전체 삭제한다.', () => { - cy.visit('/promotion-admin/artwork'); - cy.contains(PAartworkTestData.title).click(); - cy.contains('삭제하기').click(); - cy.on('window:confirm', () => true); - }); - - it('삭제 후 Artwork 섹션에 디폴트 아트워크 데이터가 있는지 확인한다.', () => { - cy.visit('/'); - cy.get('[data-cy="artwork-section"]').within(() => { - cy.get('[data-cy="artwork_name"]').should('contain', ARTWORKLIST_DATA.TITLE); - cy.get('[data-cy="artwork_client"]').should('contain', ARTWORKLIST_DATA.CLIENT); - cy.get('[data-cy="artwork_overview"]').should('contain', ARTWORKLIST_DATA.OVERVIEW); - }); - }); -}); - -describe('Client 데이터 생성, 수정, 삭제 및 확인', () => { - beforeEach(() => { - login(); - cy.fixture('MainPage/PA_client_data.json').then((data: ClientData[]) => { - PAClientTestData = data; - }); + it('관리 페이지에서 생성한 Artwork 데이터를 삭제한다.', () => { + cy.visit('/promotion-admin/artwork'); + cy.get('[data-cy="PA_artwork_list"]').contains(PAartworkTestData.title).click(); + cy.contains('삭제하기').click(); + cy.on('window:confirm', () => true); }); - it('Client 데이터를 추가한다.', () => { - cy.visit('/promotion-admin/dataEdit'); - cy.get('[data-cy="nav-btn-client"]').click(); - cy.wait(500); - - PAClientTestData.forEach((client, index) => { - cy.get('#create_client').click(); - const clientTestImage = clientTestImages[index]; - cy.get('input[type="file"]').selectFile(clientTestImage, { force: true }); - cy.get('#create_client_name').focus().type(client.name, { force: true }); - if (client.visibility) { - cy.get('#switch').check({ force: true }); - } else { - cy.get('#switch').uncheck({ force: true }); - } - cy.get('#create_client_finish').click({ force: true }); - cy.wait(500); - }); + it('관리 페이지에서 삭제한 아트워크가 뜨지 않는지 확인한다.', () => { + cy.visit('/promotion-admin/artwork'); + cy.contains(PAartworkTestData.title).should('not.exist'); }); - it('outro 섹션의 클라이언트 이미지를 확인한다.', () => { + it('삭제 후 홍보 페이지에서 Artwork 섹션에 디폴트 아트워크 데이터가 있는지 확인한다.', () => { cy.visit('/'); - cy.get('[data-cy="outro-section"]').within(() => { - cy.get('img').should('exist'); // 이미지가 존재하는지 확인 + cy.get('[data-cy="artworklist-section"]').within(() => { + cy.get('[data-cy="artwork_name"]').should('contain', ARTWORKLIST_DATA.TITLE); + cy.get('[data-cy="artwork_client"]').should('contain', ARTWORKLIST_DATA.CLIENT); + cy.get('[data-cy="artwork_overview"]').should('contain', ARTWORKLIST_DATA.OVERVIEW); }); }); +}); - it('Client 데이터를 수정한다.', () => { - cy.visit('/promotion-admin/dataEdit'); - cy.get('[data-cy="nav-btn-client"]').click(); - cy.wait(500); +////////////////////////////////////////////////////////////////// - PAClientTestData.forEach((client) => { - cy.contains(client.name).click({ force: true }); - cy.wait(500); - cy.get('[data-cy="edit_client_name"]').click({ force: true }).type('{end}').type(' - 수정'); - cy.contains('저장하기').click({ force: true }); - cy.wait(1000); - }); - }); +// describe('Client 데이터 생성, 수정, 삭제 및 확인', () => { +// beforeEach(() => { +// login(); +// cy.fixture('MainPage/PA_client_data.json').then((data: ClientData[]) => { +// PAClientTestData = data; +// }); +// }); - it('수정된 Client 데이터 확인', () => { - cy.visit('/promotion-admin/dataEdit'); - cy.get('[data-cy="nav-btn-client"]').click(); - PAClientTestData.forEach((client) => { - cy.get('[data-cy="client_name"]').contains(`${client.name} - 수정`).should('exist'); - }); - }); +// it('관리 페이지에서 Client 데이터를 추가한다.', () => { +// cy.visit('/promotion-admin/dataEdit'); +// cy.get('[data-cy="nav-btn-client"]').click(); +// cy.wait(500); - it('Client 데이터를 전체 삭제한다.', () => { - cy.visit('/promotion-admin/dataEdit'); - cy.get('[data-cy="nav-btn-client"]').click(); - cy.wait(1000); - PAClientTestData.forEach((client) => { - cy.contains(client.name).click({ force: true }); - cy.contains('삭제하기').click({ force: true }); - cy.on('window:confirm', () => true); - }); - }); +// PAClientTestData.forEach((client, index) => { +// cy.get('#create_client').click(); +// const clientTestImage = clientTestImages[index]; +// cy.get('input[type="file"]').selectFile(clientTestImage, { force: true }); +// cy.get('#create_client_name').focus().type(client.name, { force: true }); +// if (client.visibility) { +// cy.get('#switch').check({ force: true }); +// } else { +// cy.get('#switch').uncheck({ force: true }); +// } +// cy.get('#create_client_finish').click({ force: true }); +// cy.wait(500); +// }); +// }); - it('Client 리스트가 존재하지 않는 Outro 섹션을 확인한다.', () => { - cy.visit('/'); - cy.get('[data-cy="outro-section"]').within(() => { - clientTestImages.forEach((image) => { - cy.get(`img[src="${image}"]`).should('not.exist'); // 이미지가 존재하지 않는지 확인 - }); - // PAClientTestData.forEach((client) => { - // cy.contains(client.name).should('not.exist'); - // }); - }); - }); -}); +// it('홍보 페이지에서 outro 섹션의 클라이언트 이미지를 확인한다.', () => { +// cy.visit('/'); +// cy.get('[data-cy="outro-section"]').within(() => { +// cy.get('img').should('exist'); // 이미지가 존재하는지 확인 +// }); +// }); + +// it('관리 페이지에서 Client 데이터를 수정한다.', () => { +// cy.visit('/promotion-admin/dataEdit'); +// cy.get('[data-cy="nav-btn-client"]').click(); +// cy.wait(500); + +// PAClientTestData.forEach((client) => { +// cy.contains(client.name).click({ force: true }); +// cy.wait(500); +// cy.get('[data-cy="edit_client_name"]').click({ force: true }).type('{end}').type(' - 수정'); +// cy.contains('저장하기').click({ force: true }); +// cy.wait(1000); +// }); +// }); + +// it('홍보 페이지에서 수정된 Client 데이터를 확인한다.', () => { +// cy.visit('/promotion-admin/dataEdit'); +// cy.get('[data-cy="nav-btn-client"]').click(); +// PAClientTestData.forEach((client) => { +// cy.get('[data-cy="client_name"]').contains(`${client.name} - 수정`).should('exist'); +// }); +// }); + +// it('관리 페이지에서 Client 데이터를 전체 삭제한다.', () => { +// cy.visit('/promotion-admin/dataEdit'); +// cy.get('[data-cy="nav-btn-client"]').click(); +// cy.wait(1000); +// PAClientTestData.forEach((client) => { +// cy.contains(client.name).click({ force: true }); +// cy.contains('삭제하기').click({ force: true }); +// cy.on('window:confirm', () => true); +// }); +// }); + +// it('홍보 페이지에서 Client 리스트가 존재하지 않는 Outro 섹션을 확인한다.', () => { +// cy.visit('/'); +// cy.get('[data-cy="outro-section"]').within(() => { +// clientTestImages.forEach((image) => { +// cy.get(`img[src="${image}"]`).should('not.exist'); // 이미지가 존재하지 않는지 확인 +// }); +// }); +// }); +// }); // describe('Intro 데이터 생성, 수정 및 확인', () => { // beforeEach(() => { diff --git a/cypress/fixtures/Artwork/artwork_data.json b/cypress/fixtures/Artwork/artwork_data.json index 8d815852..2ad3feb5 100644 --- a/cypress/fixtures/Artwork/artwork_data.json +++ b/cypress/fixtures/Artwork/artwork_data.json @@ -30,9 +30,9 @@ }, { "title": "D.P. 시즌 2 비하인드 코멘터리", - "overView": "배우 정해인, 구교환, 김성균, 손석구 그리고 한준희 감독이 말해주는 D.P. 시즌 2 비하인드 코멘터리", + "overview": "배우 정해인, 구교환, 김성균, 손석구 그리고 한준희 감독이 말해주는 D.P. 시즌 2 비하인드 코멘터리", "customer": "NETFLIX Korea Youtube", - "date": "2021-12", + "date": "2021-12-01", "category": "Entertainment", "link": "https://www.youtube.com/watch?v=ys1BGlHvnCw", "artworkType": "Main", diff --git a/cypress/support/hooks.ts b/cypress/support/hooks.ts index 6ded8285..95b04fbf 100644 --- a/cypress/support/hooks.ts +++ b/cypress/support/hooks.ts @@ -23,3 +23,17 @@ export const confirmAndCheckCompletion = (confirmMessage: string, alertMessage: return true; // 알림 확인 }); }; + +// HTML 태그 제거 및 HTML 엔티티 디코딩 함수 +export const normalizeHtml = (html: string) => { + // HTML 엔티티 디코딩 + const textarea = document.createElement('textarea'); + textarea.innerHTML = html; + const decodedHtml = textarea.value; + + // 태그 제거 및 텍스트 정리 + return decodedHtml + .replace(/<[^>]*>/g, '') // HTML 태그 제거 + .replace(/\s+/g, ' ') // 공백 정리 + .trim(); // 앞뒤 공백 제거 +}; diff --git a/cypress/support/types.d.ts b/cypress/support/types.d.ts index 96355ab7..df088d6f 100644 --- a/cypress/support/types.d.ts +++ b/cypress/support/types.d.ts @@ -20,7 +20,8 @@ export interface ArtworkRequiredField { name: string; selector: string; value: string; - type?: 'type' | 'dropdown' | 'select'; + type?: 'type' | 'dropdown' | 'select-type' | 'select-open'; + maxLength?: number; } export interface IntroData { diff --git a/src/components/Error/ErrorComponent.tsx b/src/components/Error/ErrorComponent.tsx new file mode 100644 index 00000000..cbcb9e5f --- /dev/null +++ b/src/components/Error/ErrorComponent.tsx @@ -0,0 +1,122 @@ +import { ERROR_MESSAGES } from "@/constants/ppErrorMessage"; +import { theme } from "@/styles/theme"; +import { AxiosError } from "axios"; +import { useState } from "react"; +import styled, { keyframes } from "styled-components"; + +interface ErrorMessageProps { + error: AxiosError | null; + onClose: () => void; // 모달 닫기 콜백 +} + +const getErrorMessage = (error: AxiosError | null): string => { + if (error?.message === "Network Error") { + return ERROR_MESSAGES.NETWORK_ERROR; + } + if (error?.response) { + switch (error?.response.status) { + case 400: + return ERROR_MESSAGES.BAD_REQUEST; + case 401: + return ERROR_MESSAGES.UNAUTHORIZED; + case 403: + return ERROR_MESSAGES.FORBIDDEN; + case 404: + return ERROR_MESSAGES.NOT_FOUND; + case 500: + return ERROR_MESSAGES.SERVER_ERROR; + default: + return ERROR_MESSAGES.DEFAULT_ERROR; + } + } + return ERROR_MESSAGES.DEFAULT_ERROR; +}; + +const ErrorComponent:React.FC=({error, onClose})=> { + const [isExiting, setIsExiting] = useState(false); + const message = getErrorMessage(error); + if (!error||error===null) return null; + + const handleClose = () => { + setIsExiting(true); + setTimeout(() => { + onClose(); + }, 300); // 애니메이션 지속 시간과 동일 + }; + + return ( + + +

Error

+

{message}

+ 닫기 +
+
+);}; + +const fadeIn = keyframes` + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1); + } +`; + +const fadeOut = keyframes` + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.9); + } +`; + +const ModalOverlay = styled.div` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +`; + +const ModalContainer = styled.div<{ isExiting: boolean }>` + background-color: #454545; + padding: 24px; + border-radius: 8px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + max-width: 400px; + width: 100%; + text-align: center; + + animation: ${(props) => (props.isExiting ? fadeOut : fadeIn)} 0.3s ease-in-out; +`; + +const CloseButton = styled.button` + margin-top: 16px; + padding: 8px 16px; + background-color: ${theme.color.yellow.bold}; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + + &:hover { + background-color: #ffba59; + } + + &:focus { + outline: none; + } +`; + +export default ErrorComponent; \ No newline at end of file diff --git a/src/components/PromotionAdmin/Artwork/ArtworkCreating/ArtworkCreating.tsx b/src/components/PromotionAdmin/Artwork/ArtworkCreating/ArtworkCreating.tsx index 7881aa12..ab2a484f 100644 --- a/src/components/PromotionAdmin/Artwork/ArtworkCreating/ArtworkCreating.tsx +++ b/src/components/PromotionAdmin/Artwork/ArtworkCreating/ArtworkCreating.tsx @@ -18,7 +18,7 @@ const ArtworkCreating = () => { const [projectType, setProjectType] = useState('others'); const [link, setLink] = useState(''); const [mainImage, setMainImage] = useState(); - const [responsiveMainImage, setResponsiveMainImage]=useState(); + const [responsiveMainImage, setResponsiveMainImage] = useState(); const [detailImages, setDetailImages] = useState([]); const [title, setTitle] = useState(''); const [customer, setCustomer] = useState(''); @@ -26,7 +26,6 @@ const ArtworkCreating = () => { const [overview, setOverview] = useState(''); const [submitButtonDisabled, setSubmitButtonDisabled] = useState(true); const [linkRegexMessage, setLinkRegexMessage] = useState(''); - const [errorMessage, setErrorMessage] = useState(''); const [isTopMainArtwork, setIsTopMainArtwork] = useState(false); const navigate = useNavigate(); @@ -58,7 +57,6 @@ const ArtworkCreating = () => { ]); useEffect(() => { - setErrorMessage(''); if (projectType === 'top' || projectType === 'main') { setIsTopMainArtwork(true); } else { @@ -93,16 +91,17 @@ const ArtworkCreating = () => { setMainImage(Array.isArray(newImage) ? newImage[0] : newImage); }; - const handleResponsiveMainImageChange=(newImage:File|File[])=>{ + const handleResponsiveMainImageChange = (newImage: File | File[]) => { setResponsiveMainImage(Array.isArray(newImage) ? newImage[0] : newImage); - } + }; + // 상세 이미지 상태 (1~3개) const handleDetailImageChange = (newImages: File | File[]) => { - const newImageList = Array.isArray(newImages) ? newImages : [newImages]; - // 기존 이미지와 새로운 이미지를 합치고 최대 3개까지만 유지 - const updatedImages = [...detailImages, ...newImageList].slice(0, 3); + const updatedImages = Array.isArray(newImages) ? newImages : [newImages]; + + // 최대 3개 제한 if (updatedImages.length > 3) { - alert('최대 3개의 이미지만 업로드할 수 있습니다.'); + updatedImages.splice(3); } setDetailImages(updatedImages); }; @@ -133,8 +132,8 @@ const ArtworkCreating = () => { if (mainImage) { formData.append('file', mainImage); } - if (responsiveMainImage){ - formData.append('responsiveFile',responsiveMainImage); + if (responsiveMainImage) { + formData.append('responsiveFile', responsiveMainImage); } if (detailImages) { detailImages.forEach((file, index) => { @@ -145,7 +144,6 @@ const ArtworkCreating = () => { try { const response = await postArtwork(formData); if (response.code === 400 && response.data === null && response.message) { - setErrorMessage(response.message); return; } alert(MSG.ALERT_MSG.SAVE); @@ -187,26 +185,31 @@ const ArtworkCreating = () => { setProducingIsOpened(false)}>x - {defaultValue.map((item: DefaultValueItem, index: number) => ( - item.name==='responsiveMainImage'?null: - item.name === 'mainImage' && defaultValue[index + 1]?.name === 'responsiveMainImage'? -
- {errorMessage && ⚠ {errorMessage}} - - -
- : -
- {errorMessage && item.name === 'artworkType' && ⚠ {errorMessage}} - {linkRegexMessage && item.name === 'link' && ⚠ {linkRegexMessage}} - -
- ))} + {defaultValue.map((item: DefaultValueItem, index: number) => + item.name === 'responsiveMainImage' ? null : item.name === 'mainImage' && + defaultValue[index + 1]?.name === 'responsiveMainImage' ? ( +
+ {/* {errorMessage && ⚠ {errorMessage}} */} + + +
+ ) : ( +
+ {/* {errorMessage && item.name === 'artworkType' && ⚠ {errorMessage}} */} + {linkRegexMessage && item.name === 'link' && ⚠ {linkRegexMessage}} + +
+ ), + )}
handleSubmit()} > 저장하기 @@ -230,12 +233,13 @@ const ValueWrapper = styled.div` backdrop-filter: blur(10px); box-sizing: border-box; width: fit-content; - height: 700px; + height: 32rem; overflow-y: scroll; - padding: 55px 55px; + width: 100%; + padding: 2.3rem; display: grid; grid-template-columns: repeat(2, 1fr); - gap: 30px; + gap: 0.5rem; /* 박스 간 간격 */ `; const CloseContainer = styled.div` @@ -252,7 +256,7 @@ const SubmitBtn = styled.button` border: none; outline-style: none; font-family: 'pretendard-semibold'; - font-size: 17px; + font-size: 1rem; background-color: #6c757d; width: 150px; text-align: center; @@ -260,10 +264,9 @@ const SubmitBtn = styled.button` border-radius: 5px; transition: all 0.3s ease-in-out; cursor: pointer; - padding: 10px 20px; + padding: 0.7rem; margin-left: auto; margin-top: 20px; - grid-column: 1 / -1; &:disabled { opacity: 0.5; cursor: default; @@ -273,6 +276,8 @@ const SubmitBtn = styled.button` } &:hover { background-color: #5a6268; + cursor: pointer; + transition: all 300ms ease-in-out; } `; diff --git a/src/components/PromotionAdmin/Artwork/ArtworkCreating/ArtworkDefaultValue.tsx b/src/components/PromotionAdmin/Artwork/ArtworkCreating/ArtworkDefaultValue.tsx index 11ad6f01..bad0f0ed 100644 --- a/src/components/PromotionAdmin/Artwork/ArtworkCreating/ArtworkDefaultValue.tsx +++ b/src/components/PromotionAdmin/Artwork/ArtworkCreating/ArtworkDefaultValue.tsx @@ -3,6 +3,7 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import dayjs from 'dayjs'; +import { useState } from 'react'; import styled from 'styled-components'; import CategoryDropDown from '../CategoryDropDown'; import ImageUpload from './ImageUpload'; @@ -29,7 +30,7 @@ export const getArtworkDefaultValue = ( mainImage: File | undefined | null, handleMainImageChange: (newImage: File | File[]) => void, responsiveMain: File | undefined | null, - handleResponsiveMainImageChange: (newImage: File|File[])=>void, + handleResponsiveMainImageChange: (newImage: File | File[]) => void, detailImage: File[], handleDetailImageChange: (newImages: File | File[]) => void, title: string, @@ -39,8 +40,9 @@ export const getArtworkDefaultValue = ( overview: string, handleOverviewChange: (newOverview: string) => void, isTopMainArtwork: boolean, + handleImageClick?: (src: string) => void, getModeMainImg?: string, - getModeResponsiveMainImg? : string, + getModeResponsiveMainImg?: string, getModeDetailImgs?: string[], isGetMode?: boolean, ) => { @@ -52,14 +54,16 @@ export const getArtworkDefaultValue = ( content: isGetMode ? ( {title} ) : ( - ) => handleTitleChange(e.target.value)} - /> + + ) => handleTitleChange(e.target.value)} + /> + {title.length} / 30 + ), }, { @@ -69,14 +73,16 @@ export const getArtworkDefaultValue = ( content: isGetMode ? ( {overview} ) : ( - ) => handleOverviewChange(e.target.value)} - /> + + ) => handleOverviewChange(e.target.value)} + /> + {overview.length} / 120 + ), }, { @@ -86,14 +92,16 @@ export const getArtworkDefaultValue = ( content: isGetMode ? ( {customer} ) : ( - ) => handleCustomerChange(e.target.value)} - /> + + ) => handleCustomerChange(e.target.value)} + /> + {customer.length} / 30 + ), }, { @@ -110,8 +118,11 @@ export const getArtworkDefaultValue = ( onChange={(newValue) => handleDateChange(newValue ? newValue.toDate() : null)} slotProps={{ textField: { - inputProps:{'data-cy':'create_artwork_date'}, + inputProps: { 'data-cy': 'create_artwork_date' }, sx: { + width: '100%', + borderRadius: '5px', + fontSize: '0.8rem', backgroundColor: '#dadada9f', '.MuiOutlinedInput-root': { border: 'none', @@ -139,13 +150,17 @@ export const getArtworkDefaultValue = ( content: isGetMode ? ( {selectedCategory} ) : ( - + ), }, { name: 'link', title: '아트워크 외부 연결 미디어 링크', - description: '', + description: '아트워크 외부 연결 링크는 최대 200자까지 입력 가능합니다.', content: isGetMode ? ( @@ -153,25 +168,28 @@ export const getArtworkDefaultValue = ( ) : ( - ) => handleLinkChange(e.target.value)} - /> + + ) => handleLinkChange(e.target.value)} + /> + {link.length} / 200 + ), }, { name: 'artworkType', title: '아트워크 타입', - description: `① Top\n 메인 페이지에서 가장 먼저 보이는 아트워크이며, 1개만 지정할 수 있습니다. \n\n ③ Main\n 메인 페이지에서 슬라이드로 보이는 아트워크이며, 최대 5개까지 지정할 수 있습니다. \n\n ② Others\n 그 외의 아트워크 유형으로, 아트워크 페이지에서 보여집니다.`, + description: `① 대표\n 메인 페이지에서 가장 먼저 보이는 아트워크이며, 1개만 지정할 수 있습니다. \n\n ③ 메인\n 메인 페이지에서 슬라이드로 보이는 아트워크이며, 최대 5개까지 지정할 수 있습니다. \n\n ② 기본\n 그 외의 아트워크 유형으로, 아트워크 페이지에서 보여집니다.`, content: ( -
!isGetMode && projectType !== 'top' && setProjectType('top')}>Top
-
!isGetMode && projectType !== 'main' && setProjectType('main')}>Main
-
!isGetMode && projectType !== 'others' && setProjectType('others')}>Others
+
!isGetMode && projectType !== 'top' && setProjectType('top')}>대표
+
!isGetMode && projectType !== 'main' && setProjectType('main')}>메인
+
!isGetMode && projectType !== 'others' && setProjectType('others')}>기본
), }, @@ -181,16 +199,36 @@ export const getArtworkDefaultValue = ( description: '비공개로 설정할 시, 프로모션의 아트워크 페이지에서 숨겨집니다.', content: isTopMainArtwork ? ( <> - ⚠ Top, Main 선택 시 항상 프로모션 페이지에 공개됩니다. + ⚠ 대표, 메인 선택 시 항상 프로모션 페이지에 공개됩니다. -
!isGetMode && !isprojectopened && handleTogglePosted()}>공개
-
!isGetMode && isprojectopened && handleTogglePosted()}>비공개
+
!isGetMode && !isprojectopened && handleTogglePosted()} + > + 공개 +
+
!isGetMode && isprojectopened && handleTogglePosted()} + > + 비공개 +
) : ( -
!isGetMode && !isprojectopened && handleTogglePosted()}>공개
-
!isGetMode && isprojectopened && handleTogglePosted()}>비공개
+
!isGetMode && !isprojectopened && handleTogglePosted()} + > + 공개 +
+
!isGetMode && isprojectopened && handleTogglePosted()} + > + 비공개 +
), }, @@ -201,11 +239,10 @@ export const getArtworkDefaultValue = ( description: '썸네일 이미지는 최대 한 개만 설정 가능합니다.', content: isGetMode && getModeMainImg ? ( - + handleImageClick && handleImageClick(getModeMainImg)}> + + 클릭하여 상세보기 + ) : ( <> ), }, + { name: 'responsiveMainImage', title: '아트워크 반응형 썸네일 이미지 설정', description: '반응형 썸네일 이미지는 최대 한 개만 설정 가능합니다.\n권장픽셀: 400px*900px', content: - isGetMode && getModeResponsiveMainImg ? ( - - ) : ( - <> - handleResponsiveMainImageChange(newImage)} - /> - - ), + isGetMode && getModeResponsiveMainImg ? ( + handleImageClick && handleImageClick(getModeResponsiveMainImg)}> + + 클릭하여 상세보기 + + ) : ( + <> + handleResponsiveMainImageChange(newImage)} + /> + + ), }, { name: 'detailImages', title: '아트워크 서브 이미지', description: '아트워크 서브 이미지는 해당 아트워크 페이지 안에서 보이며, 최소 1개에서 최대 3개까지 지정 가능합니다.', - content: isGetMode && getModeDetailImgs ? ( getModeDetailImgs.map((i, index) => ( - + handleImageClick && handleImageClick(i)}> + + 클릭하여 상세보기 + )) ) : ( handleDetailImageChange(newImage)} type='detail' value={detailImage} - onChange={(newImages: File | File[]) => handleDetailImageChange(newImages)} /> ), }, @@ -268,7 +302,16 @@ export const getArtworkDefaultValue = ( export default getArtworkDefaultValue; const IsGetModeImg = styled.img` - border-radius: 10px; + width: 100%; /* 부모 너비에 맞게 설정 */ + height: 100%; /* 부모 높이에 맞게 설정 */ + object-fit: cover; /* 이미지 비율을 유지하며 꽉 채우기 */ + border-radius: 5px; + transition: opacity 0.3s ease-in-out; + cursor: pointer; + + &:hover { + opacity: 0.7; /* 마우스 호버 시 옅어짐 */ + } `; const IsTopMainArtworkText = styled.h1` @@ -284,13 +327,11 @@ const IsTopMainArtworkContainer = styled.div` cursor: pointer; font-family: 'pretendard-semibold'; background-color: #cacaca88; - height: 40px; - border-radius: 20px; + height: 2rem; + border-radius: 10px; position: relative; - width: 150px; - - h1 { - } + width: 8rem; + font-size: 0.9rem; div { z-index: 1; @@ -302,6 +343,126 @@ const IsTopMainArtworkContainer = styled.div` } `; +const StyledInput = styled.textarea` + width: 100%; + padding: 8px; + height: 8rem; + box-sizing: border-box; + font-family: 'pretendard-medium'; + outline-style: none; + border-radius: 5px; + font-size: 15px; + border: none; + color: black; + margin-bottom: 20px; + background-color: #dadada9f; + resize: none; /* 크기 조정 비활성화 */ + &:hover { + cursor: pointer; + background-color: #ffffff73; + transition: all 300ms ease-in-out; + } + &:focus { + background-color: white; + transition: all 300ms ease-in-out; + } + ::placeholder { + color: #7a7a7a; + } +`; + +const GetInputWrapper = styled.div` + padding: 8px; + box-sizing: border-box; + width: 100%; + border: none; + border-bottom: 1px solid #ccc; + font-size: 0.9rem; + line-height: 140%; + font-family: 'pretendard-regular'; +`; + +const GetHrefContainer = styled.div` + width: 100%; + + border-bottom: 1px solid #ccc; + padding: 8px; +`; +const GetHrefWrapper = styled.a` + text-decoration: none; + border: none; + font-size: 0.9rem; + line-height: 140%; + font-family: 'pretendard-regular'; + word-wrap: break-word; /* 긴 단어를 줄바꿈 */ + overflow-wrap: break-word; /* 긴 단어를 줄바꿈 */ + display: inline-block; + box-sizing: border-box; + width: 100%; +`; +const OverviewInput = styled.textarea` + width: 100%; + height: 'fit-content'; + padding: 8px; + height: 8rem; + font-family: 'pretendard-medium'; + outline-style: none; + box-sizing: border-box; + resize: none; /* 크기 조정 비활성화 */ + border-radius: 5px; + font-size: 15px; + border: none; + background-color: #d1d1d1a0; + color: black; + margin-bottom: 20px; + &:hover { + cursor: pointer; + background-color: #ffffff73; + transition: all 300ms ease-in-out; + } + &:focus { + background-color: white; + transition: all 300ms ease-in-out; + } + ::placeholder { + color: #7a7a7a; + } +`; + +const HoverContainer = styled.div` + position: relative; + width: 100%; + height: 9rem; + overflow: hidden; + cursor: pointer; + border-radius: 10px; + background-color: #0000002b; + margin-bottom: 0.5rem; + + &:hover img { + opacity: 0.7; /* 이미지 호버 시 흐릿하게 */ + } + + &:hover div { + opacity: 1; /* 텍스트 호버 시 보이기 */ + } +`; + +const HoverText = styled.div` + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + font-family: 'pretendard-semibold'; + font-size: 14px; + color: white; + background: rgba(0, 0, 0, 0.6); + padding: 8px 12px; + border-radius: 5px; + opacity: 0; + transition: opacity 0.3s ease-in-out; +`; + const Ispostedcontainer = styled.div.withConfig({ shouldForwardProp: (prop) => prop !== 'isopened', })<{ isopened: string }>` @@ -310,20 +471,21 @@ const Ispostedcontainer = styled.div.withConfig({ cursor: pointer; font-family: 'pretendard-semibold'; background-color: #a3a3a360; - height: 40px; - border-radius: 20px; + height: 2rem; + border-radius: 10px; position: relative; - width: 150px; + width: 8rem; + font-size: 0.9rem; &::before { content: ''; position: absolute; - width: 75px; + width: 4rem; height: 100%; background-color: #fcfcfce2; - border-radius: 20px; + border-radius: 10px; transition: transform 0.3s ease-in-out; - transform: ${(props) => (props.isopened === 'true' ? 'translateX(0)' : 'translateX(75px)')}; + transform: ${(props) => (props.isopened === 'true' ? 'translateX(0)' : 'translateX(4rem)')}; } div { @@ -352,28 +514,29 @@ const TypeContainer = styled.div.withConfig({ align-items: center; cursor: pointer; font-family: 'pretendard-semibold'; + font-size: 0.9rem; background-color: #a3a3a360; - height: 40px; - border-radius: 20px; + height: 2rem; + border-radius: 10px; position: relative; - width: 225px; + width: 12rem; &::before { content: ''; position: absolute; - width: 75px; + width: 4rem; height: 100%; background-color: #fcfcfce2; - border-radius: 20px; + border-radius: 10px; transition: transform 0.3s ease-in-out; transform: ${(props) => { switch (props.projectType) { case 'top': return 'translateX(0)'; case 'main': - return 'translateX(75px)'; + return 'translateX(4rem)'; case 'others': - return 'translateX(150px)'; + return 'translateX(8rem)'; default: return 'translateX(0)'; } @@ -404,78 +567,19 @@ const TypeContainer = styled.div.withConfig({ } } `; - -const StyledInput = styled.input` - width: 90%; - height: 'fit-content'; - padding: 8px; - height: 30px; - font-family: 'pretendard-medium'; - outline-style: none; - border-radius: 5px; - font-size: 15px; - border: none; - color: black; - margin-bottom: 20px; - background-color: #dadada9f; - &:hover { - cursor: pointer; - background-color: #ffffff73; - transition: all 300ms ease-in-out; - } - &:focus { - background-color: white; - transition: all 300ms ease-in-out; - } - ::placeholder { - color: #7a7a7a; - } -`; -const GetInputWrapper = styled.div` - width: 80%; - padding: 8px; - border: none; - border-bottom: 1px solid #ccc; - font-size: 15px; - line-height: 140%; +const CharacterCount = styled.div` + font-size: 0.6rem; font-family: 'pretendard-regular'; + position: absolute; + bottom: 2rem; + right: 0.8rem; + color: #424242; + text-align: right; + background-color: #dadada; + border-radius: 5px; + padding: 0.4rem; `; -const GetHrefContainer = styled.div` - width: 100%; - border-bottom: 1px solid #ccc; - padding: 8px; -`; -const GetHrefWrapper = styled.a` - text-decoration: none; - border: none; - font-size: 17px; - line-height: 140%; - font-family: 'pretendard-regular'; -`; -const OverviewInput = styled.input` - width: 90%; - height: 'fit-content'; - padding: 8px; - height: 30px; - font-family: 'pretendard-medium'; - outline-style: none; - border-radius: 5px; - font-size: 15px; - border: none; - background-color: #d1d1d1a0; - color: black; - margin-bottom: 20px; - &:hover { - cursor: pointer; - background-color: #ffffff73; - transition: all 300ms ease-in-out; - } - &:focus { - background-color: white; - transition: all 300ms ease-in-out; - } - ::placeholder { - color: #7a7a7a; - } +const StyleInputContainer = styled.div` + position: relative; `; diff --git a/src/components/PromotionAdmin/Artwork/ArtworkCreating/ArtworkValueLayout.tsx b/src/components/PromotionAdmin/Artwork/ArtworkCreating/ArtworkValueLayout.tsx index 1d4c17db..75181a64 100644 --- a/src/components/PromotionAdmin/Artwork/ArtworkCreating/ArtworkValueLayout.tsx +++ b/src/components/PromotionAdmin/Artwork/ArtworkCreating/ArtworkValueLayout.tsx @@ -26,7 +26,8 @@ const Container = styled.div` flex-direction: column; justify-content: flex-start; margin-bottom: 15px; - width: 330px; + width: 19rem; + height: fit-content; white-space: pre-line; `; @@ -34,48 +35,24 @@ const ContentWrapper = styled.div` width: 100%; height: fit-content; `; + const Title = styled.div` - font-family: 'pretendard-bold'; - font-size: 17px; + background-color: #525252a0; + padding: 0.7rem; + box-sizing: border-box; + border-radius: 5px; + width: 100%; + font-family: 'pretendard-semibold'; + font-size: 0.86rem; margin-bottom: 10px; - color: #282828; + color: #ffffff; `; const Description = styled.div` font-family: 'pretendard-regular'; width: 100%; - font-size: 13px; + font-size: 0.7rem; color: #595959; - margin-bottom: 10px; + margin-bottom: 0.5rem; line-height: 120%; `; const Content = styled.div``; - -const TextInputWrapper = styled.div` - display: flex; - margin-bottom: 20px; - height: fit-content; - align-items: center; - - textarea { - font-family: 'pretendard-regular'; - font-size: 18px; - height: fit-content; - background: inherit; - border-radius: 5px; - border-style: none; - background-color: #f7f7f7f2; - resize: none; /* 크기 조절 비활성화 */ - display: flex; - align-items: center; - width: 90%; - padding: 10px; - overflow-y: hidden; - &:hover { - background-color: #7e7e7e2c; - transition: all 300ms ease-in-out; - } - &:focus { - outline: none; - } - } -`; diff --git a/src/components/PromotionAdmin/Artwork/ArtworkCreating/ImageUpload.tsx b/src/components/PromotionAdmin/Artwork/ArtworkCreating/ImageUpload.tsx index 8f2b4dc2..54beccaf 100644 --- a/src/components/PromotionAdmin/Artwork/ArtworkCreating/ImageUpload.tsx +++ b/src/components/PromotionAdmin/Artwork/ArtworkCreating/ImageUpload.tsx @@ -8,9 +8,10 @@ interface ImageUploadProps { } const ImageUpload = ({ type, value, onChange }: ImageUploadProps) => { - const [images, setImages] = useState([]); - const [previewURLs, setPreviewURLs] = useState([]); + const [images, setImages] = useState([]); // 현재 이미지 파일 상태 + const [previewURLs, setPreviewURLs] = useState([]); // 프리뷰 URL 상태 + // value prop 변화에 따라 상태 업데이트 useEffect(() => { if (value) { const files = Array.isArray(value) ? value : [value]; @@ -18,43 +19,46 @@ const ImageUpload = ({ type, value, onChange }: ImageUploadProps) => { setImages(files); setPreviewURLs(files.map((file) => URL.createObjectURL(file))); } + } else { + setImages([]); + setPreviewURLs([]); } }, [value]); const handleImageChange = (event: React.ChangeEvent) => { if (event.target.files) { const selectedFiles = Array.from(event.target.files); - const maxFiles = type === 'main'||type==='responsiveMain' ? 1 : 3; + + // 각 타입에 따라 최대 파일 개수 설정 + const maxFiles = type === 'main' || type === 'responsiveMain' ? 1 : 3; const newFiles = selectedFiles.slice(0, maxFiles); - const newPreviewURLs = newFiles.map((file) => URL.createObjectURL(file)); + // 기존 URL 객체 해제 + previewURLs.forEach((url) => URL.revokeObjectURL(url)); - if (type === 'main'||type==='responsiveMain') { - setImages(newFiles); - setPreviewURLs(newPreviewURLs); - onChange(newFiles[0]); - } else { - const updatedFiles = [...images, ...newFiles]; - const updatedPreviewURLs = [...previewURLs, ...newPreviewURLs]; + const uniqueFiles = newFiles; // 새 이미지로만 상태 교체 + const newPreviewURLs = uniqueFiles.map((file) => URL.createObjectURL(file)); - while (updatedFiles.length > maxFiles) { - updatedFiles.shift(); - updatedPreviewURLs.shift(); - } + setImages(uniqueFiles); + setPreviewURLs(newPreviewURLs); - setImages(updatedFiles); - setPreviewURLs(updatedPreviewURLs); - onChange(updatedFiles); - } + // 부모 컴포넌트로 변경된 상태 전달 + onChange(uniqueFiles.length > 1 ? uniqueFiles : uniqueFiles[0]); } }; const handleDeleteImage = (index: number) => { const updatedImages = images.filter((_, i) => i !== index); const updatedPreviewURLs = previewURLs.filter((_, i) => i !== index); + + // URL 객체 해제 + URL.revokeObjectURL(previewURLs[index]); + setImages(updatedImages); setPreviewURLs(updatedPreviewURLs); - onChange(updatedImages); + + // 부모 컴포넌트로 상태 전달 + onChange(updatedImages.length > 1 ? updatedImages : updatedImages[0] || null); }; return ( @@ -74,7 +78,10 @@ const ImageUpload = ({ type, value, onChange }: ImageUploadProps) => { {previewURLs.map((url, index) => ( - + handleDeleteImage(index)}>삭제하기 ))} @@ -92,18 +99,19 @@ const ImageUploadContainer = styled.div` const UploadLabel = styled.label` cursor: pointer; - padding: 10px 20px; + padding: 0.5rem; + margin-left: auto; background-color: #6c757d; color: white; border-radius: 5px; margin-bottom: 10px; transition: all 0.3s ease-in-out; - + font-size: 0.7rem; &:hover { background-color: #5a6268; } - &[aria-disabled="true"] { + &[aria-disabled='true'] { opacity: 0.5; cursor: default; &:hover { @@ -116,14 +124,21 @@ const ImagesPreviewContainer = styled.div` display: flex; flex-wrap: wrap; gap: 10px; - margin-top: 10px; + width: 100%; + min-height: 9rem; + height: 100%; + box-sizing: border-box; + + background-color: #0000000f; + padding: 0.8rem; + border-radius: 5px; `; const ImagePreviewWrapper = styled.div` position: relative; display: inline-block; - max-width: 100%; - max-height: 200px; + width: 100%; + height: 100%; &:hover button { display: block; @@ -131,10 +146,11 @@ const ImagePreviewWrapper = styled.div` `; const ImagePreview = styled.img` - max-width: 100%; - max-height: 200px; + width: 100%; + height: auto; + object-fit: contain; + border-radius: 5px; - margin-top: 5px; `; const DeleteButton = styled.button` @@ -143,6 +159,7 @@ const DeleteButton = styled.button` top: 10px; right: 10px; padding: 5px 10px; + background-color: rgba(0, 0, 0, 0.5); color: white; border: none; diff --git a/src/components/PromotionAdmin/Artwork/ArtworkDefault/Artwork.tsx b/src/components/PromotionAdmin/Artwork/ArtworkDefault/Artwork.tsx index 26829e68..84133475 100644 --- a/src/components/PromotionAdmin/Artwork/ArtworkDefault/Artwork.tsx +++ b/src/components/PromotionAdmin/Artwork/ArtworkDefault/Artwork.tsx @@ -80,8 +80,8 @@ const Artwork = () => {
setSelectedProjectType('all')}>전체
-
setSelectedProjectType('top')}>Top
-
setSelectedProjectType('main')}>Main
+
setSelectedProjectType('top')}>대표
+
setSelectedProjectType('main')}>메인
setProducingIsOpened(!producingIsOpend)}> 아트워크 생성하기 @@ -91,7 +91,7 @@ const Artwork = () => { {filteredAndSortedArtworks.length === 0 ? ( 😊 아트워크 데이터가 존재하지 않습니다. ) : ( -
+
{currentArtworks.map((artwork) => ( prop !== 'projectType', })<{ projectType: projectType | 'all' }>` display: flex; + font-size: 0.8rem; align-items: center; cursor: pointer; font-family: 'pretendard-semibold'; background-color: #a3a3a360; - height: 40px; - border-radius: 20px; + height: 2rem; + border-radius: 10px; position: relative; - width: 225px; + width: 12rem; &::before { content: ''; position: absolute; - width: 75px; + width: 4rem; height: 100%; background-color: #fcfcfce2; - border-radius: 20px; + border-radius: 10px; transition: transform 0.3s ease-in-out; transform: ${(props) => { switch (props.projectType) { case 'all': return 'translateX(0)'; case 'top': - return 'translateX(75px)'; + return 'translateX(4rem)'; case 'main': - return 'translateX(150px)'; + return 'translateX(8rem)'; default: return 'translateX(0)'; } diff --git a/src/components/PromotionAdmin/Artwork/ArtworkDefault/ArtworkBox.tsx b/src/components/PromotionAdmin/Artwork/ArtworkDefault/ArtworkBox.tsx index 2f156ab7..888d8235 100644 --- a/src/components/PromotionAdmin/Artwork/ArtworkDefault/ArtworkBox.tsx +++ b/src/components/PromotionAdmin/Artwork/ArtworkDefault/ArtworkBox.tsx @@ -19,12 +19,12 @@ const ArtworkBox = ({ projectImages, sequence, }: ArtworkData) => { - const slicedName = name.length > 25 ? `${name.slice(0, 25)}...` : name; - const slicedOverview = overView.length > 120 ? `${overView.slice(0, 120)}...` : overView; - const slicedClient = client.length > 30 ? `${client.slice(0, 30)}...` : client; + const slicedName = name.length > 15 ? `${name.slice(0, 15)}...` : name; + const slicedOverview = overView.length > 80 ? `${overView.slice(0, 80)}...` : overView; + const slicedClient = client.length > 20 ? `${client.slice(0, 20)}...` : client; return ( - {mainImg ? mainImg : No Image} + mainImg
@@ -32,11 +32,17 @@ const ArtworkBox = ({

{slicedName}

- {isPosted ? : } + {isPosted ? : }

{category}

-

{slicedOverview}

{projectType} + +

{slicedOverview}

+
+ + + {projectType === 'others' ? '기본 아트워크' : projectType === 'top' ? '대표 아트워크' : '메인 아트워크'} +
); @@ -48,38 +54,38 @@ const Container = styled.div` display: flex; align-items: center; justify-content: space-between; - width: 100%; + width: 35rem; border-radius: 10px; - background-color: #afafaf13; - padding: 20px; + background-color: #00000009; + padding: 1rem; box-sizing: border-box; margin-bottom: 10px; &:hover { cursor: pointer; - opacity: 0.8; + opacity: 0.7; transition: all ease-in-out 300ms; } img { - width: 130px; - height: 130px; + width: 9.4rem; + height: 9.4rem; object-fit: cover; border-radius: 5px; } h1 { font-family: 'pretendard-semibold'; - font-size: 18px; + font-size: 1.1rem; color: black; margin-top: 3px; } h2 { font-family: 'pretendard-medium'; - font-size: 15px; + font-size: 0.8rem; color: #707070; } h3 { font-family: 'pretendard-medium'; - font-size: 15px; + font-size: 0.9rem; color: #4b4b4b; line-height: 18px; } @@ -89,22 +95,15 @@ const Wrapper = styled.div` display: flex; align-items: center; justify-content: space-between; - margin-bottom: 23px; -`; - -const NoMainImageWrapper = styled.div` - width: 180px; - height: 180px; - font-family: 'pretendard-medium'; - font-size: 15px; - color: #ffffff; + margin-bottom: 0.8rem; `; const DescriptionWrapper = styled.div` display: flex; flex-direction: column; - justify-content: flex-start; - width: 500px; + justify-content: space-between; + width: 100%; + height: 100%; margin-left: 23px; `; const RightAlignWrapper = styled.div` @@ -114,6 +113,10 @@ const RightAlignWrapper = styled.div` } `; +const OverviewWrapper = styled.div` + height: 5rem; +`; + const TypeWrapper = styled.div.withConfig({ shouldForwardProp: (prop) => prop !== 'projectType', })<{ projectType: 'others' | 'top' | 'main' }>` @@ -123,10 +126,10 @@ const TypeWrapper = styled.div.withConfig({ border-radius: 10px; background-color: ${({ projectType }) => projectType === 'main' ? '#ffaa007d' : projectType === 'top' ? '#d3002384' : '#33333321'}; - margin-bottom: 5px; + margin-left: auto; - margin-top: 15px; + font-family: 'pretendard-medium'; - font-size: 15px; + font-size: 0.8rem; color: ${({ projectType }) => (projectType === 'main' || projectType === 'top' ? 'white' : 'black')}; `; diff --git a/src/components/PromotionAdmin/Artwork/ArtworkDefault/ArtworkDetail.tsx b/src/components/PromotionAdmin/Artwork/ArtworkDefault/ArtworkDetail.tsx index 7a0d2761..dca865c0 100644 --- a/src/components/PromotionAdmin/Artwork/ArtworkDefault/ArtworkDetail.tsx +++ b/src/components/PromotionAdmin/Artwork/ArtworkDefault/ArtworkDetail.tsx @@ -10,8 +10,13 @@ import { PA_ROUTES } from '@/constants/routerConstants'; import { linkCheck } from '@/components/ValidationRegEx/ValidationRegEx'; import { useUnsavedChangesWarning } from '@/hooks/useUnsavedChangesWarning'; import { MSG } from '@/constants/messages'; +import BackDrop from '@/components/Backdrop/Backdrop'; +import ArtworkImgView from './ArtworkImgView'; +import { urlToFile } from '@/utils/urlToFile'; const ArtworkDetail = () => { + const [isModalOpen, setIsModalOpen] = useState(false); + const [modalImgSrc, setModalImgSrc] = useState(''); const [getModeMainImg, setGetModeMainImg] = useState(''); const [getModeResponsiveMainImg, setGetModeResponsiveMainImg] = useState(''); const [getModeDetailImgs, setGetModeDetailImgs] = useState([]); @@ -20,14 +25,13 @@ const ArtworkDetail = () => { const [isProjectOpened, setIsProjectOpened] = useState(false); const [projectType, setProjectType] = useState('others'); const [link, setLink] = useState(''); - const [mainImage, setMainImage] = useState(null); - const [responsiveMainImage, setResponsiveMainImage]=useState(null); + const [mainImage, setMainImage] = useState(null); + const [responsiveMainImage, setResponsiveMainImage] = useState(null); const [detailImages, setDetailImages] = useState([]); const [title, setTitle] = useState(''); const [customer, setCustomer] = useState(''); const [overview, setOverview] = useState(''); const [submitButtonDisabled, setSubmitButtonDisabled] = useState(true); - const [errorMessage, setErrorMessage] = useState(''); const { artworkId } = useParams(); const [artworkData, setArtworkData] = useState(); const [isGetMode, setIsGetMode] = useState(true); @@ -35,36 +39,20 @@ const ArtworkDetail = () => { const [linkRegexMessage, setLinkRegexMessage] = useState(''); const [isTopMainArtwork, setIsTopMainArtwork] = useState(false); useUnsavedChangesWarning(MSG.CONFIRM_MSG.EXIT, !isGetMode); - const [putData, setPutData] = useState({ - request: { - projectId: 0, - department: '', - category: '', - name: '', - client: '', - date: '', - link: '', - overView: '', - deletedImageId: [], - }, - file: '', - responsiveFile: '', - files: [], - }); useEffect(() => { setSubmitButtonDisabled( !selectedDate || - selectedCategory === '' || - projectType === null || - link === '' || - !mainImage || - !responsiveMainImage || - !detailImages || - detailImages.length === 0 || - title === '' || - customer === '' || - overview === '', + selectedCategory === '' || + projectType === null || + link === '' || + !mainImage || + !responsiveMainImage || + !detailImages || + detailImages.length === 0 || + title === '' || + customer === '' || + overview === '', ); }, [ selectedDate, @@ -79,28 +67,19 @@ const ArtworkDetail = () => { customer, overview, ]); + useEffect(() => { fetchArtworkDetails(); setIsGetMode(true); }, [artworkId]); + useEffect(() => { - setErrorMessage(''); if (projectType === 'top' || projectType === 'main') { setIsTopMainArtwork(true); } else { setIsTopMainArtwork(false); } }, [projectType]); - async function urlToFile(url: string, fileName: string): Promise { - try { - const response = await fetch(url); - const blob = await response.blob(); - return new File([blob], fileName); - } catch (error) { - console.error('Error URL to file:', error); - throw error; - } - } // fetchArtworkDetails 함수 내에서 ArtworkData를 받아와서 state에 설정하는 부분 const fetchArtworkDetails = async () => { @@ -113,76 +92,54 @@ const ArtworkDetail = () => { setIsProjectOpened(data.isPosted); setProjectType(data.projectType); setLink(data.link); - setPutData({ - request: { - projectId: data.id, - department: '', - category: data.category, - name: data.name, - client: data.client, - date: data.date, - link: data.link, - overView: data.overView, - deletedImageId: [], - }, - file: data.mainImg, - responsiveFile: data.responsiveMainImg, - files: [], - }); + setCustomer(data.client); + setOverview(data.overView); + if (data.projectType === 'top' || data.projectType === 'main') { setIsTopMainArtwork(true); } else { setIsTopMainArtwork(false); } + if (data.mainImg) { - setGetModeMainImg(data.mainImg); try { - const mainImgFile = await urlToFile(data.mainImg + '?t=' + Date.now(), `${data.mainimg}.png`); + const mainImgFile = await urlToFile(data.mainImg); setMainImage(mainImgFile); + setGetModeMainImg(data.mainImg); } catch (error) { - console.error('Error fetching artwork details:', error); + console.error('[ArtworkDetail Failed to load main image]', error); + setMainImage(null); + setGetModeMainImg(''); } - }else{ - setGetModeMainImg(''); - setMainImage(null); } - if(data.responsiveMainImg){ - setGetModeResponsiveMainImg(data.responsiveMainImg); + + if (data.responsiveMainImg) { try { - const responsiveMainImgFile = await urlToFile(data.responsiveMainImg + '?t=' + Date.now(), `${data.responsiveMainImg}.png`); - setResponsiveMainImage(responsiveMainImgFile); + const responsiveImgFile = await urlToFile(data.responsiveMainImg); + setResponsiveMainImage(responsiveImgFile); + setGetModeResponsiveMainImg(data.responsiveMainImg); } catch (error) { - console.error('Error fetching artwork details:', error); + console.error('[ArtworkDetail Failed to load responsive main image]', error); + setResponsiveMainImage(null); + setGetModeResponsiveMainImg(''); } - }else{ - setGetModeResponsiveMainImg(''); - setResponsiveMainImage(null); } + if (data.projectImages && data.projectImages.length > 0) { try { const detailImageFiles = await Promise.all( - data.projectImages.map(async (image: { imageUrlList: string }) => { - const detailImgFile = await urlToFile(image.imageUrlList, `${image.imageUrlList}.png`); - return detailImgFile; - }), + data.projectImages.map(async (image: { imageUrlList: string }) => urlToFile(image.imageUrlList)), ); setDetailImages(detailImageFiles); - setPutData((prevState) => ({ - ...prevState, - files: detailImageFiles, - })); + setGetModeDetailImgs(data.projectImages.map((image: { imageUrlList: string }) => image.imageUrlList)); } catch (error) { - console.error('Error fetching artwork details:', error); + console.error('[ArtworkDetail Failed to load detail images]', error); + setDetailImages([]); + setGetModeDetailImgs([]); } - setGetModeDetailImgs(data.projectImages.map((image: { imageUrlList: string }) => image.imageUrlList)); - }else{ - setGetModeDetailImgs([]); - setDetailImages([]); } - setCustomer(data.client); - setOverview(data.overView); } catch (error) { - console.error('Error fetching artwork details', error); + console.error('[Error fetching artwork details]', error); } }; @@ -213,16 +170,23 @@ const ArtworkDetail = () => { } }; + // 메인 이미지 상태 변경 const handleMainImageChange = (newImage: File | File[]) => { - setMainImage(Array.isArray(newImage) ? newImage[0] : newImage); + const updatedImage = Array.isArray(newImage) ? newImage[0] : newImage; + setMainImage(updatedImage); // 새 이미지로 상태 교체 }; - const handleResponsiveMainImageChange=(newImage:File|File[])=>{ - setResponsiveMainImage(Array.isArray(newImage) ? newImage[0] : newImage); + // 반응형 메인 이미지 상태 변경 + const handleResponsiveMainImageChange = (newImage: File | File[]) => { + const updatedImage = Array.isArray(newImage) ? newImage[0] : newImage; + setResponsiveMainImage(updatedImage); // 새 이미지로 상태 교체 }; + // 상세 이미지 상태 변경 const handleDetailImageChange = (newImages: File | File[]) => { - setDetailImages(Array.isArray(newImages) ? newImages : [newImages]); + const updatedImages = Array.isArray(newImages) ? newImages : [newImages]; + const truncatedImages = updatedImages.slice(0, 3); // 최대 3개 제한 + setDetailImages(truncatedImages); // 새 이미지로 상태 교체 }; const handleTitleChange = (newTitle: string) => { @@ -252,29 +216,31 @@ const ArtworkDetail = () => { if (mainImage) { formData.append('file', mainImage); } - if (responsiveMainImage){ - formData.append('responsiveFile',responsiveMainImage); + if (responsiveMainImage) { + formData.append('responsiveFile', responsiveMainImage); } if (detailImages) { detailImages.forEach((file, index) => { formData.append('files', file); }); } + const formDataEntries = Array.from(formData.entries()); + formDataEntries.forEach(([key, value]) => { + console.log(`${key}:`, value); + }); try { const response = await putArtwork(formData); if (response.code === 400 && response.data === null && response.message) { - setErrorMessage(response.message); return; } alert(MSG.ALERT_MSG.SAVE); await fetchArtworkDetails(); setIsGetMode(true); - setErrorMessage(''); window.scrollTo({ top: 0, behavior: 'smooth' }); } catch (error: any) { - console.log('Error creating artwork:', error); + console.log('[artwork updating error]', error); } }; @@ -288,11 +254,21 @@ const ArtworkDetail = () => { navigate(`${PA_ROUTES.ARTWORK}`); } catch (error) { alert(MSG.CONFIRM_MSG.FAILED); - console.error('Error deleting artwork:', error); - // 삭제 실패 시 처리 + console.error('Error deleting requestData:', detailImages); } } }; + + const handleImageClick = (src: string) => { + setModalImgSrc(src); + setIsModalOpen(true); + }; + + const closeModal = () => { + setIsModalOpen(false); + setModalImgSrc(''); + }; + const defaultValue = getArtworkDefaultValue( selectedDate, handleDateChange, @@ -317,6 +293,7 @@ const ArtworkDetail = () => { overview, handleOverviewChange, isTopMainArtwork, + handleImageClick, getModeMainImg, getModeResponsiveMainImg, getModeDetailImgs, @@ -324,41 +301,52 @@ const ArtworkDetail = () => { ); return ( - - - - {defaultValue.map((item: DefaultValueItem, index: number) => ( - item.name==='responsiveMainImage'?null: - item.name === 'mainImage' && defaultValue[index + 1]?.name === 'responsiveMainImage'? + <> + {isModalOpen && ( + } isOpen={isModalOpen} /> + )} + + + + {defaultValue.map((item: DefaultValueItem, index: number) => + item.name === 'responsiveMainImage' ? null : item.name === 'mainImage' && + defaultValue[index + 1]?.name === 'responsiveMainImage' ? (
- {errorMessage && ⚠ {errorMessage}} - - + +
- : -
- {errorMessage && !isGetMode && item.name === 'artworkType' && ( - ⚠ {errorMessage} - )}{' '} - {linkRegexMessage && item.name === 'link' && ⚠ {linkRegexMessage}} - -
- ))} -
+ ) : ( +
+ {linkRegexMessage && item.name === 'link' && ⚠ {linkRegexMessage}} + +
+ ), + )} + + + {!isGetMode && ( handleSubmit()} > 저장하기 )} - {isGetMode && 수정하기} - {' '} - 삭제하기 - + {isGetMode && ( + + 수정하기 + + )}{' '} + 삭제하기 + {' '} + ); }; @@ -367,27 +355,27 @@ export default ArtworkDetail; const Container = styled.div` display: flex; flex-direction: column; - width: 100%; + width: 44rem; position: relative; `; const ValueWrapper = styled.div` - background-color: rgba(255, 255, 255, 0.336); + background-color: #00000009; border-radius: 10px; backdrop-filter: blur(7px); box-sizing: border-box; width: 100%; - padding: 55px 55px; + padding: 2rem; display: grid; grid-template-columns: repeat(2, 1fr); - gap: 30px; + gap: 0.5rem; /* 박스 간 간격 */ `; const SubmitBtn = styled.button` border: none; outline-style: none; font-family: 'pretendard-semibold'; - font-size: 17px; + font-size: 1rem; background-color: #6c757d; width: 150px; text-align: center; @@ -395,7 +383,7 @@ const SubmitBtn = styled.button` border-radius: 5px; transition: all 0.3s ease-in-out; cursor: pointer; - padding: 10px 20px; + padding: 0.7rem; margin-left: auto; margin-top: 20px; &:disabled { @@ -407,6 +395,8 @@ const SubmitBtn = styled.button` } &:hover { background-color: #5a6268; + cursor: pointer; + transition: all 300ms ease-in-out; } `; @@ -421,23 +411,36 @@ const ErrorMessage = styled.div` margin-bottom: 15px; `; -const DeleteWrapper = styled.div` +const DeleteWrapper = styled.button` + border: none; + outline-style: none; font-family: 'pretendard-semibold'; - font-size: 17px; - background-color: #c0c0c0; - text-align: center; + font-size: 1rem; + background-color: #6c757d; width: 150px; text-align: center; color: white; border-radius: 5px; transition: all 0.3s ease-in-out; cursor: pointer; - padding: 10px 20px; + padding: 0.7rem; margin-left: auto; - margin-right: 20px; margin-top: 20px; + &:disabled { + opacity: 0.5; + cursor: default; + &:hover { + background-color: #6c757d; + } + } &:hover { + cursor: pointer; + transition: all 300ms ease-in-out; background-color: #ca0505c5; } `; + +const ButtonContainer = styled.div` + text-align: right; +`; diff --git a/src/components/PromotionAdmin/Artwork/ArtworkDefault/ArtworkImgView.tsx b/src/components/PromotionAdmin/Artwork/ArtworkDefault/ArtworkImgView.tsx new file mode 100644 index 00000000..c59374a3 --- /dev/null +++ b/src/components/PromotionAdmin/Artwork/ArtworkDefault/ArtworkImgView.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import styled from 'styled-components'; + +const Modal = ({ src, closeModal }: { src: string; closeModal: () => void }) => { + return ( + + + 확대 이미지 + + 닫기 + + ); +}; + +export default Modal; + +const Container = styled.div` + padding: 1rem; + height: auto; + width: 30rem; + display: flex; + flex-direction: column; + justify-content: center; +`; +const ModalContent = styled.div` + max-width: 100%; + max-height: 100%; + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 1rem; + img { + object-fit: contain; + max-width: 100%; + max-height: 100%; + border-radius: 10px; + } +`; + +const CloseButton = styled.div` + font-size: 0.8rem; + cursor: pointer; + width: fit-content; + font-family: 'pretendard-semibold'; + padding: 10px 20px; + background-color: #6c757d; + color: white; + border-radius: 5px; + margin-left: auto; + &:hover { + background-color: #5a6268; + } +`; diff --git a/src/components/PromotionAdmin/Artwork/ArtworkDefault/ArtworkInput.tsx b/src/components/PromotionAdmin/Artwork/ArtworkDefault/ArtworkInput.tsx deleted file mode 100644 index 89856e5f..00000000 --- a/src/components/PromotionAdmin/Artwork/ArtworkDefault/ArtworkInput.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import React, { ChangeEvent, useState } from 'react'; -import styled from 'styled-components'; - -type Props = { - label: string; - name?: string; - value?: string; - onChange?: (e: React.ChangeEvent) => void; - onTextAreaChange?: (e: ChangeEvent) => void; - isEditMode: boolean; - isFile: boolean; - mainFile?: string; -}; - -const ArtworkInput = ({ label, name, value, onChange, onTextAreaChange, isEditMode, isFile, mainFile }: Props) => { - const [previewUrl, setPreviewUrl] = useState(mainFile || value); - - const handleFileChange = (e: ChangeEvent) => { - const file = e.target.files && e.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onload = () => { - setPreviewUrl(reader.result as string); - }; - reader.readAsDataURL(file); - } - if (onChange) { - onChange(e); - } - }; - return ( - - {isFile ? ( - <> - {label} - Main Images는 최대 한 개만 지정이 가능합니다. - {isEditMode ? ( - <> - - 이미지 업로드 - - - - - ) : ( - mainFile && - )} - - ) : ( - - {label} - {isEditMode ? ( -