From e8ecbc8a89183396efb22c61070481d9aa074d0d Mon Sep 17 00:00:00 2001 From: HaLin Kim Date: Sun, 24 Sep 2023 15:32:58 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20WithMockUser=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...hMockCustomUserSecurityContextFactory.java | 30 +++++++++++++++++++ .../backend/test/util/WithMockCustomUser.java | 12 ++++++++ 2 files changed, 42 insertions(+) create mode 100644 backend/src/test/java/com/graphy/backend/test/security/WithMockCustomUserSecurityContextFactory.java create mode 100644 backend/src/test/java/com/graphy/backend/test/util/WithMockCustomUser.java diff --git a/backend/src/test/java/com/graphy/backend/test/security/WithMockCustomUserSecurityContextFactory.java b/backend/src/test/java/com/graphy/backend/test/security/WithMockCustomUserSecurityContextFactory.java new file mode 100644 index 00000000..efa78f9c --- /dev/null +++ b/backend/src/test/java/com/graphy/backend/test/security/WithMockCustomUserSecurityContextFactory.java @@ -0,0 +1,30 @@ +package com.graphy.backend.test.security; + +import com.graphy.backend.domain.auth.util.MemberInfo; +import com.graphy.backend.domain.member.domain.Member; +import com.graphy.backend.test.util.WithMockCustomUser; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.test.context.support.WithSecurityContextFactory; + +import java.util.ArrayList; +import java.util.List; + +public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory { + + @Override + public SecurityContext createSecurityContext(WithMockCustomUser withMockCustomUser) { + SecurityContext context = SecurityContextHolder.createEmptyContext(); + List grantedAuthorities = new ArrayList<>(); + grantedAuthorities.add((GrantedAuthority) () -> "ROLE_USER"); + Member member = Member.builder().id(1L).email("testEamil").password("testPassword").build(); + MemberInfo memberInfo = new MemberInfo(member); + + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(memberInfo, "testPassword", grantedAuthorities); + context.setAuthentication(authentication); + return context; + } +} diff --git a/backend/src/test/java/com/graphy/backend/test/util/WithMockCustomUser.java b/backend/src/test/java/com/graphy/backend/test/util/WithMockCustomUser.java new file mode 100644 index 00000000..aa8109f6 --- /dev/null +++ b/backend/src/test/java/com/graphy/backend/test/util/WithMockCustomUser.java @@ -0,0 +1,12 @@ +package com.graphy.backend.test.util; + +import com.graphy.backend.test.security.WithMockCustomUserSecurityContextFactory; +import org.springframework.security.test.context.support.WithSecurityContext; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class) +public @interface WithMockCustomUser { +} From 7cc500b504f6e8ea93f0e8cc30db8ee0e8f06a68 Mon Sep 17 00:00:00 2001 From: HaLin Kim Date: Sun, 24 Sep 2023 15:33:21 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20member=20Controller=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/controller/MemberControllerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/test/java/com/graphy/backend/domain/member/controller/MemberControllerTest.java b/backend/src/test/java/com/graphy/backend/domain/member/controller/MemberControllerTest.java index bc63b4fe..0a3e9da5 100644 --- a/backend/src/test/java/com/graphy/backend/domain/member/controller/MemberControllerTest.java +++ b/backend/src/test/java/com/graphy/backend/domain/member/controller/MemberControllerTest.java @@ -73,7 +73,7 @@ public void setup(RestDocumentationContextProvider provider) { @Test @DisplayName("닉네임으로 사용자를 조회한다") - public void findMemberTest() throws Exception { + void findMemberTest() throws Exception { // given GetMemberResponse response1 = GetMemberResponse.builder() .nickname(member1.getNickname()) @@ -122,7 +122,7 @@ public void findMemberTest() throws Exception { @Test @DisplayName("현재 로그인한 사용자의 정보로 마이페이지를 조회한다") - public void getMyPageTest() throws Exception { + void getMyPageTest() throws Exception { GetMyPageResponse result = GetMyPageResponse.builder() .nickname(member1.getNickname()) .introduction(member1.getIntroduction()) From 75a4ec1b7e463f069141d165a6ad6bed3fa2969b Mon Sep 17 00:00:00 2001 From: HaLin Kim Date: Sun, 24 Sep 2023 15:33:44 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20project=20Controller=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=88=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ProjectControllerTest.java | 159 ++++++++++-------- 1 file changed, 87 insertions(+), 72 deletions(-) diff --git a/backend/src/test/java/com/graphy/backend/domain/project/controller/ProjectControllerTest.java b/backend/src/test/java/com/graphy/backend/domain/project/controller/ProjectControllerTest.java index 39794fad..7d50b6bf 100644 --- a/backend/src/test/java/com/graphy/backend/domain/project/controller/ProjectControllerTest.java +++ b/backend/src/test/java/com/graphy/backend/domain/project/controller/ProjectControllerTest.java @@ -1,11 +1,16 @@ package com.graphy.backend.domain.project.controller; import com.graphy.backend.domain.comment.service.CommentService; +import com.graphy.backend.domain.member.domain.Member; +import com.graphy.backend.domain.project.dto.request.CreateProjectRequest; +import com.graphy.backend.domain.project.dto.request.GetProjectPlanRequest; import com.graphy.backend.domain.project.dto.request.GetProjectsRequest; +import com.graphy.backend.domain.project.dto.response.CreateProjectResponse; import com.graphy.backend.domain.project.dto.response.GetProjectResponse; import com.graphy.backend.domain.project.service.ProjectService; import com.graphy.backend.global.config.SecurityConfig; import com.graphy.backend.test.MockApiTest; +import com.graphy.backend.test.util.WithMockCustomUser; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -15,9 +20,9 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import org.springframework.data.domain.Pageable; +import org.springframework.http.MediaType; import org.springframework.restdocs.RestDocumentationContextProvider; import org.springframework.restdocs.RestDocumentationExtension; -import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -25,24 +30,25 @@ import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.concurrent.CompletableFuture; import static com.graphy.backend.test.config.ApiDocumentUtil.getDocumentRequest; import static com.graphy.backend.test.config.ApiDocumentUtil.getDocumentResponse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.when; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; -import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.requestParameters; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(ProjectController.class) @ExtendWith(RestDocumentationExtension.class) -@WithMockUser(username = "yukeon@gmail.com") @Import(SecurityConfig.class) class ProjectControllerTest extends MockApiTest { @@ -55,7 +61,7 @@ class ProjectControllerTest extends MockApiTest { @MockBean CommentService commentService; - private static String baseUrl = "/api/v1/projects"; + private static final String BASE_URL = "/api/v1/projects"; @BeforeEach public void setup(RestDocumentationContextProvider provider) { @@ -66,43 +72,53 @@ public void setup(RestDocumentationContextProvider provider) { } -// @Test -// @DisplayName("프로젝트 생성 테스트") -// void createProject() throws Exception { -// //given -// CreateProjectRequest request = CreateProjectRequest.builder() -// .projectName("projectName") -// .description("description") -// .content("content") -// .build(); -// -// CreateProjectResponse response = CreateProjectResponse.builder().projectId(1L).build(); -// -// //when -// when(projectService.addProject(request, new Member())).thenReturn(response); -// -// //then -// mvc.perform(post(baseUrl) -// .contentType(MediaType.APPLICATION_JSON) -// .content(objectMapper.writeValueAsString(request))) -// .andExpect(status().isOk()) -// .andDo(document("project-create", -// preprocessResponse(prettyPrint())) -// ); -// } - -// @Test -// @DisplayName("프로젝트 삭제 테스트") -// void deleteProject() throws Exception { -// //given -// Long projectId = 1L; -// -// doNothing().when(projectService).removeProject(anyLong()); -// -// mvc.perform(delete(baseUrl + "/{projectId}", 1L) -// .contentType(MediaType.APPLICATION_JSON)) -// .andExpect(status().isOk()); -// } + @Test + @WithMockCustomUser + @DisplayName("프로젝트 생성 테스트") + void createProject() throws Exception { + //given + CreateProjectRequest request = CreateProjectRequest.builder() + .projectName("projectName") + .description("description") + .content("content") + .build(); + + CreateProjectResponse response = CreateProjectResponse.builder().projectId(1L).build(); + + //when + when(projectService.addProject(request, new Member())).thenReturn(response); + + //then + mvc.perform(post(BASE_URL) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andExpect(status().isCreated()) + .andDo(document("project/add/success", + getDocumentRequest(), + getDocumentResponse(), + requestFields( + fieldWithPath("projectName").description("프로젝트 이름"), + fieldWithPath("description").description("프로젝트 설명"), + fieldWithPath("content").description("프로젝트 내용"), + fieldWithPath("techTags").description("기술 스택"), + fieldWithPath("thumbNail").description("썸네일") + ), + responseFields( + fieldWithPath("code").description("상태 코드"), + fieldWithPath("message").description("응답 메시지"), + fieldWithPath("data").description("응답 데이터") + ))); + } + + @Test + @WithMockCustomUser + @DisplayName("프로젝트 삭제 테스트") + void deleteProject() throws Exception { + + mvc.perform(delete(BASE_URL + "/{projectId}", 1L) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + } @Test @DisplayName("프로젝트 이름/내용/전체 검색한다") @@ -111,10 +127,8 @@ void searchProjectsWithName() throws Exception { // given String projectName = "검색이름"; - GetProjectsRequest request = GetProjectsRequest.builder() - .projectName(projectName).build(); - List result = new ArrayList(); + List result = new ArrayList<>(); for (int i = 0; i < 5; i++) { GetProjectResponse response = @@ -130,7 +144,7 @@ void searchProjectsWithName() throws Exception { info.add("size", "5"); // when & then - mvc.perform(get(baseUrl).params(info)) + mvc.perform(get(BASE_URL).params(info)) .andExpect(status().isOk()) .andDo(document("project/list/success", getDocumentRequest(), @@ -154,29 +168,30 @@ void searchProjectsWithName() throws Exception { ))); } -// @Test -// @DisplayName("프로젝트 고도화 계획을 제안 받는다") -// void getProjectPlan() throws Exception { -// -// // given -// List features = new ArrayList<>(Arrays.asList("게시물 업로드", "좋아요 누르는 기능")); -// List techStacks = new ArrayList<>(Arrays.asList("Springboot", "React", "mySQL")); -// List plans = new ArrayList<>(Arrays.asList("Spring Security", "Docker")); -// String topic = "간단한 게시판"; -// -// GetProjectPlanRequest request = new GetProjectPlanRequest(topic, features, techStacks, plans); -// -// String apiResult = "API 결과"; -// CompletableFuture result = CompletableFuture.completedFuture(apiResult); -// -// given(projectService.getProjectPlanAsync(any())).willReturn(result); -// -// // when -// String body = objectMapper.writeValueAsString(request); -// mvc.perform(post(baseUrl+"/plans").principal(new TestingAuthenticationToken("testEmail", "testPassword")) -// .content(objectMapper.writeValueAsString(request)) -// .contentType(MediaType.APPLICATION_JSON)) -// .andExpect(status().isOk()); -// -// } + @Test + @WithMockCustomUser + @DisplayName("프로젝트 고도화 계획을 제안 받는다") + void getProjectPlan() throws Exception { + + // given + List features = new ArrayList<>(Arrays.asList("게시물 업로드", "좋아요 누르는 기능")); + List techStacks = new ArrayList<>(Arrays.asList("Springboot", "React", "mySQL")); + List plans = new ArrayList<>(Arrays.asList("Spring Security", "Docker")); + String topic = "간단한 게시판"; + + GetProjectPlanRequest request = new GetProjectPlanRequest(topic, features, techStacks, plans); + + String apiResult = "API 결과"; + CompletableFuture result = CompletableFuture.completedFuture(apiResult); + + given(projectService.getProjectPlanAsync(any())).willReturn(result); + + // when + + mvc.perform(post(BASE_URL +"/plans") + .content(objectMapper.writeValueAsString(request)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + } } From 6eea5e9f6bc1e68d5c9b2f497d09d736120aaf1e Mon Sep 17 00:00:00 2001 From: HaLin Kim Date: Sun, 24 Sep 2023 15:34:17 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20follow=20Controller=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/FollowControllerTest.java | 191 ++++++++++-------- 1 file changed, 108 insertions(+), 83 deletions(-) diff --git a/backend/src/test/java/com/graphy/backend/domain/follow/controller/FollowControllerTest.java b/backend/src/test/java/com/graphy/backend/domain/follow/controller/FollowControllerTest.java index f5afed92..ae3a8dcf 100644 --- a/backend/src/test/java/com/graphy/backend/domain/follow/controller/FollowControllerTest.java +++ b/backend/src/test/java/com/graphy/backend/domain/follow/controller/FollowControllerTest.java @@ -3,7 +3,9 @@ import com.graphy.backend.domain.auth.util.MemberInfo; import com.graphy.backend.domain.follow.service.FollowService; import com.graphy.backend.domain.member.domain.Member; +import com.graphy.backend.domain.member.dto.response.GetMemberListResponse; import com.graphy.backend.test.MockApiTest; +import com.graphy.backend.test.util.WithMockCustomUser; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -17,15 +19,23 @@ import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.web.context.WebApplicationContext; +import java.util.Arrays; +import java.util.List; + import static com.graphy.backend.domain.member.domain.Role.ROLE_USER; import static com.graphy.backend.test.config.ApiDocumentUtil.getDocumentRequest; import static com.graphy.backend.test.config.ApiDocumentUtil.getDocumentResponse; +import static org.hamcrest.Matchers.hasSize; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(FollowController.class) @ExtendWith(RestDocumentationExtension.class) @@ -42,7 +52,7 @@ public void setup(RestDocumentationContextProvider provider) { memberInfo = new MemberInfo(Member.builder().id(1L).email("test@gmail.com").password("password").role(ROLE_USER).build()); } - private static String baseUrl = "/api/v1/follow"; + private static final String BASE_URL = "/api/v1/follow"; @Test @DisplayName("팔로우 신청 테스트") @@ -51,7 +61,7 @@ void followTest() throws Exception { Long id = 1L; // when & then - mvc.perform(post(baseUrl + "/{id}", id).principal(new TestingAuthenticationToken("testEmail", "testPassword"))) + mvc.perform(post(BASE_URL + "/{id}", id).principal(new TestingAuthenticationToken("testEmail", "testPassword"))) .andExpect(status().isCreated()) .andDo(document("follow/add/success", getDocumentRequest(), @@ -73,7 +83,7 @@ void unfollowTest() throws Exception { Long id = 1L; //then - mvc.perform(RestDocumentationRequestBuilders.delete(baseUrl + "/{id}", id).principal(new TestingAuthenticationToken("testEmail", "testPassword"))) + mvc.perform(RestDocumentationRequestBuilders.delete(BASE_URL + "/{id}", id).principal(new TestingAuthenticationToken("testEmail", "testPassword"))) .andExpect(status().isNoContent()) .andDo(document("follow/remove/success", getDocumentRequest(), @@ -88,84 +98,99 @@ void unfollowTest() throws Exception { ))); } -// @Test -// @DisplayName("팔로잉 리스트 조회 테스트") -// void getFollowingListTest() throws Exception { -// // given -// GetMemberListResponse following1 = new GetMemberListResponse() { -// public Long getId() { -// return 1L; -// } -// public String getNickname() { -// return "memberA"; -// } -// }; -// -// GetMemberListResponse following2 = new GetMemberListResponse() { -// public Long getId() { -// return 2L; -// } -// public String getNickname() { -// return "memberB"; -// } -// }; -// -// List followingList = Arrays.asList(following1, following2); -// -// // when -// given(followService.findFollowingList(any(Member.class))).willReturn(followingList); -// -// //then -// mvc.perform(get(baseUrl + "/following").with(user(memberInfo))) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.data", hasSize(2))) -// .andExpect(jsonPath("$.data[0].id").exists()) -// .andExpect(jsonPath("$.data[0].nickname").value("memberA")) -// .andExpect(jsonPath("$.data[1].id").exists()) -// .andExpect(jsonPath("$.data[1].nickname").value("memberB")) -// .andDo(document("followingList-get", -// preprocessResponse(prettyPrint())) -// ); -// } - -// @Test -// @WithAnonymousUser -// @DisplayName("팔로워 리스트 조회 테스트") -// void getFollowerListTest() throws Exception { -// // given -// GetMemberListResponse follower1 = new GetMemberListResponse() { -// public Long getId() { -// return 1L; -// } -// public String getNickname() { -// return "memberA"; -// } -// }; -// -// GetMemberListResponse follower2 = new GetMemberListResponse() { -// public Long getId() { -// return 2L; -// } -// public String getNickname() { -// return "memberB"; -// } -// }; -// -// List followerList = Arrays.asList(follower1, follower2); -// -// // when -// given(followService.findFollowerList(any(Member.class))).willReturn(followerList); -// -// //then -// mvc.perform(get(baseUrl + "/follower").principal(new TestingAuthenticationToken("testEmail", "testPassword"))) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$.data", hasSize(2))) -// .andExpect(jsonPath("$.data[0].id").exists()) -// .andExpect(jsonPath("$.data[0].nickname").value("memberA")) -// .andExpect(jsonPath("$.data[1].id").exists()) -// .andExpect(jsonPath("$.data[1].nickname").value("memberB")) -// .andDo(document("followerList-get", -// preprocessResponse(prettyPrint())) -// ); -// } + @Test + @WithMockCustomUser + @DisplayName("팔로잉 리스트 조회 테스트") + void getFollowingListTest() throws Exception { + // given + GetMemberListResponse following1 = new GetMemberListResponse() { + public Long getId() { + return 1L; + } + public String getNickname() { + return "memberA"; + } + }; + + GetMemberListResponse following2 = new GetMemberListResponse() { + public Long getId() { + return 2L; + } + public String getNickname() { + return "memberB"; + } + }; + + List followingList = Arrays.asList(following1, following2); + + // when + given(followService.findFollowingList(any(Member.class))).willReturn(followingList); + + //then + mvc.perform(get(BASE_URL + "/following")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data", hasSize(2))) + .andExpect(jsonPath("$.data[0].id").exists()) + .andExpect(jsonPath("$.data[0].nickname").value("memberA")) + .andExpect(jsonPath("$.data[1].id").exists()) + .andExpect(jsonPath("$.data[1].nickname").value("memberB")) + .andDo(document("follow/followingList/success", + getDocumentRequest(), + getDocumentResponse(), + responseFields( + fieldWithPath("code").description("상태 코드"), + fieldWithPath("message").description("응답 메시지"), + fieldWithPath("data").description("응답 데이터"), + fieldWithPath("data[].id").description("팔로잉하는 사용자의 ID"), + fieldWithPath("data[].nickname").description("팔로잉하는 사용자의 nickname") + ))); + } + + @Test + @WithMockCustomUser + @DisplayName("팔로워 리스트 조회 테스트") + void getFollowerListTest() throws Exception { + // given + GetMemberListResponse follower1 = new GetMemberListResponse() { + public Long getId() { + return 1L; + } + public String getNickname() { + return "memberA"; + } + }; + + GetMemberListResponse follower2 = new GetMemberListResponse() { + public Long getId() { + return 2L; + } + public String getNickname() { + return "memberB"; + } + }; + + List followerList = Arrays.asList(follower1, follower2); + + // when + given(followService.findFollowerList(any(Member.class))).willReturn(followerList); + + //then + mvc.perform(get(BASE_URL + "/follower").principal(new TestingAuthenticationToken("testEmail", "testPassword"))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data", hasSize(2))) + .andExpect(jsonPath("$.data[0].id").exists()) + .andExpect(jsonPath("$.data[0].nickname").value("memberA")) + .andExpect(jsonPath("$.data[1].id").exists()) + .andExpect(jsonPath("$.data[1].nickname").value("memberB")) + .andDo(document("follow/followingList/success", + getDocumentRequest(), + getDocumentResponse(), + responseFields( + fieldWithPath("code").description("상태 코드"), + fieldWithPath("message").description("응답 메시지"), + fieldWithPath("data").description("응답 데이터"), + fieldWithPath("data[].id").description("팔로워 사용자의 ID"), + fieldWithPath("data[].nickname").description("팔로워 사용자의 nickname") + ))); + } } From 2947d92595c5f74ac77ccd9a85cfa67312c33611 Mon Sep 17 00:00:00 2001 From: rachel4w2 Date: Tue, 26 Sep 2023 15:57:59 +0900 Subject: [PATCH 05/12] =?UTF-8?q?feat=20:=20Banner=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Swiper 컴포넌트에서 사용하지 않는 리스너 함수 제거 - Swiper 컴포넌트에 Autoplay 기능 추가 --- frontend/.eslintrc.json | 3 +- frontend/package.json | 4 + frontend/src/app/page.tsx | 4 +- frontend/src/components/main/Banner.tsx | 56 +++++++ frontend/src/components/main/ProjectCard.tsx | 0 frontend/tailwind.config.cjs | 58 +++++++ frontend/tailwind.config.ts | 20 --- frontend/tsconfig.json | 10 +- frontend/yarn.lock | 155 +++++++++++++++---- 9 files changed, 259 insertions(+), 51 deletions(-) create mode 100644 frontend/src/components/main/Banner.tsx create mode 100644 frontend/src/components/main/ProjectCard.tsx create mode 100644 frontend/tailwind.config.cjs delete mode 100644 frontend/tailwind.config.ts diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index 66033337..4dd15c4f 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -9,6 +9,7 @@ "project": "./tsconfig.json" }, "rules": { - "react/react-in-jsx-scope": "off" + "react/react-in-jsx-scope": "off", + "import/no-extraneous-dependencies": "off" } } diff --git a/frontend/package.json b/frontend/package.json index b99c28f3..b7e53caa 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,12 +21,16 @@ "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^17.1.0", "eslint-config-next": "13.5.1", + "lottie-react": "^2.4.0", "next": "13.5.1", "postcss": "8.4.30", "prettier": "^3.0.3", "react": "18.2.0", "react-dom": "18.2.0", + "react-router-dom": "^6.16.0", "recoil": "^0.7.7", + "sass": "^1.68.0", + "swiper": "^10.3.0", "tailwindcss": "3.3.3", "typescript": "5.2.2" }, diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index b772bfb7..bca7edf6 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,3 +1,5 @@ -export default function Home() { +import Banner from '../components/main/Banner' + +export default function Main() { return } diff --git a/frontend/src/components/main/Banner.tsx b/frontend/src/components/main/Banner.tsx new file mode 100644 index 00000000..8a185beb --- /dev/null +++ b/frontend/src/components/main/Banner.tsx @@ -0,0 +1,56 @@ +'use client' + +import Lottie from 'lottie-react' +import { Pagination, Autoplay } from 'swiper/modules' +import { Swiper, SwiperSlide } from 'swiper/react' +import 'swiper/css' +import 'swiper/scss/pagination' +import lotties from '../../utils/lotties' + +function Banner() { + return ( +
+ + +
+

+ 프로젝트 기록과 공유의 공간, Graphy +

+

+ Graphy와 함께 성장해보세요. +

+
+
+ +
+
+

+ 프로젝트 공유하러 출발! +

+

+ Graphy에서 누구나 프로젝트를 작성하고 공유할 수 있습니다. 이제 + 출발하세요! +

+
+ +
+
+
+
+ ) +} + +export default Banner diff --git a/frontend/src/components/main/ProjectCard.tsx b/frontend/src/components/main/ProjectCard.tsx new file mode 100644 index 00000000..e69de29b diff --git a/frontend/tailwind.config.cjs b/frontend/tailwind.config.cjs new file mode 100644 index 00000000..8fda9443 --- /dev/null +++ b/frontend/tailwind.config.cjs @@ -0,0 +1,58 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./src/**/*.{html,js,ts,tsx}'], + theme: { + extend: { + spacing: { + 660: '660px', + 630: '630px', + 555: '555px', + 460: '460px', + 355: '350px', + 504: '54px', + }, + colors: { + graphybg: '#F9F8F8', + graphyblue: '#505F9A', + graphypink: '#CA92C7', + mainbannerleft: '#678EF4', + mainbannerright: '#FF93AE', + subbanner: '#C1D0EF', + gptbutton: '#7082CA', + button: '#364A9A', + }, + width: { + 284: '17.75rem', + }, + height: { + 49: '3.063rem', + 110: '6.875rem', + 228: '14.25rem', + }, + minWidth: { + 284: '17.75rem', + }, + maxWidth: { + 1100: '68.75rem', + }, + minHeight: { + 56: '14rem', + 96: '24rem', + }, + fontFamily: { + sans: ['NanumGothic', 'Arial', 'sans-serif'], + ng: ['NanumGothic', 'sans-serif'], + 'ng-b': ['NanumGothicBold', 'sans-serif'], + 'ng-eb': ['NanumGothicExtraBold', 'sans-serif'], + 'ng-l': ['NanumGothicLight', 'sans-serif'], + lef: ['LeferiBaseRegular', 'sans-serif'], + 'lef-b': ['LeferiBaseBold', 'sans-serif'], + lato: ['LatoRegular', 'sans-serif'], + 'lato-b': ['LatoBold', 'sans-serif'], + 'lato-l': ['LatoLight', 'sans-serif'], + 'lato-sb': ['LatoSemibold', 'sans-serif'], + }, + }, + }, + plugins: [], +} \ No newline at end of file diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts deleted file mode 100644 index 1af3b8f0..00000000 --- a/frontend/tailwind.config.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Config } from 'tailwindcss' - -const config: Config = { - content: [ - './src/pages/**/*.{js,ts,jsx,tsx,mdx}', - './src/components/**/*.{js,ts,jsx,tsx,mdx}', - './src/app/**/*.{js,ts,jsx,tsx,mdx}', - ], - theme: { - extend: { - backgroundImage: { - 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', - 'gradient-conic': - 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', - }, - }, - }, - plugins: [], -} -export default config diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index e59724b2..bb05c098 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -22,6 +22,12 @@ "@/*": ["./src/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": ["next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "postcss.config.js", + "tailwind.config.cjs", + "next.config.js"], "exclude": ["node_modules"] -} +} \ No newline at end of file diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 8feb674f..7dd6de7f 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -26,7 +26,12 @@ dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": +"@eslint-community/regexpp@^4.5.1": + version "4.8.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.8.2.tgz#26585b7c0ba36362893d3a3c206ee0c57c389616" + integrity sha512-0MGxAVt1m/ZK+LTJp/j0qF7Hz97D9O/FH9Ms3ltnyIdDD57cbb1ACIQTkbHvNXtWDv5TPq7w5Kq56+cNukbo7g== + +"@eslint-community/regexpp@^4.6.1": version "4.8.1" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.8.1.tgz#8c4bb756cc2aa7eaf13cfa5e69c83afb3260c20c" integrity sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ== @@ -180,6 +185,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@remix-run/router@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.9.0.tgz#9033238b41c4cbe1e961eccb3f79e2c588328cf6" + integrity sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA== + "@rushstack/eslint-patch@^1.3.3": version "1.4.0" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.4.0.tgz#77e948b9760bd22736a5d26e335a690f76fda37b" @@ -234,20 +244,20 @@ integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== "@types/semver@^7.5.0": - version "7.5.2" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.2.tgz#31f6eec1ed7ec23f4f05608d3a2d381df041f564" - integrity sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw== + version "7.5.3" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.3.tgz#9a726e116beb26c24f1ccd6850201e1246122e04" + integrity sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw== "@typescript-eslint/eslint-plugin@^6.0.0": - version "6.7.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.2.tgz#f18cc75c9cceac8080a9dc2e7d166008c5207b9f" - integrity sha512-ooaHxlmSgZTM6CHYAFRlifqh1OAr3PAQEwi7lhYhaegbnXrnh7CDcHmc3+ihhbQC7H0i4JF0psI5ehzkF6Yl6Q== + version "6.7.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.3.tgz#d98046e9f7102d49a93d944d413c6055c47fafd7" + integrity sha512-vntq452UHNltxsaaN+L9WyuMch8bMd9CqJ3zhzTPXXidwbf5mqqKCVXEuvRZUqLJSTLeWE65lQwyXsRGnXkCTA== dependencies: "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "6.7.2" - "@typescript-eslint/type-utils" "6.7.2" - "@typescript-eslint/utils" "6.7.2" - "@typescript-eslint/visitor-keys" "6.7.2" + "@typescript-eslint/scope-manager" "6.7.3" + "@typescript-eslint/type-utils" "6.7.3" + "@typescript-eslint/utils" "6.7.3" + "@typescript-eslint/visitor-keys" "6.7.3" debug "^4.3.4" graphemer "^1.4.0" ignore "^5.2.4" @@ -255,7 +265,7 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/parser@^5.4.2 || ^6.0.0", "@typescript-eslint/parser@^6.0.0": +"@typescript-eslint/parser@^5.4.2 || ^6.0.0": version "6.7.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.7.2.tgz#e0ae93771441b9518e67d0660c79e3a105497af4" integrity sha512-KA3E4ox0ws+SPyxQf9iSI25R6b4Ne78ORhNHeVKrPQnoYsb9UhieoiRoJgrzgEeKGOXhcY1i8YtOeCHHTDa6Fw== @@ -266,6 +276,17 @@ "@typescript-eslint/visitor-keys" "6.7.2" debug "^4.3.4" +"@typescript-eslint/parser@^6.0.0": + version "6.7.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.7.3.tgz#aaf40092a32877439e5957e18f2d6a91c82cc2fd" + integrity sha512-TlutE+iep2o7R8Lf+yoer3zU6/0EAUc8QIBB3GYBc1KGz4c4TRm83xwXUZVPlZ6YCLss4r77jbu6j3sendJoiQ== + dependencies: + "@typescript-eslint/scope-manager" "6.7.3" + "@typescript-eslint/types" "6.7.3" + "@typescript-eslint/typescript-estree" "6.7.3" + "@typescript-eslint/visitor-keys" "6.7.3" + debug "^4.3.4" + "@typescript-eslint/scope-manager@6.7.2": version "6.7.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.2.tgz#cf59a2095d2f894770c94be489648ad1c78dc689" @@ -274,13 +295,21 @@ "@typescript-eslint/types" "6.7.2" "@typescript-eslint/visitor-keys" "6.7.2" -"@typescript-eslint/type-utils@6.7.2": - version "6.7.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.7.2.tgz#ed921c9db87d72fa2939fee242d700561454f367" - integrity sha512-36F4fOYIROYRl0qj95dYKx6kybddLtsbmPIYNK0OBeXv2j9L5nZ17j9jmfy+bIDHKQgn2EZX+cofsqi8NPATBQ== +"@typescript-eslint/scope-manager@6.7.3": + version "6.7.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.3.tgz#07e5709c9bdae3eaf216947433ef97b3b8b7d755" + integrity sha512-wOlo0QnEou9cHO2TdkJmzF7DFGvAKEnB82PuPNHpT8ZKKaZu6Bm63ugOTn9fXNJtvuDPanBc78lGUGGytJoVzQ== dependencies: - "@typescript-eslint/typescript-estree" "6.7.2" - "@typescript-eslint/utils" "6.7.2" + "@typescript-eslint/types" "6.7.3" + "@typescript-eslint/visitor-keys" "6.7.3" + +"@typescript-eslint/type-utils@6.7.3": + version "6.7.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.7.3.tgz#c2c165c135dda68a5e70074ade183f5ad68f3400" + integrity sha512-Fc68K0aTDrKIBvLnKTZ5Pf3MXK495YErrbHb1R6aTpfK5OdSFj0rVN7ib6Tx6ePrZ2gsjLqr0s98NG7l96KSQw== + dependencies: + "@typescript-eslint/typescript-estree" "6.7.3" + "@typescript-eslint/utils" "6.7.3" debug "^4.3.4" ts-api-utils "^1.0.1" @@ -289,6 +318,11 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.2.tgz#75a615a6dbeca09cafd102fe7f465da1d8a3c066" integrity sha512-flJYwMYgnUNDAN9/GAI3l8+wTmvTYdv64fcH8aoJK76Y+1FCZ08RtI5zDerM/FYT5DMkAc+19E4aLmd5KqdFyg== +"@typescript-eslint/types@6.7.3": + version "6.7.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.3.tgz#0402b5628a63f24f2dc9d4a678e9a92cc50ea3e9" + integrity sha512-4g+de6roB2NFcfkZb439tigpAMnvEIg3rIjWQ+EM7IBaYt/CdJt6em9BJ4h4UpdgaBWdmx2iWsafHTrqmgIPNw== + "@typescript-eslint/typescript-estree@6.7.2": version "6.7.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.2.tgz#ce5883c23b581a5caf878af641e49dd0349238c7" @@ -302,17 +336,30 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/utils@6.7.2": - version "6.7.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.7.2.tgz#b9ef0da6f04932167a9222cb4ac59cb187165ebf" - integrity sha512-ZCcBJug/TS6fXRTsoTkgnsvyWSiXwMNiPzBUani7hDidBdj1779qwM1FIAmpH4lvlOZNF3EScsxxuGifjpLSWQ== +"@typescript-eslint/typescript-estree@6.7.3": + version "6.7.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.3.tgz#ec5bb7ab4d3566818abaf0e4a8fa1958561b7279" + integrity sha512-YLQ3tJoS4VxLFYHTw21oe1/vIZPRqAO91z6Uv0Ss2BKm/Ag7/RVQBcXTGcXhgJMdA4U+HrKuY5gWlJlvoaKZ5g== + dependencies: + "@typescript-eslint/types" "6.7.3" + "@typescript-eslint/visitor-keys" "6.7.3" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@6.7.3": + version "6.7.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.7.3.tgz#96c655816c373135b07282d67407cb577f62e143" + integrity sha512-vzLkVder21GpWRrmSR9JxGZ5+ibIUSudXlW52qeKpzUEQhRSmyZiVDDj3crAth7+5tmN1ulvgKaCU2f/bPRCzg== dependencies: "@eslint-community/eslint-utils" "^4.4.0" "@types/json-schema" "^7.0.12" "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "6.7.2" - "@typescript-eslint/types" "6.7.2" - "@typescript-eslint/typescript-estree" "6.7.2" + "@typescript-eslint/scope-manager" "6.7.3" + "@typescript-eslint/types" "6.7.3" + "@typescript-eslint/typescript-estree" "6.7.3" semver "^7.5.4" "@typescript-eslint/visitor-keys@6.7.2": @@ -323,6 +370,14 @@ "@typescript-eslint/types" "6.7.2" eslint-visitor-keys "^3.4.1" +"@typescript-eslint/visitor-keys@6.7.3": + version "6.7.3" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.3.tgz#83809631ca12909bd2083558d2f93f5747deebb2" + integrity sha512-HEVXkU9IB+nk9o63CeICMHxFWbHWr3E1mpilIQBe9+7L/lH97rleFLVtYsfnWB+JVMaiFnEaxvknvmIzX+CqVg== + dependencies: + "@typescript-eslint/types" "6.7.3" + eslint-visitor-keys "^3.4.1" + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -578,7 +633,7 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chokidar@^3.5.3: +"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -1361,6 +1416,11 @@ ignore@^5.2.0, ignore@^5.2.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== +immutable@^4.0.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.4.tgz#2e07b33837b4bb7662f288c244d1ced1ef65a78f" + integrity sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA== + import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -1693,6 +1753,18 @@ loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lottie-react@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/lottie-react/-/lottie-react-2.4.0.tgz#f7249eee2b1deee70457a2d142194fdf2456e4bd" + integrity sha512-pDJGj+AQlnlyHvOHFK7vLdsDcvbuqvwPZdMlJ360wrzGFurXeKPr8SiRCjLf3LrNYKANQtSsh5dz9UYQHuqx4w== + dependencies: + lottie-web "^5.10.2" + +lottie-web@^5.10.2: + version "5.12.2" + resolved "https://registry.yarnpkg.com/lottie-web/-/lottie-web-5.12.2.tgz#579ca9fe6d3fd9e352571edd3c0be162492f68e5" + integrity sha512-uvhvYPC8kGPjXT3MyKMrL3JitEAmDMp30lVkuq/590Mw9ok6pWcFCwXJveo0t5uqYw1UREQHofD+jVpdjBv8wg== + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -2057,6 +2129,21 @@ react-is@^16.13.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-router-dom@^6.16.0: + version "6.16.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.16.0.tgz#86f24658da35eb66727e75ecbb1a029e33ee39d9" + integrity sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg== + dependencies: + "@remix-run/router" "1.9.0" + react-router "6.16.0" + +react-router@6.16.0: + version "6.16.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.16.0.tgz#abbf3d5bdc9c108c9b822a18be10ee004096fb81" + integrity sha512-VT4Mmc4jj5YyjpOi5jOf0I+TYzGpvzERy4ckNSvSh2RArv8LLoCxlsZ2D+tc7zgjxcY34oTz2hZaeX5RVprKqA== + dependencies: + "@remix-run/router" "1.9.0" + react@18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" @@ -2177,6 +2264,15 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" +sass@^1.68.0: + version "1.68.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.68.0.tgz#0034b0cc9a50248b7d1702ac166fd25990023669" + integrity sha512-Lmj9lM/fef0nQswm1J2HJcEsBUba4wgNx2fea6yJHODREoMFnwRpZydBnX/RjyXw2REIwdkbqE4hrTo4qfDBUA== + dependencies: + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" + scheduler@^0.23.0: version "0.23.0" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" @@ -2231,7 +2327,7 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -source-map-js@^1.0.2: +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== @@ -2332,6 +2428,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +swiper@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/swiper/-/swiper-10.3.0.tgz#a9f4d54a1860d76c2f85d7f09e2248bfe5f770a4" + integrity sha512-o81KZH4phL/dJxKFWpZ/zf78QxOyFMood+c86vJJO0ZeSOoniyzV8HWDsw12KOlQdm4WRxrc/6kfGImAcxcS3w== + tailwindcss@3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.3.tgz#90da807393a2859189e48e9e7000e6880a736daf" From f568beae2623ecd77784a2bae3e1ae1be367d617 Mon Sep 17 00:00:00 2001 From: rachel4w2 Date: Tue, 26 Sep 2023 20:07:45 +0900 Subject: [PATCH 06/12] =?UTF-8?q?feat=20:=20=EB=A9=94=EC=9D=B8=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - API 호출 방식 변경 : axios -> fetch - useNavigate -> useRouter - react-query 사용을 위한 queryProvider 컴포넌트 생성 --- frontend/package.json | 2 + frontend/src/app/layout.tsx | 5 +- frontend/src/app/page.tsx | 102 +++++++++++++++++++++++- frontend/src/components/main/Banner.tsx | 4 +- frontend/src/utils/atoms.ts | 8 ++ frontend/src/utils/queryProvider.tsx | 15 ++++ frontend/yarn.lock | 23 ++++++ 7 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 frontend/src/utils/queryProvider.tsx diff --git a/frontend/package.json b/frontend/package.json index b7e53caa..cf21ea2d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ "format:fix": "prettier --write --ignore-path .gitignore ." }, "dependencies": { + "@tanstack/react-query": "^4.35.3", "@types/node": "20.6.3", "@types/react": "18.2.22", "@types/react-dom": "18.2.7", @@ -29,6 +30,7 @@ "react-dom": "18.2.0", "react-router-dom": "^6.16.0", "recoil": "^0.7.7", + "recoil-persist": "^5.1.0", "sass": "^1.68.0", "swiper": "^10.3.0", "tailwindcss": "3.3.3", diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 12e59501..6d76d5ef 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -2,6 +2,7 @@ import './globals.css' import type { Metadata } from 'next' import { Inter } from 'next/font/google' import RecoilRootProvider from '../utils/recoilRootProvider' +import QueryProvider from '../utils/queryProvider' const inter = Inter({ subsets: ['latin'] }) @@ -18,7 +19,9 @@ export default function RootLayout({ return ( - {children} + + {children} + ) diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index bca7edf6..1aac6dc0 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,5 +1,105 @@ +'use client' + +import { useInfiniteQuery } from '@tanstack/react-query' +import { useEffect } from 'react' +import { useRouter } from 'next/navigation' +import { useRecoilValue } from 'recoil' + +import WriteIcon from '../../public/images/svg/pencil-square.svg' +// TODO: import NavBar from '../components/general/NavBar' import Banner from '../components/main/Banner' +// TODO: import ProjectCard from '../components/main/ProjectCard' +import searchTextState from '../utils/atoms' + +// TODO: type DataObject export default function Main() { - return + const searchText = useRecoilValue(searchTextState) + + const router = useRouter() // react-router-dom useNavigate 사용 선언 + + function toWrite() { + // react-router-dom을 이용한 글쓰기 페이지로 이동 함수 + router.push('/write') + } + + async function getData({ pageParam = 1 }) { + const params = { page: pageParam, size: 12 } + const res = await fetch( + `${process.env.NEXT_PUBLIC_BASE_URL}/projects/search`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params), + }, + ) + if (!res.ok) { + throw new Error('프로젝트 검색에 실패했습니다.') + } + + const data = await res.json() + + return data.data + } + + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } = + useInfiniteQuery(['projects'], getData, { + getNextPageParam: (lastPage, pages) => + lastPage.length < 12 ? undefined : pages.length + 1, + }) + + useEffect(() => { + if (!isFetchingNextPage) { + const handleScroll = () => { + if ( + document.documentElement.scrollHeight - + document.documentElement.scrollTop === + document.documentElement.clientHeight + ) { + fetchNextPage() + } + } + window.addEventListener('scroll', handleScroll) + } + }, [fetchNextPage, isFetchingNextPage, searchText]) + + if (status === 'loading') { + return Loading... + } + + if (status === 'error') { + return Error fetching data + } + + return ( +
+ {/* TODO: NavBar */} + +
+ {/* 프로젝트 공유 버튼 */} + + +
+ {/* All */} +
+ +
+ {/* TODO: ProjectCard */} + {hasNextPage && isFetchingNextPage && Loading more...} +
+
+
+ ) } diff --git a/frontend/src/components/main/Banner.tsx b/frontend/src/components/main/Banner.tsx index 8a185beb..e1fb1509 100644 --- a/frontend/src/components/main/Banner.tsx +++ b/frontend/src/components/main/Banner.tsx @@ -7,7 +7,7 @@ import 'swiper/css' import 'swiper/scss/pagination' import lotties from '../../utils/lotties' -function Banner() { +export default function Banner() { return (
) } - -export default Banner diff --git a/frontend/src/utils/atoms.ts b/frontend/src/utils/atoms.ts index e69de29b..4ed7ed4f 100644 --- a/frontend/src/utils/atoms.ts +++ b/frontend/src/utils/atoms.ts @@ -0,0 +1,8 @@ +import { atom } from 'recoil' + +const searchTextState = atom({ + key: 'searchTextState', + default: '', +}) + +export default searchTextState diff --git a/frontend/src/utils/queryProvider.tsx b/frontend/src/utils/queryProvider.tsx new file mode 100644 index 00000000..053fe9fa --- /dev/null +++ b/frontend/src/utils/queryProvider.tsx @@ -0,0 +1,15 @@ +'use client' + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' + +const queryClient = new QueryClient() + +export default function QueryProvider({ + children, +}: { + children: React.ReactNode +}) { + return ( + {children} + ) +} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 7dd6de7f..fd18de00 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -202,6 +202,19 @@ dependencies: tslib "^2.4.0" +"@tanstack/query-core@4.35.3": + version "4.35.3" + resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.35.3.tgz#1c127e66b4ad1beeac052db83a812d7cb67369e1" + integrity sha512-PS+WEjd9wzKTyNjjQymvcOe1yg8f3wYc6mD+vb6CKyZAKvu4sIJwryfqfBULITKCla7P9C4l5e9RXePHvZOZeQ== + +"@tanstack/react-query@^4.35.3": + version "4.35.3" + resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.35.3.tgz#03d726ef6a19d426166427c6539b003dd9606d1b" + integrity sha512-UgTPioip/rGG3EQilXfA2j4BJkhEQsR+KAbF+KIuvQ7j4MkgnTCJF01SfRpIRNtQTlEfz/+IL7+jP8WA8bFbsw== + dependencies: + "@tanstack/query-core" "4.35.3" + use-sync-external-store "^1.2.0" + "@types/json-schema@^7.0.12": version "7.0.13" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" @@ -2165,6 +2178,11 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +recoil-persist@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/recoil-persist/-/recoil-persist-5.1.0.tgz#c4232fe04f2e4b6afcc815baff56f2521f6dcde1" + integrity sha512-sew4k3uBVJjRWKCSFuBw07Y1p1pBOb0UxLJPxn4G2bX/9xNj+r2xlqYy/BRfyofR/ANfqBU04MIvulppU4ZC0w== + recoil@^0.7.7: version "0.7.7" resolved "https://registry.yarnpkg.com/recoil/-/recoil-0.7.7.tgz#c5f2c843224384c9c09e4a62c060fb4c1454dc8e" @@ -2598,6 +2616,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +use-sync-external-store@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" + integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== + util-deprecate@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" From a2a81450eeea35383683ee38e4dee6b0cdb1ad0e Mon Sep 17 00:00:00 2001 From: rachel4w2 Date: Fri, 29 Sep 2023 01:41:44 +0900 Subject: [PATCH 07/12] =?UTF-8?q?feat=20:=20=EB=A9=94=EC=9D=B8=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80,=20ProjectCard=20=EB=A7=88=EC=9D=B4=EA=B7=B8?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - img 태그 일부 'next/image'의 Image 컴포넌트로 변경 - API 호출 쿼리 파라미터 부분 수정 --- frontend/src/app/layout.tsx | 4 +- frontend/src/app/page.tsx | 49 ++++++-- .../general}/queryProvider.tsx | 0 .../general}/recoilRootProvider.tsx | 0 frontend/src/components/main/Banner.tsx | 14 +-- frontend/src/components/main/ProjectCard.tsx | 113 ++++++++++++++++++ frontend/src/utils/atoms.ts | 11 +- 7 files changed, 168 insertions(+), 23 deletions(-) rename frontend/src/{utils => components/general}/queryProvider.tsx (100%) rename frontend/src/{utils => components/general}/recoilRootProvider.tsx (100%) diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 6d76d5ef..8b0bbd1c 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,8 +1,8 @@ import './globals.css' import type { Metadata } from 'next' import { Inter } from 'next/font/google' -import RecoilRootProvider from '../utils/recoilRootProvider' -import QueryProvider from '../utils/queryProvider' +import RecoilRootProvider from '../components/general/recoilRootProvider' +import QueryProvider from '../components/general/queryProvider' const inter = Inter({ subsets: ['latin'] }) diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 1aac6dc0..e0157123 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -4,37 +4,46 @@ import { useInfiniteQuery } from '@tanstack/react-query' import { useEffect } from 'react' import { useRouter } from 'next/navigation' import { useRecoilValue } from 'recoil' +import Image from 'next/image' import WriteIcon from '../../public/images/svg/pencil-square.svg' // TODO: import NavBar from '../components/general/NavBar' import Banner from '../components/main/Banner' -// TODO: import ProjectCard from '../components/main/ProjectCard' -import searchTextState from '../utils/atoms' - -// TODO: type DataObject +import ProjectCard from '../components/main/ProjectCard' +import { searchTextState } from '../utils/atoms' + +type DataObject = { + id: number + createdAt: string + projectName: string + description: string + techTags: string[] + thumbNail: string +} export default function Main() { const searchText = useRecoilValue(searchTextState) - const router = useRouter() // react-router-dom useNavigate 사용 선언 + const router = useRouter() function toWrite() { - // react-router-dom을 이용한 글쓰기 페이지로 이동 함수 router.push('/write') } async function getData({ pageParam = 1 }) { - const params = { page: pageParam, size: 12 } + const params = new URLSearchParams() + params.set('page', String(pageParam)) + params.set('size', '12') + const res = await fetch( - `${process.env.NEXT_PUBLIC_BASE_URL}/projects/search`, + `${process.env.NEXT_PUBLIC_BASE_URL}/projects?${params.toString()}`, { - method: 'GET', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify(params), }, ) + if (!res.ok) { throw new Error('프로젝트 검색에 실패했습니다.') } @@ -87,7 +96,12 @@ export default function Main() { aria-label="toWritePage" type="button" > - WriteIcon + WriteIcon 프로젝트 공유 @@ -96,7 +110,18 @@ export default function Main() {
- {/* TODO: ProjectCard */} + {data.pages.map((group, i) => ( +
+ {group.map((item: DataObject) => ( +
+ +
+ ))} +
+ ))} {hasNextPage && isFetchingNextPage && Loading more...}
diff --git a/frontend/src/utils/queryProvider.tsx b/frontend/src/components/general/queryProvider.tsx similarity index 100% rename from frontend/src/utils/queryProvider.tsx rename to frontend/src/components/general/queryProvider.tsx diff --git a/frontend/src/utils/recoilRootProvider.tsx b/frontend/src/components/general/recoilRootProvider.tsx similarity index 100% rename from frontend/src/utils/recoilRootProvider.tsx rename to frontend/src/components/general/recoilRootProvider.tsx diff --git a/frontend/src/components/main/Banner.tsx b/frontend/src/components/main/Banner.tsx index e1fb1509..c5d9f34b 100644 --- a/frontend/src/components/main/Banner.tsx +++ b/frontend/src/components/main/Banner.tsx @@ -1,5 +1,3 @@ -'use client' - import Lottie from 'lottie-react' import { Pagination, Autoplay } from 'swiper/modules' import { Swiper, SwiperSlide } from 'swiper/react' @@ -20,12 +18,12 @@ export default function Banner() { autoplay={{ delay: 4000, disableOnInteraction: false }} loop > - +
-

+

프로젝트 기록과 공유의 공간, Graphy

-

+

Graphy와 함께 성장해보세요.

@@ -33,16 +31,16 @@ export default function Banner() {
-

+

프로젝트 공유하러 출발!

-

+

Graphy에서 누구나 프로젝트를 작성하고 공유할 수 있습니다. 이제 출발하세요!

diff --git a/frontend/src/components/main/ProjectCard.tsx b/frontend/src/components/main/ProjectCard.tsx index e69de29b..30e3b5f1 100644 --- a/frontend/src/components/main/ProjectCard.tsx +++ b/frontend/src/components/main/ProjectCard.tsx @@ -0,0 +1,113 @@ +'use client' + +import { useRouter } from 'next/navigation' +import { useRecoilState } from 'recoil' +import Image from 'next/image' +import project from '../../../public/images/png/project.png' + +import { projectIdState } from '../../utils/atoms' +import AllStacks from '../../utils/stacks' + +type ProjectCardProps = { + items: { + id: number + createdAt: string + projectName: string + description: string + techTags: string[] + thumbNail: string + } + index: number +} + +export default function ProjectCard({ items, index }: ProjectCardProps) { + const [, setProjectId] = useRecoilState(projectIdState) + const router = useRouter() + + function findImage(tag: string) { + return AllStacks.map((x) => x.image)[ + AllStacks.map((x) => x.name).findIndex((x) => x === tag) + ] + } + + function toRead() { + const urlToCheck = `${process.env.NEXT_PUBLIC_BASE_URL}/projects/${items.id}` + + if ( + !navigator.onLine && + 'serviceWorker' in navigator && + navigator.serviceWorker.controller + ) { + const messageChannel = new MessageChannel() + + messageChannel.port1.onmessage = (event) => { + if (event.data.hasMatch) { + router.push(`/read/${items.id}`) + setProjectId(items.id) + } else { + alert('오프라인 상태입니다. 네트워크 연결을 확인해주세요.') + } + } + + Promise.resolve().then(() => { + if (navigator.serviceWorker.controller) { + navigator.serviceWorker.controller.postMessage( + { action: 'cache-contains', url: urlToCheck }, + [messageChannel.port2], + ) + } + }) + } else { + router.push(`/read/${items.id}`) + setProjectId(items.id) + } + } + + return ( + + ) +} diff --git a/frontend/src/utils/atoms.ts b/frontend/src/utils/atoms.ts index 4ed7ed4f..61175e59 100644 --- a/frontend/src/utils/atoms.ts +++ b/frontend/src/utils/atoms.ts @@ -1,8 +1,17 @@ import { atom } from 'recoil' +import { recoilPersist } from 'recoil-persist' + +const { persistAtom } = recoilPersist() + +const projectIdState = atom({ + key: 'projectIdState', + default: 0, + effects_UNSTABLE: [persistAtom], +}) const searchTextState = atom({ key: 'searchTextState', default: '', }) -export default searchTextState +export { projectIdState, searchTextState } From 93ff220487798df0a8008aa7934f3b7aaa443115 Mon Sep 17 00:00:00 2001 From: rachel4w2 Date: Fri, 29 Sep 2023 18:56:01 +0900 Subject: [PATCH 08/12] =?UTF-8?q?refactor=20:=20recoil,=20react-query=20Pr?= =?UTF-8?q?ovider=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - recoilRootProvider, queryProvider 컴포넌트 -> Provider 컴포넌트 하나로 합치기 - react-router-dom 라이브러리 삭제 --- frontend/package.json | 1 - frontend/src/app/layout.tsx | 7 ++----- .../{queryProvider.tsx => Provider.tsx} | 5 ++++- .../components/general/recoilRootProvider.tsx | 11 ---------- frontend/yarn.lock | 20 ------------------- 5 files changed, 6 insertions(+), 38 deletions(-) rename frontend/src/components/general/{queryProvider.tsx => Provider.tsx} (60%) delete mode 100644 frontend/src/components/general/recoilRootProvider.tsx diff --git a/frontend/package.json b/frontend/package.json index cf21ea2d..bfebaddb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,7 +28,6 @@ "prettier": "^3.0.3", "react": "18.2.0", "react-dom": "18.2.0", - "react-router-dom": "^6.16.0", "recoil": "^0.7.7", "recoil-persist": "^5.1.0", "sass": "^1.68.0", diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 8b0bbd1c..b8733d8a 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -1,8 +1,7 @@ import './globals.css' import type { Metadata } from 'next' import { Inter } from 'next/font/google' -import RecoilRootProvider from '../components/general/recoilRootProvider' -import QueryProvider from '../components/general/queryProvider' +import Provider from '../components/general/Provider' const inter = Inter({ subsets: ['latin'] }) @@ -19,9 +18,7 @@ export default function RootLayout({ return ( - - {children} - + {children} ) diff --git a/frontend/src/components/general/queryProvider.tsx b/frontend/src/components/general/Provider.tsx similarity index 60% rename from frontend/src/components/general/queryProvider.tsx rename to frontend/src/components/general/Provider.tsx index 053fe9fa..c80ce5bc 100644 --- a/frontend/src/components/general/queryProvider.tsx +++ b/frontend/src/components/general/Provider.tsx @@ -1,6 +1,7 @@ 'use client' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { RecoilRoot } from 'recoil' const queryClient = new QueryClient() @@ -10,6 +11,8 @@ export default function QueryProvider({ children: React.ReactNode }) { return ( - {children} + + {children} + ) } diff --git a/frontend/src/components/general/recoilRootProvider.tsx b/frontend/src/components/general/recoilRootProvider.tsx deleted file mode 100644 index 32768a57..00000000 --- a/frontend/src/components/general/recoilRootProvider.tsx +++ /dev/null @@ -1,11 +0,0 @@ -'use client' - -import { RecoilRoot } from 'recoil' - -export default function RecoilRootProvider({ - children, -}: { - children: React.ReactNode -}) { - return {children} -} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index fd18de00..b690a250 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -185,11 +185,6 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@remix-run/router@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.9.0.tgz#9033238b41c4cbe1e961eccb3f79e2c588328cf6" - integrity sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA== - "@rushstack/eslint-patch@^1.3.3": version "1.4.0" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.4.0.tgz#77e948b9760bd22736a5d26e335a690f76fda37b" @@ -2142,21 +2137,6 @@ react-is@^16.13.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-router-dom@^6.16.0: - version "6.16.0" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.16.0.tgz#86f24658da35eb66727e75ecbb1a029e33ee39d9" - integrity sha512-aTfBLv3mk/gaKLxgRDUPbPw+s4Y/O+ma3rEN1u8EgEpLpPe6gNjIsWt9rxushMHHMb7mSwxRGdGlGdvmFsyPIg== - dependencies: - "@remix-run/router" "1.9.0" - react-router "6.16.0" - -react-router@6.16.0: - version "6.16.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.16.0.tgz#abbf3d5bdc9c108c9b822a18be10ee004096fb81" - integrity sha512-VT4Mmc4jj5YyjpOi5jOf0I+TYzGpvzERy4ckNSvSh2RArv8LLoCxlsZ2D+tc7zgjxcY34oTz2hZaeX5RVprKqA== - dependencies: - "@remix-run/router" "1.9.0" - react@18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" From 0896e930810d6f8f55a256a2192faa81974bcbe5 Mon Sep 17 00:00:00 2001 From: rachel4w2 Date: Fri, 29 Sep 2023 22:44:50 +0900 Subject: [PATCH 09/12] =?UTF-8?q?fix=20:=20prettier=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - yarn format:fix 실행 --- frontend/tailwind.config.cjs | 2 +- frontend/tsconfig.json | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/frontend/tailwind.config.cjs b/frontend/tailwind.config.cjs index 8fda9443..2dea46e5 100644 --- a/frontend/tailwind.config.cjs +++ b/frontend/tailwind.config.cjs @@ -55,4 +55,4 @@ module.exports = { }, }, plugins: [], -} \ No newline at end of file +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index bb05c098..5a90d522 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -22,12 +22,14 @@ "@/*": ["./src/*"] } }, - "include": ["next-env.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts", - "postcss.config.js", - "tailwind.config.cjs", - "next.config.js"], + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "postcss.config.js", + "tailwind.config.cjs", + "next.config.js" + ], "exclude": ["node_modules"] -} \ No newline at end of file +} From 2aea10d86a49fb4ee803f74dcb04710722a47b8a Mon Sep 17 00:00:00 2001 From: rachel4w2 Date: Sat, 30 Sep 2023 19:37:13 +0900 Subject: [PATCH 10/12] =?UTF-8?q?feat=20:=20=EC=9C=A0=EC=A0=80=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A7=88=EC=9D=B4?= =?UTF-8?q?=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - api 호출 방식 변경 axios → fetch - useNavigate → useRouter - react-router-dom의 URL 파라미터 라우팅 방식 → Next.js dynamic routing 방식 - useParams → Next.js dynamic routing - img 태그 → 'next/image’의 Image 태그로 변경 --- frontend/package.json | 4 +- .../(search)/search-user/[nickname]/page.tsx | 138 ++++++++++++++++++ .../src/app/(search)/search-user/page.tsx | 0 frontend/yarn.lock | 10 ++ 4 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 frontend/src/app/(search)/search-user/[nickname]/page.tsx delete mode 100644 frontend/src/app/(search)/search-user/page.tsx diff --git a/frontend/package.json b/frontend/package.json index 43008934..3f9df1d2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,11 +11,12 @@ "format:fix": "prettier --write --ignore-path .gitignore ." }, "dependencies": { - "@tanstack/react-query": "^4.35.3", "@hookform/resolvers": "^3.3.1", + "@tanstack/react-query": "^4.35.3", "@types/node": "20.6.3", "@types/react": "18.2.22", "@types/react-dom": "18.2.7", + "@types/uuid": "^9.0.4", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "autoprefixer": "10.4.15", @@ -36,6 +37,7 @@ "swiper": "^10.3.0", "tailwindcss": "3.3.3", "typescript": "5.2.2", + "uuid": "^9.0.1", "yup": "^1.3.0" }, "devDependencies": { diff --git a/frontend/src/app/(search)/search-user/[nickname]/page.tsx b/frontend/src/app/(search)/search-user/[nickname]/page.tsx new file mode 100644 index 00000000..8ef225f8 --- /dev/null +++ b/frontend/src/app/(search)/search-user/[nickname]/page.tsx @@ -0,0 +1,138 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useRouter } from 'next/navigation' +import { v4 as uuidv4 } from 'uuid' +import Image from 'next/image' + +import WriteIcon from '../../../../../public/images/svg/pencil-square.svg' +import ProfileIcon from '../../../../../public/images/svg/profileIcon.svg' +// TODO: import NavBar from '../../../components/general/NavBar' +import Banner from '../../../../components/main/Banner' + +type DataObject = { + nickname: string + email: string +} + +type ParamsType = { + params: { + nickname: string + } +} + +export default function SearchUserPage({ params }: ParamsType) { + const [data, setData] = useState([]) // 데이터를 담을 state 선언 + const [hoveredEmail, setHoveredEmail] = useState('') + const router = useRouter() + const accessToken = + typeof window !== 'undefined' ? sessionStorage.getItem('accessToken') : null + const persistToken = + typeof window !== 'undefined' ? localStorage.getItem('persistToken') : null + + function handleMouseEnter(email: string) { + setHoveredEmail(email) + } + + function handleMouseLeave() { + setHoveredEmail('') + } + + function toWrite() { + router.push('/write') + } + + async function getData() { + const res = await fetch( + `${process.env.NEXT_PUBLIC_BASE_URL}/members?nickname=${params.nickname}`, + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${accessToken || persistToken}`, + }, + }, + ) + + const resData = await res.json() + + if (resData.data.length === 0) { + setData([{ nickname: '검색 결과가 없습니다.', email: '' }]) + } else { + setData(resData.data) + } + + if (!res.ok) { + alert('검색 결과가 없습니다.') + router.push('/') + throw new Error('검색 결과가 없습니다.') + } + } + + useEffect(() => { + getData() + }, [params]) + + return ( +
+ {/* TODO: */} + + +
+ {/* 프로젝트 공유 버튼 */} + + +
+ {/* All */} +
+
+
+ {data.map((item) => ( +
+ {item.nickname === '검색 결과가 없습니다.' ? ( +
+ {item.nickname} +
+ ) : ( +
handleMouseEnter(item.email)} + onMouseLeave={handleMouseLeave} + > + ProfileIcon + + {item.nickname} + {hoveredEmail === item.email && ( + + {item.email} + + )} + +
+ )} +
+ ))} +
+
+
+
+ ) +} diff --git a/frontend/src/app/(search)/search-user/page.tsx b/frontend/src/app/(search)/search-user/page.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 0bb05eee..e4d706fc 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -261,6 +261,11 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.3.tgz#9a726e116beb26c24f1ccd6850201e1246122e04" integrity sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw== +"@types/uuid@^9.0.4": + version "9.0.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.4.tgz#e884a59338da907bda8d2ed03e01c5c49d036f1c" + integrity sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA== + "@typescript-eslint/eslint-plugin@^6.0.0": version "6.7.3" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.3.tgz#d98046e9f7102d49a93d944d413c6055c47fafd7" @@ -2636,6 +2641,11 @@ util-deprecate@^1.0.2: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +uuid@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" + integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== + watchpack@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" From dd29d9d74507392fc1f7f621836146e7b9d2f58f Mon Sep 17 00:00:00 2001 From: rachel4w2 Date: Sun, 1 Oct 2023 04:49:30 +0900 Subject: [PATCH 11/12] =?UTF-8?q?feat=20:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=EA=B2=80=EC=83=89=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - api 호출 방식 변경 axios → fetch - useNavigate → useRouter - react-router-dom의 URL 파라미터 라우팅 방식 → Next.js dynamic routing 방식 - useParams → Next.js dynamic routing --- .../(search)/search-post/[projectId]/page.tsx | 97 +++++++++++++++++++ .../src/app/(search)/search-post/page.tsx | 0 .../{[nickname] => [userName]}/page.tsx | 4 +- 3 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 frontend/src/app/(search)/search-post/[projectId]/page.tsx delete mode 100644 frontend/src/app/(search)/search-post/page.tsx rename frontend/src/app/(search)/search-user/{[nickname] => [userName]}/page.tsx (99%) diff --git a/frontend/src/app/(search)/search-post/[projectId]/page.tsx b/frontend/src/app/(search)/search-post/[projectId]/page.tsx new file mode 100644 index 00000000..15b6fdc3 --- /dev/null +++ b/frontend/src/app/(search)/search-post/[projectId]/page.tsx @@ -0,0 +1,97 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useRouter } from 'next/navigation' + +import WriteIcon from '../../../../../public/images/svg/pencil-square.svg' +// TODO: import NavBar from '../../../components/general/NavBar' +import Banner from '../../../../components/main/Banner' +import ProjectCard from '../../../../components/main/ProjectCard' + +type DataObject = { + id: number + createdAt: string + projectName: string + description: string + techTags: string[] + thumbNail: string +} + +type ParamsType = { + params: { + projectName: string + } +} + +function SearchProjectPage({ params }: ParamsType) { + const [data, setData] = useState([]) // 데이터를 담을 state 선언 + const router = useRouter() // react-router-dom useNavigate 사용 선언 + + function toWrite() { + router.push('/write') + } + + async function getData() { + const res = await fetch( + `${process.env.NEXT_PUBLIC_BASE_URL}/projects?${params.projectName}`, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ) + + const resData = await res.json() + + if (!res.ok) { + alert('검색 결과가 없습니다.') + router.push('/') + throw new Error('프로젝트 검색에 실패했습니다.') + } + setData(resData.data) + } + + useEffect(() => { + getData() + }, [params]) + + return ( +
+ {/* TODO: */} + + +
+ {/* 프로젝트 공유 버튼 */} + + +
+ {/* All */} +
+
+
+ {data.map((item, i) => ( +
+ +
+ ))} +
+
+
+
+ ) +} + +export default SearchProjectPage diff --git a/frontend/src/app/(search)/search-post/page.tsx b/frontend/src/app/(search)/search-post/page.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/src/app/(search)/search-user/[nickname]/page.tsx b/frontend/src/app/(search)/search-user/[userName]/page.tsx similarity index 99% rename from frontend/src/app/(search)/search-user/[nickname]/page.tsx rename to frontend/src/app/(search)/search-user/[userName]/page.tsx index 8ef225f8..ae4ce3de 100644 --- a/frontend/src/app/(search)/search-user/[nickname]/page.tsx +++ b/frontend/src/app/(search)/search-user/[userName]/page.tsx @@ -17,7 +17,7 @@ type DataObject = { type ParamsType = { params: { - nickname: string + userName: string } } @@ -44,7 +44,7 @@ export default function SearchUserPage({ params }: ParamsType) { async function getData() { const res = await fetch( - `${process.env.NEXT_PUBLIC_BASE_URL}/members?nickname=${params.nickname}`, + `${process.env.NEXT_PUBLIC_BASE_URL}/members?nickname=${params.userName}`, { headers: { 'Content-Type': 'application/json', From 8d7693ff9860405d462a969b5bb9b70354d18051 Mon Sep 17 00:00:00 2001 From: rachel4w2 Date: Sun, 1 Oct 2023 05:12:34 +0900 Subject: [PATCH 12/12] =?UTF-8?q?refactor=20:=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - projectName -> postName 변수명 변경 - export default SearchProjectPage -> export default function SearchProjectPage로 변경 --- .../search-post/{[projectId] => [postName]}/page.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) rename frontend/src/app/(search)/search-post/{[projectId] => [postName]}/page.tsx (93%) diff --git a/frontend/src/app/(search)/search-post/[projectId]/page.tsx b/frontend/src/app/(search)/search-post/[postName]/page.tsx similarity index 93% rename from frontend/src/app/(search)/search-post/[projectId]/page.tsx rename to frontend/src/app/(search)/search-post/[postName]/page.tsx index 15b6fdc3..d1774ce3 100644 --- a/frontend/src/app/(search)/search-post/[projectId]/page.tsx +++ b/frontend/src/app/(search)/search-post/[postName]/page.tsx @@ -19,11 +19,11 @@ type DataObject = { type ParamsType = { params: { - projectName: string + postName: string } } -function SearchProjectPage({ params }: ParamsType) { +export default function SearchProjectPage({ params }: ParamsType) { const [data, setData] = useState([]) // 데이터를 담을 state 선언 const router = useRouter() // react-router-dom useNavigate 사용 선언 @@ -33,7 +33,7 @@ function SearchProjectPage({ params }: ParamsType) { async function getData() { const res = await fetch( - `${process.env.NEXT_PUBLIC_BASE_URL}/projects?${params.projectName}`, + `${process.env.NEXT_PUBLIC_BASE_URL}/projects?${params.postName}`, { headers: { 'Content-Type': 'application/json', @@ -93,5 +93,3 @@ function SearchProjectPage({ params }: ParamsType) {
) } - -export default SearchProjectPage