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 ? : No Image }
+
@@ -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 ? (
-
- ) : (
- {value}
- )}
-
- )}
-
- );
-};
-export default ArtworkInput;
-
-const Container = styled.div`
- input[type='file'] {
- display: none;
- }
-`;
-
-const FileLabel = styled.label`
- background: inherit;
- border-style: none;
- border-radius: 5px;
- height: 30px;
- padding: 5px 10px;
-
- cursor: pointer;
- font-family: 'pretendard-regular';
- font-size: 15px;
- background-image: none;
- color: #fff;
- background-color: #2b2b2b;
-
- &:hover {
- background-color: #2b2b2b71;
- transition: all 300ms ease-in-out;
- }
-`;
-const FileWrapper = styled.div`
- margin-bottom: 15px;
-`;
-const FileInput = styled.input`
- display: none;
-`;
-const LabelWrapper = styled.div`
- font-family: 'pretendard-bold';
- font-size: 20px;
- margin-bottom: 15px;
-`;
-
-const MainImg = styled.img`
- width: 100%;
- height: 280px;
- object-fit: contain;
- margin-top: 20px;
- margin-bottom: 20px;
-`;
-
-const FileDes = styled.div`
- font-family: 'pretendard-light';
- font-size: 15px;
- opacity: 0.8;
- margin-bottom: 15px;
-`;
-
-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;
- }
- }
-`;
-const TextInputLabelWrapper = styled.div`
- font-family: 'pretendard-bold';
- font-size: 20px;
- width: 100px;
-`;
-
-const DetailWrapper = styled.div`
- font-family: 'pretendard-regular';
- height: fit-content;
-`;
diff --git a/src/components/PromotionAdmin/Artwork/ArtworkHeader.tsx b/src/components/PromotionAdmin/Artwork/ArtworkHeader.tsx
index e686c744..189ae90d 100644
--- a/src/components/PromotionAdmin/Artwork/ArtworkHeader.tsx
+++ b/src/components/PromotionAdmin/Artwork/ArtworkHeader.tsx
@@ -4,11 +4,10 @@ import { useEffect, useState } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import styled from 'styled-components';
-const ArtworkHeader = ({ initialCheck, control }
- : { initialCheck: number; control: (isEditing: number) => void }) => {
+const ArtworkHeader = ({ initialCheck, control }: { initialCheck: number; control: (isEditing: number) => void }) => {
const [isChecked, setIsChecked] = useState(initialCheck);
- const [moveChecked, setMoveChecked]=useState(false);
- const isUpdate=useRecoilValue(dataUpdateState);//sequence 변경 있는지 확인
+ const [moveChecked, setMoveChecked] = useState(false);
+ const isUpdate = useRecoilValue(dataUpdateState); //sequence 변경 있는지 확인
const setupdate = useSetRecoilState(dataUpdateState);
useEffect(() => {
//초기값 변경될 때 상태 업데이트
@@ -16,29 +15,56 @@ const ArtworkHeader = ({ initialCheck, control }
}, [initialCheck]);
return (
- {
- if(isUpdate){
- if(window.confirm("현재 페이지를 나가면 변경 사항이 저장되지 않습니다.\n나가시겠습니까?")){
- setMoveChecked(false)
- setupdate(false)
- }else{
- setMoveChecked(true)
- setupdate(true)
+ {
+ if (isUpdate) {
+ if (window.confirm('현재 페이지를 나가면 변경 사항이 저장되지 않습니다.\n나가시겠습니까?')) {
+ setMoveChecked(false);
+ setupdate(false);
+ } else {
+ setMoveChecked(true);
+ setupdate(true);
+ }
}
- }}}>
+ }}
+ >
-
{control(0)}} defaultChecked={isChecked === 0}
- disabled={moveChecked}/>
+
{
+ control(0);
+ }}
+ defaultChecked={isChecked === 0}
+ disabled={moveChecked}
+ />
아트워크 관리
-
{control(1)}} defaultChecked={isChecked === 1}
- disabled={moveChecked}/>
+
{
+ control(1);
+ }}
+ defaultChecked={isChecked === 1}
+ disabled={moveChecked}
+ />
메인 순서 관리
-
{control(2)}} defaultChecked={isChecked === 2}
- disabled={moveChecked}/>
+
{
+ control(2);
+ }}
+ defaultChecked={isChecked === 2}
+ disabled={moveChecked}
+ />
전체 순서 관리
@@ -57,7 +83,7 @@ const HeaderWrapper = styled.div`
margin-bottom: 21px;
.tabs {
- width: 450px;
+ width: 24rem;
display: flex;
flex-direction: row;
// position: relative;
@@ -77,8 +103,8 @@ const HeaderWrapper = styled.div`
align-items: center;
justify-content: center;
height: 50px;
- width: 150px;
- font-size: 1.2rem;
+ width: 8rem;
+ font-size: 1rem;
border-radius: 99px; // just a high number to create pill effect
cursor: pointer;
transition: color 0.15s ease-in;
@@ -118,7 +144,7 @@ const HeaderWrapper = styled.div`
position: absolute;
display: flex;
height: 50px;
- width: 150px;
+ width: 8rem;
background-color: ${theme.color.yellow.light};
z-index: 1;
border-radius: 99px;
diff --git a/src/components/PromotionAdmin/Artwork/CategoryDropDown.tsx b/src/components/PromotionAdmin/Artwork/CategoryDropDown.tsx
index b66db1ce..9203d499 100644
--- a/src/components/PromotionAdmin/Artwork/CategoryDropDown.tsx
+++ b/src/components/PromotionAdmin/Artwork/CategoryDropDown.tsx
@@ -39,13 +39,14 @@ export default CategoryDropDown;
const Wrapper = styled.div`
position: relative;
- height: 30px;
- width: 90%;
+ box-sizing: border-box;
+ width: 100%;
+ padding: 0.7rem;
text-align: center;
border-radius: 5px;
z-index: 15;
background-color: #dadada9f;
- padding: 8px;
+
&:hover {
cursor: pointer;
background-color: #ffffff73;
@@ -83,7 +84,7 @@ const Dropdown = styled.div`
width: 100%;
border-top: none;
border-radius: 0 0 5px 5px;
- background-color: #ffffffa6;
+ background-color: #ffffff75;
backdrop-filter: blur(5px);
`;
diff --git a/src/components/PromotionAdmin/Header/index.tsx b/src/components/PromotionAdmin/Header/index.tsx
index 39d4bd99..791bb07d 100644
--- a/src/components/PromotionAdmin/Header/index.tsx
+++ b/src/components/PromotionAdmin/Header/index.tsx
@@ -40,7 +40,6 @@ const Index = () => {
},
{
enabled: !!auth.userId,
- refetchInterval: 60000, // 1분마다 새로고침
onSuccess: (data) => {
if (data.length === 0) {
setIconStatus(false);
@@ -51,7 +50,6 @@ const Index = () => {
const unreadNotificationsExist = data.some((notification) => !notification.isRead);
setIconStatus(unreadNotificationsExist);
- // 알림 정렬
const sorted = data.sort((a, b) => Number(b.isRead) - Number(a.isRead));
setSortedNotifications(sorted);
@@ -255,7 +253,7 @@ const NotiContainer = styled.div`
h1 {
color: #595959;
font-family: 'pretendard-semibold';
- font-size: 1.6rem;
+ font-size: 1.4rem;
margin-bottom: 15px;
}
`;
@@ -267,5 +265,6 @@ const TextContainer = styled.div`
font-family: 'pretendard-regular';
font-size: 1rem;
color: #aaa;
+ margin-bottom: 1rem;
text-align: left;
`;
diff --git a/src/components/PromotionAdmin/Home/Graph/Graph.tsx b/src/components/PromotionAdmin/Home/Graph/Graph.tsx
index 11b7faac..8412d1fb 100644
--- a/src/components/PromotionAdmin/Home/Graph/Graph.tsx
+++ b/src/components/PromotionAdmin/Home/Graph/Graph.tsx
@@ -11,8 +11,8 @@ type Props = {
title: string;
processedData: { x: string; y: number }[];
data: ViewData[] | RequestData[];
- handleCategoryChange: (category:string)=>void;
- handleStateChange: (state:string)=>void;
+ handleCategoryChange: (category: string) => void;
+ handleStateChange: (state: string) => void;
handleStartDateChange: (newStartDate: dayjs.Dayjs | null) => void;
handleEndDateChange: (newEndDate: dayjs.Dayjs | null) => void;
category: string;
@@ -24,10 +24,10 @@ type Props = {
filter2: Option[];
};
-type Option={
- value:string;
- label:string;
-}
+type Option = {
+ value: string;
+ label: string;
+};
const Graph = ({
title,
@@ -45,14 +45,14 @@ const Graph = ({
filter,
filter2,
}: Props) => {
- const [showFilter2, setShowFilter2] = useState(false);
- useEffect(()=>{
- if(division==='request'||category===MenuType.ARTWORK){
+ const [showFilter2, setShowFilter2] = useState(false);
+ useEffect(() => {
+ if (division === 'request' || category === MenuType.ARTWORK) {
setShowFilter2(true);
- }else{
+ } else {
setShowFilter2(false);
}
- },[category])
+ }, [category]);
return (
@@ -70,17 +70,40 @@ const Graph = ({
/>
-
-
handleCategoryChange(e.target.value)}>
- {filter&&filter.map((option)=>{
- return {option.label}
- })}
+
+ handleCategoryChange(e.target.value)}
+ >
+ {filter &&
+ filter.map((option, index) => {
+ return (
+
+ {option.label}
+
+ );
+ })}
- handleStateChange(e.target.value)} disabled={showFilter2?false:true}>
- {filter2&&filter2.map((option,index)=>{
- return {option.label}
- })}
+ handleStateChange(e.target.value)}
+ disabled={showFilter2 ? false : true}
+ >
+ {filter2 &&
+ filter2.map((option, index) => {
+ return (
+
+ {option.label}
+
+ );
+ })}
@@ -159,7 +182,7 @@ const ErrorWrapper = styled.div`
}
`;
-const FilterSelect=styled.select`
+const FilterSelect = styled.select`
min-width: fit-content;
height: fit-content;
backdrop-filter: blur(4px);
@@ -168,8 +191,8 @@ const FilterSelect=styled.select`
padding: 4px;
font-size: 0.9rem;
font-family: 'pretendard';
-`
-const FilterOption=styled.option`
-font-size: 0.9rem;
-font-family: pretendard;
-`
\ No newline at end of file
+`;
+const FilterOption = styled.option`
+ font-size: 0.9rem;
+ font-family: pretendard;
+`;
diff --git a/src/components/PromotionAdmin/Home/Graph/LineGraph.tsx b/src/components/PromotionAdmin/Home/Graph/LineGraph.tsx
index 7846db52..646a7a2c 100644
--- a/src/components/PromotionAdmin/Home/Graph/LineGraph.tsx
+++ b/src/components/PromotionAdmin/Home/Graph/LineGraph.tsx
@@ -9,10 +9,10 @@ type LineGraphProps = {
const LineGraph = ({ data, division }: LineGraphProps) => {
const colors = division === 'request' ? ['#0064FF'] : ['#E16262'];
- const yValues = data.map(d => d.y);
+ const yValues = data.map((d) => d.y);
const maxY = Math.max(...yValues); // 최대값 계산
const numberOfTicks = 5; // 원하는 tick 개수
- let tickValues:number[]=[];
+ let tickValues: number[] = [];
// yValues가 비어 있지 않은 경우에만 tickValues를 계산
if (maxY > 0) {
const interval = maxY / (numberOfTicks - 1); // 간격 계산
@@ -47,14 +47,6 @@ const LineGraph = ({ data, division }: LineGraphProps) => {
legend: '',
legendOffset: 36,
legendPosition: 'middle',
- truncateTickAt: 0,
- format: (value) => {
- const parts = value.split(' ');
- if (parts.length === 2) {
- return parts[1];
- }
- return value;
- },
}}
axisLeft={{
tickSize: 5,
@@ -63,40 +55,71 @@ const LineGraph = ({ data, division }: LineGraphProps) => {
legend: '',
legendOffset: -40,
legendPosition: 'middle',
- truncateTickAt: 0,
tickValues: tickValues,
- format: (value) => Math.round(value).toString(),
}}
+ enableGridX={true}
+ enableGridY={true}
+ animate={true}
enablePoints={true}
pointSize={10}
pointColor={{ theme: 'background' }}
pointBorderWidth={2}
pointBorderColor={{ from: 'serieColor' }}
+ pointLabel={(d) => `${d.y}`}
pointLabelYOffset={-12}
+ enablePointLabel={false} // 포인트 레이블 비활성화
enableArea={true}
- enableTouchCrosshair={true}
+ areaOpacity={0.3}
+ areaBaselineValue={0}
+ areaBlendMode='normal'
+ lineWidth={2}
+ legends={[]} // 범례 비활성화
+ isInteractive={true}
+ debugMesh={false}
+ enableSlices='x' // 슬라이스 활성화
+ debugSlices={false}
+ enableCrosshair={true}
+ crosshairType='bottom-left'
+ role='application'
+ defs={[]} // 그래프 패턴 정의
+ fill={[]} // 채우기 스타일 정의
useMesh={true}
- tooltip={(tooltip) => {
- return (
-
-
- {tooltip.point.data.xFormatted}
-
-
- {division === 'request' ? '문의 수' : '조회 수'} {Math.round(Number(tooltip.point.data.y))}
+ layers={['grid', 'markers', 'axes', 'areas', 'lines', 'points', 'slices', 'mesh', 'legends']} // 필수 추가
+ sliceTooltip={({ slice }) => (
+
+ {slice.points.map((point) => (
+
+ {point.data.xFormatted} : {point.data.yFormatted}
+ ))}
+
+ )}
+ tooltip={(tooltip) => (
+
+
+ {tooltip.point.data.xFormatted}
- );
- }}
+
+ {division === 'request' ? '문의 수' : '조회 수'} {Math.round(Number(tooltip.point.data.y))}
+
+
+ )}
/>
);
diff --git a/src/components/PromotionAdmin/Home/RequestSummary/RequestSummary.tsx b/src/components/PromotionAdmin/Home/RequestSummary/RequestSummary.tsx
index 4bf519c8..3dc38dff 100644
--- a/src/components/PromotionAdmin/Home/RequestSummary/RequestSummary.tsx
+++ b/src/components/PromotionAdmin/Home/RequestSummary/RequestSummary.tsx
@@ -22,7 +22,7 @@ const WatingRequests = () => {
const handleSort = () => {
setSortByRecent((prev) => !prev);
};
- console.log(data);
+
return (
@@ -34,7 +34,7 @@ const WatingRequests = () => {
승인 대기 의뢰 총 {data && data.length > 0 ? data.length : 0}건
-
+
{sortByRecent ? '최신순' : '오래된 순'}
@@ -131,7 +131,9 @@ const LoadingWrapper = styled.div`
font-size: 17px;
`;
-const SortWrapper = styled.button<{ rotate: boolean }>`
+const SortWrapper = styled.button.withConfig({
+ shouldForwardProp: (prop) => prop !== 'rotate',
+})<{ rotate: boolean }>`
border-style: none;
background: inherit;
display: flex;
diff --git a/src/components/PromotionAdmin/Home/RequestSummary/WaitingRequestsList.tsx b/src/components/PromotionAdmin/Home/RequestSummary/WaitingRequestsList.tsx
index 8c32f403..2391753b 100644
--- a/src/components/PromotionAdmin/Home/RequestSummary/WaitingRequestsList.tsx
+++ b/src/components/PromotionAdmin/Home/RequestSummary/WaitingRequestsList.tsx
@@ -14,7 +14,16 @@ type Props = {
hoverBackgroundColor: string;
};
-const WaitingRequestsList = ({ requestId, organization, clientName, description, category, date, email, hoverBackgroundColor }: Props) => {
+const WaitingRequestsList = ({
+ requestId,
+ organization,
+ clientName,
+ description,
+ category,
+ date,
+ email,
+ hoverBackgroundColor,
+}: Props) => {
const limitedOrganization = organization.length > 10 ? organization.slice(0, 10) + '...' : organization;
const limitedDescription = description.length > 28 ? description.slice(0, 28) + '...' : description;
const limitedName = clientName.length > 7 ? clientName.slice(0, 7) + '...' : clientName;
@@ -36,7 +45,9 @@ const WaitingRequestsList = ({ requestId, organization, clientName, description,
export default WaitingRequestsList;
-const Container = styled(Link)<{ hoverBackgroundColor: string }>`
+const Container = styled(Link).withConfig({
+ shouldForwardProp: (prop) => prop !== 'hoverBackgroundColor',
+})<{ hoverBackgroundColor: string }>`
display: flex;
align-items: center;
justify-content: space-between;
@@ -67,6 +78,7 @@ const Container = styled(Link)<{ hoverBackgroundColor: string }>`
width: 150px;
}
`;
+
const OrganizationWrapper = styled.div`
width: 150px;
white-space: nowrap;
@@ -91,4 +103,4 @@ const DetailWrapper = styled.div`
font-family: 'pretendard-regular';
font-size: 13px;
}
-`;
\ No newline at end of file
+`;
diff --git a/src/components/PromotionAdmin/Recruitment/index.tsx b/src/components/PromotionAdmin/Recruitment/index.tsx
index 77c295f5..45cbcf49 100644
--- a/src/components/PromotionAdmin/Recruitment/index.tsx
+++ b/src/components/PromotionAdmin/Recruitment/index.tsx
@@ -1,8 +1,8 @@
-import React from 'react';
+import React, { useEffect } from 'react';
import { PA_ROUTES } from '@/constants/routerConstants';
import styled from 'styled-components';
import NavBtn from './NavBtn';
-import { useLocation } from 'react-router-dom';
+import { useLocation, useNavigate } from 'react-router-dom';
const linksData = [
{
@@ -19,13 +19,21 @@ const linksData = [
function DetailNavigator() {
const location = useLocation();
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ // 페이지가 리로드되었을 때, location.pathname에 따라 조건을 설정
+ if (location.pathname === `${PA_ROUTES.RECRUITMENT}/benefit/manage`) {
+ navigate(`${PA_ROUTES.RECRUITMENT}/benefit/manage`);
+ }
+ }, [location.pathname, navigate]);
return (
{linksData.map((link, index) => {
const isActive =
- location.pathname === link.path || location.pathname === link.relatedPaths;
+ location.pathname === link.path || location.pathname === link.relatedPaths;
return (
- {isError?{isError} :아직 프로젝트가 없습니다. }
+ 아직 프로젝트가 없습니다.
props.theme.color.white.bold};
font-size: 20px;
-
-
@media ${theme.media.mobile} {
width: 85vw;
display: flex;
diff --git a/src/components/PromotionPage/ArtworkDetail/ScrollAnimatedComponent.tsx b/src/components/PromotionPage/ArtworkDetail/ScrollAnimatedComponent.tsx
index 0da6e458..7f098454 100644
--- a/src/components/PromotionPage/ArtworkDetail/ScrollAnimatedComponent.tsx
+++ b/src/components/PromotionPage/ArtworkDetail/ScrollAnimatedComponent.tsx
@@ -22,5 +22,11 @@ function ScrollAnimatedComponent({ article }: string | any) {
export default ScrollAnimatedComponent;
const Article = styled.div`
+ justify-content: center;
+ text-align: left;
+ width: 90%;
+ margin: 0 auto;
font-family: ${(props) => props.theme.font.semiBold};
+ // text-align: center;
+ word-break: keep-all;
`;
diff --git a/src/components/PromotionPage/Header/HeaderDetail.tsx b/src/components/PromotionPage/Header/HeaderDetail.tsx
index 3d409058..e3eafb28 100644
--- a/src/components/PromotionPage/Header/HeaderDetail.tsx
+++ b/src/components/PromotionPage/Header/HeaderDetail.tsx
@@ -60,7 +60,7 @@ export default HeaderDetail;
const NavWrapper = styled.div`
span {
position: relative;
- z-index: 1;
+ z-index: 2;
}
li {
list-style: none;
@@ -82,6 +82,7 @@ const NavWrapper = styled.div`
&:hover::after {
width: 100%;
+ z-index:1;
}
}
`;
diff --git a/src/components/PromotionPage/Main/ArtworkList.tsx b/src/components/PromotionPage/Main/ArtworkList.tsx
index 7ffa3996..1a6bda00 100644
--- a/src/components/PromotionPage/Main/ArtworkList.tsx
+++ b/src/components/PromotionPage/Main/ArtworkList.tsx
@@ -34,7 +34,7 @@ const ArtworkList = React.forwardRef(({ index, data,
};
return (
- = ({ artworks }) => {
const activeIndexRef = useRef(0);
useEffect(() => {
- if (!artworks || artworks.length === 0) return;
+ if (!artworks || artworks.length <= 1) return;
const interval = setInterval(() => {
setTransitioning(true);
activeIndexRef.current = (activeIndexRef.current + 1) % artworks.length;
- // 0.1초 후 activeIndex 교체 -> 바꿀 준비 완
+ // 0.2초 후 activeIndex 교체 -> 바꿀 준비 완
setTimeout(() => {
setActiveIndex(activeIndexRef.current);
setTransitioning(false);
@@ -31,10 +31,32 @@ const ArtworkSlider: React.FC = ({ artworks }) => {
if (!artworks || artworks.length === 0) {
return <>표시할 아트워크가 없습니다.>;
+ } // 수정하기
+
+ if (artworks.length === 1) {
+ return (
+
+
{}}
+ elementHeight={window.innerHeight}
+ index={0}
+ />
+
+ );
}
return (
= ({ artworks }) => {
};
export default ArtworkSlider;
+
diff --git a/src/components/PromotionPage/Main/Intro.tsx b/src/components/PromotionPage/Main/Intro.tsx
index 142c6930..611b833b 100644
--- a/src/components/PromotionPage/Main/Intro.tsx
+++ b/src/components/PromotionPage/Main/Intro.tsx
@@ -1,5 +1,5 @@
import { motion, useInView } from 'framer-motion';
-import React, { useRef } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import Circle from '../Circle/Circle';
import { getCompanyData } from '../../../apis/PromotionAdmin/dataEdit';
@@ -7,11 +7,7 @@ import { Link } from 'react-router-dom';
import { theme } from '@/styles/theme';
import { INTRO_DATA } from '@/constants/introdutionConstants';
import { useQuery } from 'react-query';
-
-interface ICompanyData {
- mainOverview: string;
- commitment: string;
-}
+import { ICompanyData } from '@/types/PromotionAdmin/dataEdit';
const Intro = () => {
const introRef = useRef(null);
@@ -22,13 +18,24 @@ const Intro = () => {
const desInView = useInView(desRef);
const circleInView = useInView(circleRef);
- const { data, isLoading, error } = useQuery
(['company', 'id'], getCompanyData);
+ const [companyMainOverview, setCompanyMainOverview] = useState('');
+ const [companyCommitment, setCompanyCommitment] = useState('');
+
+ const { data, isLoading, error } = useQuery(
+ ['company', 'id'],
+ getCompanyData
+ );
- const companyMainOverview = data?.mainOverview || INTRO_DATA.MAIN_OVERVIEW;
- const companyCommitment = data?.commitment || INTRO_DATA.COMMITMENT;
+ useEffect(() => {
+ if (data) {
+ setCompanyMainOverview(data.mainOverview);
+ setCompanyCommitment(data.commitment);
+ }
+ }, [data]);
if (isLoading) return <>is Loading...>;
if (error) return <>Intro Error: {error.message}>;
+
return (
@@ -37,7 +44,7 @@ const Intro = () => {
animate={{ opacity: introInView ? 1 : 0, y: introInView ? 0 : 100 }}
transition={{ duration: 1, delay: 0.2 }}
dangerouslySetInnerHTML={{
- __html: companyMainOverview,
+ __html: companyMainOverview || INTRO_DATA.MAIN_OVERVIEW,
}}
>
@@ -47,7 +54,7 @@ const Intro = () => {
animate={{ opacity: desInView ? 1 : 0, y: desInView ? 0 : 100 }}
transition={{ duration: 2, delay: 0.6 }}
dangerouslySetInnerHTML={{
- __html: companyCommitment,
+ __html: companyCommitment || INTRO_DATA.COMMITMENT,
}}
>
@@ -68,6 +75,7 @@ const Intro = () => {
export default Intro;
+
const Container = styled.div`
width: 100%;
height: 100vh;
@@ -85,7 +93,7 @@ const Container = styled.div`
}
@supports (-webkit-touch-callout: none) {
- .modal { /* 넘어가지 않는 요소에 사용 */
+ .modal {
height: -webkit-fill-available;
}
}
diff --git a/src/constants/messages.ts b/src/constants/messages.ts
index 2115e4e6..7fbd2eb1 100644
--- a/src/constants/messages.ts
+++ b/src/constants/messages.ts
@@ -16,6 +16,7 @@ const BUTTON_MSG = {
const CONFIRM_MSG = {
SAVE: '저장하시겠습니까?',
+ EDIT: '수정하시겠습니까?',
DELETE: '삭제하시겠습니까?',
POST: '등록하시겠습니까?',
CANCLE: '취소하시겠습니까?',
@@ -31,8 +32,10 @@ const CONFIRM_MSG = {
const ALERT_MSG = {
SAVE: '저장되었습니다.',
+ EDIT: '수정되었습니다.',
DELETE: '삭제되었습니다.',
POST: '등록되었습니다.',
+ COMPLETE: '완료되었습니다.',
};
const PLACEHOLDER_MSG = {
@@ -75,8 +78,6 @@ const ERROR_MSG = {
'현재 서버 또는 인터넷 연결 문제로 인해 문의 등록이 완료되지 않았습니다. 불편을 드려 죄송합니다. 문제가 지속될 경우, 문의해 주시면 신속히 도와드리겠습니다.',
};
-const INFO_MSG = {};
-
export const MSG = {
BUTTON_MSG,
ALERT_MSG,
diff --git a/src/constants/ppErrorMessage.ts b/src/constants/ppErrorMessage.ts
new file mode 100644
index 00000000..e7140724
--- /dev/null
+++ b/src/constants/ppErrorMessage.ts
@@ -0,0 +1,10 @@
+// HTTP 상태 코드 관련 에러 메시지
+export const ERROR_MESSAGES = {
+ NETWORK_ERROR: "네트워크 에러가 발생했어요! 네트워크 환경을 확인해주세요.",
+ BAD_REQUEST: "잘못된 요청입니다! 요청을 다시 확인해주세요.",
+ UNAUTHORIZED: "인증에 실패했습니다! 다시 로그인 해주세요.",
+ FORBIDDEN: "접근 권한이 없습니다!",
+ NOT_FOUND: "요청하신 리소스를 찾을 수 없습니다!",
+ SERVER_ERROR: "서버에서 문제가 발생했습니다! 잠시 후 다시 시도해주세요.",
+ DEFAULT_ERROR: "알 수 없는 에러가 발생했습니다! 다시 시도해주세요.",
+};
\ No newline at end of file
diff --git a/src/hooks/useGraphData.tsx b/src/hooks/useGraphData.tsx
index ff53ea4c..fbc75e71 100644
--- a/src/hooks/useGraphData.tsx
+++ b/src/hooks/useGraphData.tsx
@@ -3,15 +3,21 @@ import { useState, useEffect, useCallback } from 'react';
import dayjs from 'dayjs';
// 해당 기간의 데이터를 가져오는 함수
-const fetchDataByRange = async (category: string, state: string, startDate: dayjs.Dayjs, endDate: dayjs.Dayjs, fetchFunction: Function) => {
+const fetchDataByRange = async (
+ category: string,
+ state: string,
+ startDate: dayjs.Dayjs,
+ endDate: dayjs.Dayjs,
+ fetchFunction: Function,
+) => {
if (!startDate || !endDate) return [];
const startYear = startDate.year();
const startMonth = startDate.month() + 1;
const endYear = endDate.year();
const endMonth = endDate.month() + 1;
- console.log(category+" "+state)
+
try {
- return await fetchFunction(startYear,startMonth,endYear,endMonth,category,state);
+ return await fetchFunction(startYear, startMonth, endYear, endMonth, category, state);
} catch (error) {
console.error(`[❌Error ${fetchFunction.name}]`, error);
return [];
@@ -35,10 +41,14 @@ const processChartData = (startDate: dayjs.Dayjs, endDate: dayjs.Dayjs, data: an
const foundData = data.find(
(item: { year: number; month: number }) => item.year === month.year && item.month === month.month,
);
- const count = foundData ? (division === 'statistics' ? foundData.views
- : (Object.values(foundData.RequestCount)as number[])
- .reduce((acc: number, value: number)=>{return acc+value},0)) : 0;
- console.log(count)
+ const count = foundData
+ ? division === 'statistics'
+ ? foundData.views
+ : (Object.values(foundData.RequestCount) as number[]).reduce((acc: number, value: number) => {
+ return acc + value;
+ }, 0)
+ : 0;
+
return {
x: `${month.year}년 ${month.month}월`,
y: count,
@@ -53,8 +63,8 @@ const useGraphData = (
defaultEndDate: dayjs.Dayjs,
division: 'statistics' | 'request',
) => {
- const [category, setCategory] = useState(division==='request'?'all':'ALL'); // 카테고리 상태 추가
- const [state, setState] = useState(division==='request'?'all':'ALL'); // 카테고리 상태 추가
+ const [category, setCategory] = useState(division === 'request' ? 'all' : 'ALL'); // 카테고리 상태 추가
+ const [state, setState] = useState(division === 'request' ? 'all' : 'ALL'); // 카테고리 상태 추가
const [startDate, setStartDate] = useState(defaultStartDate);
const [endDate, setEndDate] = useState(defaultEndDate);
const [data, setData] = useState([]);
@@ -63,7 +73,13 @@ const useGraphData = (
const fetchData = useCallback(async () => {
setLoading(true);
- const fetchedData = await fetchDataByRange(category, state, startDate || dayjs(), endDate || dayjs(), fetchFunction);
+ const fetchedData = await fetchDataByRange(
+ category,
+ state,
+ startDate || dayjs(),
+ endDate || dayjs(),
+ fetchFunction,
+ );
setData(fetchedData);
setProcessedData(processChartData(startDate || dayjs(), endDate || dayjs(), fetchedData, division));
setLoading(false);
@@ -74,14 +90,14 @@ const useGraphData = (
}, [category, state, startDate, endDate, division, fetchData]);
//카테고리 변경 핸들러
- const handleCategoryChange=(category:string)=>{
+ const handleCategoryChange = (category: string) => {
setCategory(category);
- setState(division==='request'?'all':'ALL');
- }
+ setState(division === 'request' ? 'all' : 'ALL');
+ };
//상태 변경 핸들러
- const handleStateChange=(state:string)=>{
+ const handleStateChange = (state: string) => {
setState(state);
- }
+ };
// 시작일 변경 핸들러
const handleStartDateChange = (newStartDate: dayjs.Dayjs | null) => {
@@ -94,8 +110,20 @@ const useGraphData = (
};
// 상태와 핸들러를 반환
- return { category, state, startDate, endDate, data, processedData, loading,
- handleCategoryChange,handleStateChange,handleStartDateChange, handleEndDateChange, division };
+ return {
+ category,
+ state,
+ startDate,
+ endDate,
+ data,
+ processedData,
+ loading,
+ handleCategoryChange,
+ handleStateChange,
+ handleStartDateChange,
+ handleEndDateChange,
+ division,
+ };
};
export default useGraphData;
diff --git a/src/loaders/aboutPageLoader.ts b/src/loaders/aboutPageLoader.ts
index ff6ba632..2ca6ed7c 100644
--- a/src/loaders/aboutPageLoader.ts
+++ b/src/loaders/aboutPageLoader.ts
@@ -3,6 +3,7 @@ import { CEO_DATA } from '@/constants/introdutionConstants';
import { ICEOInfoData, ICorpInfoData } from '@/types/PromotionPage/about';
import LocomoLogo from '@/assets/images/Locomo.png';
import defaultCEOLogo from '@/assets/images/PP/studioeye_ceo.png';
+import { AxiosError } from 'axios';
const defaultCEOData: ICEOInfoData = {
id: 1,
@@ -24,21 +25,23 @@ const defaultCorpData: ICorpInfoData[] = [
];
export const aboutPageLoader = async () => {
+ const errors: AxiosError[] = [];
try {
const [ceoData, partnersData, companyData, companyDetailData] = await Promise.all([
- getCEOData().catch(() => defaultCEOData), // CEO 데이터 실패 시 기본값 반환
- getPartnersData().catch(() => defaultCorpData), // Partners 데이터 실패 시 기본값 반환
- getCompanyData().catch(() => ({ introduction: '', sloganImageUrl: '' })), // 회사 데이터 실패 시 기본값 반환
- getCompanyDetailData().catch(() => []), // 회사 세부 데이터 실패 시 빈 배열 반환
+ getCEOData().catch((error) => { errors.push(error); return { data: defaultCEOData, error }; }), // CEO 데이터 실패 시 기본값 반환
+ getPartnersData().catch((error) => { errors.push(error); return { data: defaultCorpData, error }; }), // Partners 데이터 실패 시 기본값 반환
+ getCompanyData().catch((error) => { errors.push(error); return { data: { introduction: '', sloganImageUrl: '' }, error };}), // 회사 데이터 실패 시 기본값 반환
+ getCompanyDetailData().catch((error) => { errors.push(error); return { data: [], error }; }), // 회사 세부 데이터 실패 시 빈 배열 반환
]);
// 강제 새로고침 효과를 위해 데이터 복사
return {
ceoData: JSON.parse(JSON.stringify(ceoData || defaultCEOData)),
partnersData: JSON.parse(JSON.stringify(partnersData || defaultCorpData)),
- companyIntroData: companyData.introduction || '',
- sloganImageUrl: companyData.sloganImageUrl || '',
+ companyIntroData: companyData.data.introduction || '',
+ sloganImageUrl: companyData.data.sloganImageUrl || '',
companyDetailData: companyDetailData || [],
+ errors: errors,
};
} catch (error) {
console.error('[🌡AboutPageLoader Error]', error);
@@ -50,6 +53,7 @@ export const aboutPageLoader = async () => {
companyIntroData: '',
sloganImageUrl: '',
companyDetailData: [],
+ errors,
};
}
};
diff --git a/src/pages/PromotionAdmin/DataEditPage/MenuPage/MenuPage.tsx b/src/pages/PromotionAdmin/DataEditPage/MenuPage/MenuPage.tsx
index 24320b63..afca1276 100644
--- a/src/pages/PromotionAdmin/DataEditPage/MenuPage/MenuPage.tsx
+++ b/src/pages/PromotionAdmin/DataEditPage/MenuPage/MenuPage.tsx
@@ -20,11 +20,11 @@ interface IMenuData {
function MenuPage() {
const [menuList, setMenuList] = useState([]);
- const [isLoading, setIsLoading] = useState(true); // 로딩 상태 관리
+ const [isLoading, setIsLoading] = useState(true);
const fetchMenuData = async () => {
try {
- setIsLoading(true); // 데이터를 가져오는 동안 로딩 시작
+ setIsLoading(true);
const data = await getAllMenuData();
if (data && Array.isArray(data.data) && data.data.length > 0) {
diff --git a/src/pages/PromotionAdmin/FaqPage/FAQCheckPage.tsx b/src/pages/PromotionAdmin/FaqPage/FAQCheckPage.tsx
index 27bf4183..f5a0ce13 100644
--- a/src/pages/PromotionAdmin/FaqPage/FAQCheckPage.tsx
+++ b/src/pages/PromotionAdmin/FaqPage/FAQCheckPage.tsx
@@ -51,7 +51,6 @@ export default function FAQCheckPage() {
);
}
-// Styled components
const Wrapper = styled.div``;
const TitleWrapper = styled.div`
display: flex;
diff --git a/src/pages/PromotionAdmin/FaqPage/FAQManagePage.tsx b/src/pages/PromotionAdmin/FaqPage/FAQManagePage.tsx
index ce9aa4d6..6c97a2c1 100644
--- a/src/pages/PromotionAdmin/FaqPage/FAQManagePage.tsx
+++ b/src/pages/PromotionAdmin/FaqPage/FAQManagePage.tsx
@@ -4,7 +4,7 @@ import { useQuery } from 'react-query';
import { IFAQ, getFAQData } from '../../../apis/PromotionAdmin/faq';
import { useState, useEffect } from 'react';
import { theme } from '@/styles/theme';
-import { updateFAQData, deleteFAQData } from '../../../apis/PromotionAdmin/faq';
+import { deleteFAQData } from '../../../apis/PromotionAdmin/faq';
import { useForm } from 'react-hook-form';
import { PA_ROUTES } from '@/constants/routerConstants';
import Pagination from '@/components/Pagination/Pagination';
@@ -45,9 +45,6 @@ function FAQManagePage() {
const sliced = data.slice(indexOfFirst, indexOfLast);
setSlicedFAQ(sliced);
- const queryParams = new URLSearchParams(location.search);
- const page = queryParams.get('page');
-
if (sliced.length === 0 && currentPage > 1) {
setCurrentPage((prevPage) => prevPage - 1);
navigator(`?page=${currentPage - 1}`);
@@ -90,13 +87,12 @@ function FAQManagePage() {
}, [currentFAQ, setValue]);
const handleDelete = async (id: number) => {
- if (window.confirm('삭제하시겠습니까?')) {
+ if (window.confirm(MSG.CONFIRM_MSG.DELETE)) {
try {
await deleteFAQData(id);
- alert('FAQ가 삭제되었습니다.');
+ alert(MSG.ALERT_MSG.DELETE);
refetch();
} catch (error) {
- console.log(error);
alert('FAQ 삭제 중 오류가 발생했습니다.');
}
}
@@ -108,21 +104,11 @@ function FAQManagePage() {
return;
}
- const formData = {
- id: currentFAQ.id,
- question: data.question,
- answer: data.answer,
- visibility: currentFAQ.visibility,
- };
-
- if (!(data.question === '' || data.answer === '') && window.confirm('수정하시겠습니까?')) {
+ if (!(data.question === '' || data.answer === '') && window.confirm(MSG.CONFIRM_MSG.EDIT)) {
try {
- const response = await updateFAQData(formData);
- alert('FAQ가 수정되었습니다.');
- console.log(response);
+ alert(MSG.ALERT_MSG.EDIT);
setIsEditing(false);
} catch (error) {
- console.log(error);
alert('FAQ 수정 중 오류가 발생했습니다.');
}
}
@@ -171,7 +157,7 @@ function FAQManagePage() {
};
const handleConfirmNavigation = (faq: IFAQ) => {
- if (window.confirm('현재 페이지를 나가면 변경 사항이 저장되지 않습니다.\n나가시겠습니까?')) {
+ if (window.confirm(MSG.CONFIRM_MSG.EXIT)) {
setIsEditing(false);
setCurrentFAQ(faq);
setIsSelected(true);
@@ -184,9 +170,9 @@ function FAQManagePage() {
const handleAddNewFAQ = () => {
if (isEditing) {
- if (window.confirm('현재 페이지를 나가면 변경 사항이 저장되지 않습니다.\n나가시겠습니까?')) {
+ if (window.confirm(MSG.CONFIRM_MSG.EXIT)) {
setIsEditing(false);
- navigator(`${PA_ROUTES.FAQ}/write`);
+ navigator(`${PA_ROUTES.FAQ}/manage`);
}
} else {
navigator(`${PA_ROUTES.FAQ}/write`);
diff --git a/src/pages/PromotionAdmin/RecruitmentPage/BenefitManagePage.tsx b/src/pages/PromotionAdmin/RecruitmentPage/BenefitManagePage.tsx
index 861d727c..6075ba7f 100644
--- a/src/pages/PromotionAdmin/RecruitmentPage/BenefitManagePage.tsx
+++ b/src/pages/PromotionAdmin/RecruitmentPage/BenefitManagePage.tsx
@@ -83,18 +83,15 @@ function BenefitManagePage() {
try {
const response = await deleteBenefitData(id);
alert('사내 복지가 삭제되었습니다.');
- console.log(response);
await refetch();
setCurrentBenefit(null);
} catch (error) {
- console.log(error);
alert('사내 복지 삭제 중 오류가 발생했습니다.');
}
}
};
const onValid = async (data: IBenefit) => {
- console.log(currentBenefit);
const formData = new FormData();
formData.append(
'request',
@@ -123,12 +120,10 @@ function BenefitManagePage() {
try {
const response = await updateBenefit(formData);
alert('사내 복지가 수정되었습니다.');
- console.log(response);
await refetch();
setIsEditing(false);
setImgChange(false);
} catch (error) {
- console.log(error);
alert('사내 복지 수정 중 오류가 발생했습니다.');
}
}
@@ -137,12 +132,10 @@ function BenefitManagePage() {
try {
const response = await updateBenefitText(formData);
alert('사내 복지가 수정되었습니다.');
- console.log(response);
await refetch();
setIsEditing(false);
setImgChange(false);
} catch (error) {
- console.log(error);
alert('사내 복지 수정 중 오류가 발생했습니다.');
}
}
@@ -262,16 +255,23 @@ function BenefitManagePage() {
if (isEditing) {
if (window.confirm('현재 페이지를 나가면 변경 사항이 저장되지 않습니다.\n나가시겠습니까?')) {
setIsEditing(false);
- navigator(`${PA_ROUTES.RECRUITMENT}/benefit/write`);
+ } else {
+ return;
}
- } else {
- navigator(`${PA_ROUTES.RECRUITMENT}/benefit/write`);
}
+ if ((data?.length || 0) >= 20) {
+ alert('최대 등록 가능한 사내 복지는 20개입니다.');
+ return;
+ }
+
+ navigator(`${PA_ROUTES.RECRUITMENT}/benefit/write`);
};
+
if (isLoading) return <>Loading...>;
if (error) return <>{error.message}>;
+
return (
diff --git a/src/pages/PromotionAdmin/RecruitmentPage/BenefitWritePage.tsx b/src/pages/PromotionAdmin/RecruitmentPage/BenefitWritePage.tsx
index 13fcaa83..7e5b599e 100644
--- a/src/pages/PromotionAdmin/RecruitmentPage/BenefitWritePage.tsx
+++ b/src/pages/PromotionAdmin/RecruitmentPage/BenefitWritePage.tsx
@@ -112,11 +112,9 @@ function BenefitWritePage() {
try {
const response = await postBenefit(formData);
alert('사내 복지가 등록되었습니다.');
- console.log(response);
setIsEditing(false);
navigator(`${PA_ROUTES.RECRUITMENT}/benefit/manage`, { replace: true });
} catch (error) {
- console.log(error);
alert('사내 복지 등록 중 오류가 발생했습니다.');
}
}
diff --git a/src/pages/PromotionAdmin/RecruitmentPage/RecruitmentManagePage.tsx b/src/pages/PromotionAdmin/RecruitmentPage/RecruitmentManagePage.tsx
index c3bc748b..aeb47178 100644
--- a/src/pages/PromotionAdmin/RecruitmentPage/RecruitmentManagePage.tsx
+++ b/src/pages/PromotionAdmin/RecruitmentPage/RecruitmentManagePage.tsx
@@ -45,7 +45,7 @@ function RecruitmentManagePage() {
useEffect(() => {
if (!isFetching && !isRefetching && data && data.content.length === 0) {
- navigator(`${PA_ROUTES.RECRUITMENT}/write`);
+ navigator(`${PA_ROUTES.RECRUITMENT}/manage`);
}
}, [data, navigator, isFetching, isRefetching]);
@@ -89,7 +89,6 @@ function RecruitmentManagePage() {
try {
const response = await deleteRecruitmentData(id);
alert('채용공고가 삭제되었습니다.');
- console.log(response);
await refetch();
const updatedTotalPosts = totalPosts - 1;
@@ -100,7 +99,6 @@ function RecruitmentManagePage() {
setCurrentRecruitment(null);
} catch (error) {
- console.log(error);
alert('채용공고 삭제 중 오류가 발생했습니다.');
}
}
@@ -147,11 +145,9 @@ function RecruitmentManagePage() {
try {
const response = await updateRecruitmentData(formData);
alert('채용공고가 수정되었습니다.');
- console.log(response);
setIsEditing(false);
refetch();
} catch (error) {
- console.log(error);
alert('채용공고 수정 중 오류가 발생했습니다.');
}
}
@@ -202,7 +198,7 @@ function RecruitmentManagePage() {
if (isEditing) {
if (window.confirm('현재 페이지를 나가면 변경 사항이 저장되지 않습니다.\n나가시겠습니까?')) {
setIsEditing(false);
- navigator(`${PA_ROUTES.RECRUITMENT}/write`);
+ navigator(`${PA_ROUTES.RECRUITMENT}/manage`);
}
} else {
navigator(`${PA_ROUTES.RECRUITMENT}/write`);
@@ -229,31 +225,43 @@ function RecruitmentManagePage() {
- {data?.content.map((recruitment) => (
-
- handleDelete(recruitment.id, data.totalElements)}
- />
- {
- fetchRecruitmentData(recruitment.id);
- }}
- >
- {recruitment.title}
-
-
- {recruitment.status === 'CLOSE' ? '마감' : recruitment.status === 'OPEN' ? '진행' : '예정'}
-
-
- ))}
-
+ {data?.content && data.content.length > 0 ? (
+ data.content.map((recruitment) => (
+
+ handleDelete(recruitment.id, data.totalElements)}
+ />
+ {
+ fetchRecruitmentData(recruitment.id);
+ }}
+ >
+
+ {recruitment.title}
+
+
+
+ {recruitment.status === "CLOSE"
+ ? "마감"
+ : recruitment.status === "OPEN"
+ ? "진행"
+ : "예정"}
+
+
+ ))
+ ) : (
+ 😊 채용공고 데이터가 존재하지 않습니다.
+ )}
+
+
+
{data && (
@@ -628,3 +636,10 @@ const ErrorMessage = styled.div`
font-size: 13px;
height: 16px;
`;
+
+const NoDataMessage = styled.div`
+ padding: 20px;
+ font-size: 1rem;
+ color: ${({ theme }) => theme.color.gray};
+`;
+
diff --git a/src/pages/PromotionAdmin/RecruitmentPage/RecruitmentWritePage.tsx b/src/pages/PromotionAdmin/RecruitmentPage/RecruitmentWritePage.tsx
index dfca3eee..a3f0ff9f 100644
--- a/src/pages/PromotionAdmin/RecruitmentPage/RecruitmentWritePage.tsx
+++ b/src/pages/PromotionAdmin/RecruitmentPage/RecruitmentWritePage.tsx
@@ -89,11 +89,9 @@ function RecruitmentWritePage() {
try {
const response = await postRecruitment(formData);
alert('채용공고가 등록되었습니다.');
- console.log(response);
setIsEditing(false);
navigator(`${PA_ROUTES.RECRUITMENT}/manage`, { replace: true });
} catch (error) {
- console.log(error);
alert('채용공고 등록 중 오류가 발생했습니다.');
}
}
diff --git a/src/pages/PromotionAdmin/RequestPage/RequestCheckPage.tsx b/src/pages/PromotionAdmin/RequestPage/RequestCheckPage.tsx
index 0898e442..b03e2998 100644
--- a/src/pages/PromotionAdmin/RequestPage/RequestCheckPage.tsx
+++ b/src/pages/PromotionAdmin/RequestPage/RequestCheckPage.tsx
@@ -13,11 +13,11 @@ import Pagination from '@/components/Pagination/Pagination';
import UserInfo from '@/components/PromotionAdmin/Request/UserInfo';
import EmailListComponent from '@/components/PromotionAdmin/Request/EmailListComponent';
import Button from '@/components/PromotionAdmin/DataEdit/StyleComponents/Button';
+import { MSG } from '@/constants/messages';
const MAX_TEXT_LENGTH = 255;
const RequestDetailPage = () => {
- // pagination 구현에 사용되는 변수
const { data, isLoading } = useQuery(['request', 'id'], getRequestsData);
const [currentPage, setCurrentPage] = useState(1);
@@ -33,9 +33,8 @@ const RequestDetailPage = () => {
const fetchData = async () => {
try {
const newData = await getRequestsData();
- setData(newData); // 데이터 상태 업데이트
+ setData(newData);
} catch (error) {
- console.error('Error fetching data:', error);
}
};
@@ -43,8 +42,6 @@ const RequestDetailPage = () => {
fetchData();
}, [currentPage, postsPerPage]);
- //
-
const navigator = useNavigate();
const requestDetailMatch = useMatch(`${PA_ROUTES.REQUEST}/:requestId`);
@@ -154,7 +151,7 @@ const RequestDetailPage = () => {
},
]);
- alert('메일 발송이 완료되었습니다.');
+ alert('메일 발송이 ' + MSG.ALERT_MSG.COMPLETE);
setReplyState(state);
setTextValue('');
setEditorState(EditorState.createEmpty());
diff --git a/src/pages/PromotionAdmin/RequestPage/RequestManagePage.tsx b/src/pages/PromotionAdmin/RequestPage/RequestManagePage.tsx
index 8d43b753..1e87a086 100644
--- a/src/pages/PromotionAdmin/RequestPage/RequestManagePage.tsx
+++ b/src/pages/PromotionAdmin/RequestPage/RequestManagePage.tsx
@@ -9,6 +9,7 @@ import WaitingRequestsList from '@/components/PromotionAdmin/Home/RequestSummary
import { ContentBox } from '@/components/PromotionAdmin/Request/Components';
import Pagination from '@/components/Pagination/Pagination';
import { ReactComponent as DeleteIcon } from '@/assets/images/PA/minusIcon.svg';
+import { MSG } from '@/constants/messages';
function RequestList() {
const { data, isLoading, refetch } = useQuery('requests', getRequestsData, { refetchOnWindowFocus: false });
@@ -47,11 +48,11 @@ function RequestList() {
const slicedRequests = filteredRequests?.slice(indexOfFirst, indexOfLast) || [];
const handleDeleteRequest = async (id: number) => {
- if (window.confirm('정말로 삭제하시겠습니까?')) {
+ if (window.confirm(MSG.CONFIRM_MSG.DELETE)) {
try {
await deleteRequest(id);
- alert('삭제되었습니다.');
- refetch(); // 삭제 후 데이터 새로 불러오기
+ alert(MSG.ALERT_MSG.DELETE);
+ refetch();
} catch (error) {
alert('삭제에 실패했습니다. 다시 시도해주세요.');
}
diff --git a/src/pages/PromotionPage/AboutPage/AboutPage.tsx b/src/pages/PromotionPage/AboutPage/AboutPage.tsx
index 3009857f..df7fd338 100644
--- a/src/pages/PromotionPage/AboutPage/AboutPage.tsx
+++ b/src/pages/PromotionPage/AboutPage/AboutPage.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import IntroPage from './IntroPage';
import WhatWeDoPage from './WhatWeDoPage';
@@ -6,6 +6,7 @@ import { theme } from '@/styles/theme';
import { useMediaQuery } from 'react-responsive';
import { useLoaderData } from 'react-router-dom';
import { AboutPageLoaderData } from '@/types/PromotionPage/about';
+import ErrorComponent from '@/components/Error/ErrorComponent';
interface IContainerStyleProps {
backgroundColor?: string;
@@ -14,15 +15,27 @@ interface IContainerStyleProps {
const AboutPage = () => {
const isMobile = useMediaQuery({ query: `(max-width: ${theme.mediaSize.mobile}px)` });
- const { ceoData, partnersData, companyIntroData, sloganImageUrl, companyDetailData } =
+ const { ceoData, partnersData, companyIntroData, sloganImageUrl, companyDetailData, errors } =
useLoaderData() as AboutPageLoaderData;
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ useEffect(() => {
+ if (errors.length>0) {
+ setIsModalOpen(true);
+ }
+ }, [errors]);
+ const closeModal = () => {
+ setIsModalOpen(false);
+ window.location.reload();
+ };
+
const mainPartnersData = partnersData.filter((info) => {
return info.partnerInfo.is_main;
});
return (
+ {isModalOpen &&}
diff --git a/src/pages/PromotionPage/AboutPage/IntroPage.tsx b/src/pages/PromotionPage/AboutPage/IntroPage.tsx
index a1b01538..704a0c78 100644
--- a/src/pages/PromotionPage/AboutPage/IntroPage.tsx
+++ b/src/pages/PromotionPage/AboutPage/IntroPage.tsx
@@ -38,11 +38,19 @@ const IntroPage = ({ companyIntroData, sloganImageUrl }: IntroPageProps) => {
const missionInView = useInView(missionRef);
const removeParagraphTags = (htmlString: string) => {
- return htmlString
- .replace(/<\/?p\s*\/?>/gi, '') // 태그를 제거
- .replace(/<\/?br\s*\/?>/gi, ' '); // 태그를 공백으로 대체
+ if (!htmlString || htmlString.trim() === '') {
+ return INTRO_DATA.COMPANY_INTRO;
+ }
+
+ const cleanedString = htmlString
+ .replace(/<\/?p\s*\/?>/gi, '')
+ .replace(/<\/?br\s*\/?>/gi, ' ')
+ .trim();
+
+ return cleanedString === '' ? INTRO_DATA.COMPANY_INTRO : cleanedString;
};
+
return (
@@ -86,11 +94,9 @@ const IntroPage = ({ companyIntroData, sloganImageUrl }: IntroPageProps) => {
>
ABOUT
@@ -163,7 +169,7 @@ const InitTitleWrapper = styled.div`
gap: 0.5rem;
}
`;
-const InitTitle = styled(motion.div)`
+const InitTitle = styled(motion.div) `
font-family: ${theme.font.bold};
font-size: clamp(2.75rem, 8vw, 7.5rem);
color: ${(props) => props.color || theme.color.white.light};
diff --git a/src/pages/PromotionPage/ArtworkPage/ArtworkPage.tsx b/src/pages/PromotionPage/ArtworkPage/ArtworkPage.tsx
index 73a15c7d..773b3c16 100644
--- a/src/pages/PromotionPage/ArtworkPage/ArtworkPage.tsx
+++ b/src/pages/PromotionPage/ArtworkPage/ArtworkPage.tsx
@@ -8,6 +8,7 @@ import { artwork_categories } from '@/components/PromotionPage/Artwork/Navigatio
import { theme } from '@/styles/theme';
import React from 'react';
import { AxiosError } from 'axios';
+import ErrorComponent from '@/components/Error/ErrorComponent';
const NullException=React.lazy(()=>import('@/components/PromotionPage/Artwork/NullException'))
const SkeletonComponent=React.lazy(()=>import('@/components/PromotionPage/SkeletonComponent/SkeletonComponent'))
const ArtworkCard=React.lazy(()=>import('@/components/PromotionPage/Artwork/ArtworkCard'))
@@ -25,23 +26,30 @@ function ArtworkPage() {
const { data, isLoading, error } = useQuery(['artwork', 'id'], getArtworkData);
const category = artwork_categories.find((category) => category.key + '' === categoryId);
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ useEffect(() => {
+ if (error) {
+ setIsModalOpen(true);
+ }
+ }, [error]);
+ const closeModal = () => {
+ setIsModalOpen(false);
+ window.location.reload();
+ };
+
//useMemo를 사용해 별도로 메모리화, 리렌더링 방지 => 동일한 데이터 연산을 반복하지 않을 수 있음
- // const postedData = data?.data?.filter((artwork) => artwork.isPosted === true) ?? [];
- // const filteredData = category
- // ? postedData.filter((artwork) => artwork.category.toLowerCase() === category.label.toLocaleLowerCase())
- // : postedData;
- const postedData = useMemo(() =>
- data?.data?.filter((artwork) => artwork.isPosted) ?? [],
- [data]
- );
- const filteredData = useMemo(() =>
- category
- ? postedData.filter((artwork) =>
- artwork.category.toLowerCase() === category.label.toLowerCase()
- )
- : postedData,
- [category, postedData]
- );
+ const postedData = useMemo(() =>
+ data?.data?.filter((artwork) => artwork.isPosted) ?? [],
+ [data]
+ );
+ const filteredData = useMemo(() =>
+ category
+ ? postedData.filter((artwork) =>
+ artwork.category.toLowerCase() === category.label.toLowerCase()
+ )
+ : postedData,
+ [category, postedData]
+ );
function ScrollToTop() {
useEffect(() => {
@@ -50,19 +58,7 @@ function ArtworkPage() {
return null;
}
- const errorData=error?.response?.data as ErrorResponse|undefined
- const errorMessage=()=> {
- if(errorData){
- return errorData.status?errorData.status+": "+errorData.error:"네트워크 에러가 발생했어요! 네트워크 환경을 확인해주세요."
- } else{
- // if(!error && data===undefined){
- // return "Network Error"
- // }else{
- return null
- // }
- }}
-
- if(isLoading) return <>Loading...>
+ if(isLoading) return Loading...
return (
<>
@@ -72,7 +68,10 @@ function ArtworkPage() {
{postedData?.length === 0 || data === null ? (
Loading...
}>
-
+
+ {isModalOpen &&}
+
+
) : (
<>
@@ -102,7 +101,10 @@ function ArtworkPage() {
) : (
{filteredData?.length === 0 || data === null ? (
-
+
+ {isModalOpen &&}
+
+
) : (
<>
diff --git a/src/pages/PromotionPage/ContactPage/ContactUsPage.tsx b/src/pages/PromotionPage/ContactPage/ContactUsPage.tsx
index b0b9b82f..319c9497 100644
--- a/src/pages/PromotionPage/ContactPage/ContactUsPage.tsx
+++ b/src/pages/PromotionPage/ContactPage/ContactUsPage.tsx
@@ -1,6 +1,6 @@
import styled from 'styled-components';
import React, { useState, useEffect, useRef, Suspense } from 'react';
-import axios from 'axios';
+import axios, { AxiosError } from 'axios';
import { PROMOTION_BASIC_PATH } from '@/constants/basicPathConstants';
import logo from '../../../assets/logo/Logo.png';
import BackgroundYellowCircle from '@/components/BackgroundYellowCircle/BackgroundYellowCircle';
@@ -9,6 +9,7 @@ import { useNavigate } from 'react-router-dom';
import { getCompanyBasicData } from '../../../apis/PromotionAdmin/dataEdit';
import { emailCheck, phoneFaxCheck } from '@/components/ValidationRegEx/ValidationRegEx';
import { theme } from '@/styles/theme';
+import ErrorComponent from '@/components/Error/ErrorComponent';
import { MSG } from '@/constants/messages';
interface ICircleProps {
@@ -37,6 +38,7 @@ type ICompanyBasic = {
const ContactUsPage = () => {
const navigator = useNavigate();
+ const [error, setError] = useState(null);
const [requestStep, setRequestStep] = useState(0);
const [formData, setFormData] = useState({
category: '',
@@ -71,11 +73,23 @@ const ContactUsPage = () => {
}
} catch (error) {
console.error('Error fetching company data: ', error);
+ setError(error as AxiosError);
}
};
fetchData();
}, []);
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ useEffect(() => {
+ if (error !== null) {
+ setIsModalOpen(true);
+ }
+ }, [error]);
+ const closeModal = () => {
+ setIsModalOpen(false);
+ window.location.reload();
+ };
+
// wheel event 관리
const containerRef = useRef(null);
const handleWheel = () => (e: WheelEvent) => {
@@ -372,6 +386,7 @@ const ContactUsPage = () => {
return (
+ {isModalOpen && }
diff --git a/src/pages/PromotionPage/FaqPage/FaqPage.tsx b/src/pages/PromotionPage/FaqPage/FaqPage.tsx
index 968ca44c..4ee23d9f 100644
--- a/src/pages/PromotionPage/FaqPage/FaqPage.tsx
+++ b/src/pages/PromotionPage/FaqPage/FaqPage.tsx
@@ -4,6 +4,8 @@ import { motion } from 'framer-motion';
import BackgroundYellowCircle from '@/components/PromotionPage/BackgroundYellowCircle/BackgroundYellowCircle';
import { theme } from '@/styles/theme';
import { getFaqData } from '@/apis/PromotionPage/faq';
+import { AxiosError } from 'axios';
+import ErrorComponent from '@/components/Error/ErrorComponent';
interface FaqData {
id: number;
@@ -23,6 +25,7 @@ const FaqPage = () => {
const [searchData, setSearchData] = useState([]); // 검색된 FAQ 데이터
const [expandedItems, setExpandedItems] = useState>(new Set()); // 펼쳐진 FAQ 항목
const [errorMessage, setErrorMessage] = useState(''); // 에러 메시지 상태 추가
+ const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
@@ -36,6 +39,7 @@ const FaqPage = () => {
}
} catch (error) {
console.error(error);
+ setError(error as AxiosError)
setErrorMessage('전송이 실패했습니다.'); // 실패 시 에러 메시지 설정
setSearchResult('none'); // 검색 결과가 없을 때 상태 설정
}
@@ -43,6 +47,17 @@ const FaqPage = () => {
fetchData();
}, []);
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ useEffect(() => {
+ if (error!==null) {
+ setIsModalOpen(true);
+ }
+ }, [error]);
+ const closeModal = () => {
+ setIsModalOpen(false);
+ window.location.reload();
+ };
+
const initiate = (data: FaqData[]) => {
if (data.length === 0) {
setSearchData([]);
@@ -86,6 +101,7 @@ const FaqPage = () => {
return (
+ {isModalOpen &&}
@@ -114,7 +130,7 @@ const FaqPage = () => {
{searchResult === 'fail' ? (
검색 결과가 없습니다.
) : searchResult === 'none' ? (
- FAQ 데이터가 없습니다.
+ 데이터가 없습니다.
) : (
searchData.map((item, i) => (
import('@/components/PromotionPage/Main/Top'));
const Intro = lazy(() => import('@/components/PromotionPage/Main/Intro'));
@@ -20,7 +22,7 @@ const ArtworkSlider = lazy(() => import('@/components/PromotionPage/Main/Artwork
const MainPage = () => {
const [elementHeight, setElementHeight] = useState(window.innerHeight);
const [activeIndex, setActiveIndex] = useState(0);
- const { data, isLoading, error } = useQuery(['artwork', 'id'], getArtworkMainData, {
+ const { data, isLoading, error } = useQuery(['artwork', 'id'], getArtworkMainData, {
staleTime: 1000 * 60 * 10, // 10분
});
@@ -58,9 +60,20 @@ const MainPage = () => {
}
}, [height]);
- if (error) return <>Artwork Error: {error.message}>;
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ useEffect(() => {
+ if (error) {
+ setIsModalOpen(true);
+ }
+ }, [error]);
+ const closeModal = () => {
+ setIsModalOpen(false);
+ window.location.reload();
+ };
+
return (
<>
+ {isModalOpen &&}