diff --git a/backend/build.gradle b/backend/build.gradle index 20e42ed..2da7c03 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -36,6 +36,7 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-ui:1.6.14' implementation 'org.springframework.boot:spring-boot-starter-amqp' implementation 'org.springframework.boot:spring-boot-starter-websocket' + implementation 'org.springframework.boot:spring-boot-starter-webflux' //webClient implementation 'org.springframework.boot:spring-boot-starter-reactor-netty' implementation 'org.springframework.session:spring-session-jdbc' compileOnly 'org.projectlombok:lombok' diff --git a/backend/gradle/properties.java b/backend/gradle/properties.java deleted file mode 100644 index 58c0462..0000000 --- a/backend/gradle/properties.java +++ /dev/null @@ -1,2 +0,0 @@ -package gradle;public class properties { -} diff --git a/backend/gradle/wrapper/gradle-wrapper.jar b/backend/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 249e583..0000000 Binary files a/backend/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/backend/gradle/wrapper/gradle-wrapper.properties b/backend/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 070cb70..0000000 --- a/backend/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,5 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/backend/src/main/java/com/rising/backend/domain/comment/service/CommentService.java b/backend/src/main/java/com/rising/backend/domain/comment/service/CommentService.java index 2263912..c91cb3d 100644 --- a/backend/src/main/java/com/rising/backend/domain/comment/service/CommentService.java +++ b/backend/src/main/java/com/rising/backend/domain/comment/service/CommentService.java @@ -23,7 +23,6 @@ public class CommentService { private final CommentMapper commentMapper; public Comment createComment(CommentDto.CommentCreateRequest dto, Long userId) { - findByCommentId(dto.getParentId()); Comment entity = commentMapper.toCommentEntity(dto, userId); return commentRepository.save(entity); } diff --git a/backend/src/main/java/com/rising/backend/domain/post/controller/PostController.java b/backend/src/main/java/com/rising/backend/domain/post/controller/PostController.java index 9503868..41e033a 100644 --- a/backend/src/main/java/com/rising/backend/domain/post/controller/PostController.java +++ b/backend/src/main/java/com/rising/backend/domain/post/controller/PostController.java @@ -1,5 +1,6 @@ package com.rising.backend.domain.post.controller; +import com.rising.backend.domain.post.domain.PostType; import com.rising.backend.domain.post.dto.PostDto; import com.rising.backend.domain.post.service.PostService; import com.rising.backend.domain.user.domain.User; @@ -17,6 +18,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; + import java.util.List; import static com.rising.backend.domain.post.dto.PostDto.PostCreateRequest; @@ -42,13 +44,15 @@ public ResponseEntity create( } @GetMapping - public ResponseEntity getList(@PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) final Pageable pageable) { - List list = postService.pageList(pageable); - return ResponseEntity.ok(ResultResponse.of(ResultCode.POST_PAGINATION_SUCCESS, list)); + public ResponseEntity getList(@RequestParam(value = "type", required = false) PostType postType, @PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) final Pageable pageable) { + List result = null; + if (postType == null) + result = postService.pageList(pageable); + else + result = postService.getPostsByType(postType, pageable); + return ResponseEntity.ok(ResultResponse.of(ResultCode.POST_PAGINATION_SUCCESS, result)); } - - @LoginRequired @GetMapping("/{postId}/session") public ResponseEntity getSession( @@ -81,5 +85,23 @@ public ResponseEntity getPostListByUserId(@PathVariable Long use return ResponseEntity.ok(ResultResponse.of(ResultCode.POSTLIST_FIND_BY_USERID_SUCCESS, postList)); } + @PutMapping("/{postId}") + public ResponseEntity update( + @PathVariable Long postId, + @RequestBody PostDto.PostUpdateRequest updateRequest) { + postService.updatePost(postId, updateRequest); + return ResponseEntity.ok(ResultResponse.of(ResultCode.POST_UPDATE_SUCCESS)); + + } + + //멘토링 종료 + @PutMapping("/{postId}/solve") + public ResponseEntity solve( + @PathVariable Long postId, + @RequestBody PostDto.SolvedCodeRequest solvedCode) { + postService.solve(postId, solvedCode.getSolvedCode()); + return ResponseEntity.ok(ResultResponse.of(ResultCode.POST_SOLVED)); + + } -} +} \ No newline at end of file diff --git a/backend/src/main/java/com/rising/backend/domain/post/domain/Post.java b/backend/src/main/java/com/rising/backend/domain/post/domain/Post.java index bf35f08..982810e 100644 --- a/backend/src/main/java/com/rising/backend/domain/post/domain/Post.java +++ b/backend/src/main/java/com/rising/backend/domain/post/domain/Post.java @@ -1,8 +1,10 @@ package com.rising.backend.domain.post.domain; +import com.rising.backend.domain.post.dto.PostDto.PostUpdateRequest; import com.rising.backend.domain.user.domain.User; import com.rising.backend.global.domain.BaseEntity; import lombok.*; +import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.Where; @@ -31,31 +33,48 @@ public class Post extends BaseEntity { @NotBlank @Column(length = 100) + @Setter private String title; @NotBlank @Column(columnDefinition = "TEXT") + @Setter private String content; + @Column(length = 255) private String videoUrl; @Column(length = 255) private String sessionUrl; + @ColumnDefault("false") + private boolean isSolved; + + @Column(length = 1000) + @Setter + private String solvedCode; + @NotNull @Enumerated(EnumType.STRING) - private PostType type; + private PostType postType; @ManyToMany @JoinTable(name = "POST_TAG", joinColumns = @JoinColumn(name = "POST_ID"), inverseJoinColumns = @JoinColumn(name = "TAG_ID") ) + + @Setter private List tag = new ArrayList<>(); public void setTags(List tags) { this.tag = tags; } + + public void setSolved() { + this.isSolved = true; + } + } diff --git a/backend/src/main/java/com/rising/backend/domain/post/dto/PostDto.java b/backend/src/main/java/com/rising/backend/domain/post/dto/PostDto.java index be61c54..85a4f8e 100644 --- a/backend/src/main/java/com/rising/backend/domain/post/dto/PostDto.java +++ b/backend/src/main/java/com/rising/backend/domain/post/dto/PostDto.java @@ -30,6 +30,22 @@ public static class PostCreateRequest { private List tags = new ArrayList<>(); } + @AllArgsConstructor(access = AccessLevel.PRIVATE) + @NoArgsConstructor(access = AccessLevel.PRIVATE) + @Getter + public static class PostUpdateRequest { + private String title; + private String content; + private List tags = new ArrayList<>(); + } + + @AllArgsConstructor(access = AccessLevel.PRIVATE) + @NoArgsConstructor(access = AccessLevel.PRIVATE) + @Getter + public static class SolvedCodeRequest { + private String solvedCode; + } + @Builder @AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -52,6 +68,8 @@ public static class PostGetListResponse { @NotEmpty private Long commentCount; + private boolean isSolved; + private List tags; } @@ -76,6 +94,10 @@ public static class PostDetailResponse { private LocalDate created_at; + private boolean isSolved; + + private String solvedCode; + private List tags; } diff --git a/backend/src/main/java/com/rising/backend/domain/post/mapper/PostMapper.java b/backend/src/main/java/com/rising/backend/domain/post/mapper/PostMapper.java index 9e22fb8..1a64c39 100644 --- a/backend/src/main/java/com/rising/backend/domain/post/mapper/PostMapper.java +++ b/backend/src/main/java/com/rising/backend/domain/post/mapper/PostMapper.java @@ -30,7 +30,7 @@ public Post toPostEntity(PostCreateRequest postCreate, User loginUser) { .title(postCreate.getTitle()) .videoUrl(null) .sessionUrl(uuidConverter.toBase64(UUID.randomUUID())) - .type(postCreate.getType()).build(); + .postType(postCreate.getType()).build(); } public PostDto.PostDetailResponse toPostDto(Post post, List tags) { @@ -39,7 +39,9 @@ public PostDto.PostDetailResponse toPostDto(Post post, List tags) { .title(post.getTitle()) .content(post.getContent()) .videoUrl(post.getVideoUrl()) - .type(post.getType()) + .type(post.getPostType()) + .isSolved(post.isSolved()) + .solvedCode(post.getSolvedCode()) .tags(tags) .created_at(post.getCreatedAt().toLocalDate()) .build(); @@ -68,8 +70,9 @@ public PostGetListResponse toPostListResponse(Post post) { .content(post.getContent()) .created_at(post.getCreatedAt().toLocalDate()) .title(post.getTitle()) - .type(post.getType()) + .type(post.getPostType()) .tags(TagtoString(post.getTag())) + .isSolved(post.isSolved()) .commentCount(commentRepository.countByPost_Id(post.getId())) .build(); } diff --git a/backend/src/main/java/com/rising/backend/domain/post/repository/PostRepository.java b/backend/src/main/java/com/rising/backend/domain/post/repository/PostRepository.java index 8fb25e3..78be30c 100644 --- a/backend/src/main/java/com/rising/backend/domain/post/repository/PostRepository.java +++ b/backend/src/main/java/com/rising/backend/domain/post/repository/PostRepository.java @@ -1,7 +1,10 @@ package com.rising.backend.domain.post.repository; import com.rising.backend.domain.post.domain.Post; +import com.rising.backend.domain.post.domain.PostType; import com.rising.backend.domain.post.domain.Tag; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; @@ -11,4 +14,6 @@ public interface PostRepository extends JpaRepository { List findByUserId(Long userId); List findTagById(Long id); + + Page findByPostType(PostType postType, Pageable pageable); } diff --git a/backend/src/main/java/com/rising/backend/domain/post/service/PostService.java b/backend/src/main/java/com/rising/backend/domain/post/service/PostService.java index 6d218d9..3a200f7 100644 --- a/backend/src/main/java/com/rising/backend/domain/post/service/PostService.java +++ b/backend/src/main/java/com/rising/backend/domain/post/service/PostService.java @@ -1,6 +1,7 @@ package com.rising.backend.domain.post.service; import com.rising.backend.domain.post.domain.Post; +import com.rising.backend.domain.post.domain.PostType; import com.rising.backend.domain.post.domain.Tag; import com.rising.backend.domain.post.dto.PostDto; import com.rising.backend.domain.post.mapper.PostMapper; @@ -15,6 +16,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import java.util.Comparator; import java.util.List; import java.util.NoSuchElementException; import java.util.stream.Collectors; @@ -57,6 +59,11 @@ public List pageList(Pageable pageable) { return postMapper.toDtoPageList(postList).getContent(); } + public List getPostsByType(PostType postType, Pageable pageable) { + Page posts = postRepository.findByPostType(postType, pageable); + return postMapper.toDtoPageList(posts).getContent(); + } + public String getSessionUrl(Long postId, User user) { Post post = findPostById(postId); if (!checkIsAuthor(post, user)) { @@ -75,7 +82,10 @@ public PostDto.PostDetailResponse getPostDtoById(Long postId) { public List getPostListByUserId(Long userId) { List postList = postRepository.findByUserId(userId); - return postMapper.toDtoList(postList); + List sortedList = postList.stream() + .sorted(Comparator.comparing(Post::getCreatedAt).reversed()) + .collect(Collectors.toList()); + return postMapper.toDtoList(sortedList); } public Tag getTagByContent(String content) { @@ -85,4 +95,21 @@ public Tag getTagByContent(String content) { public void deletePostById(Long postId) { postRepository.deleteById(postId); } + + public void updatePost(Long postId, PostDto.PostUpdateRequest updateRequest) { + Post post = findPostById(postId); + + List tags = updateRequest.getTags().stream().map(t -> getTagByContent(t)) + .collect(Collectors.toList()); + + post.setTitle(updateRequest.getTitle()); + post.setContent(updateRequest.getContent()); + post.setTags(tags); + } + + public void solve(Long postId, String solvedCode) { + Post post = findPostById(postId); + post.setSolved(); //멘토링 완료 + post.setSolvedCode(solvedCode); + } } \ No newline at end of file diff --git a/backend/src/main/java/com/rising/backend/domain/sharecoding/controller/CodeController.java b/backend/src/main/java/com/rising/backend/domain/sharecoding/controller/CodeController.java index 8a649fd..beda90d 100644 --- a/backend/src/main/java/com/rising/backend/domain/sharecoding/controller/CodeController.java +++ b/backend/src/main/java/com/rising/backend/domain/sharecoding/controller/CodeController.java @@ -1,23 +1,34 @@ package com.rising.backend.domain.sharecoding.controller; import com.rising.backend.domain.sharecoding.domain.Operation; +import com.rising.backend.domain.sharecoding.dto.CodingDto; +import com.rising.backend.domain.sharecoding.service.CodeService; +import com.rising.backend.global.result.ResultCode; +import com.rising.backend.global.result.ResultResponse; +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.http.ResponseEntity; import org.springframework.messaging.handler.annotation.DestinationVariable; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; -import static com.rising.backend.global.constant.RabbitMQ.*; +import static com.rising.backend.global.constant.RabbitMQ.CODE_QUEUE_NAME; +import static com.rising.backend.global.constant.RabbitMQ.EXCHANGE_NAME; -@Controller +@RestController @RequiredArgsConstructor @Slf4j +@Tag(name = "SHARE CODE API") public class CodeController { private final RabbitTemplate rabbitTemplate; + private final CodeService codeService; @MessageMapping("code.message.{postId}") public void send(@RequestBody Operation operation, @DestinationVariable Long postId) { @@ -30,4 +41,13 @@ public void send(@RequestBody Operation operation, @DestinationVariable Long pos public void receive(Operation operation) { log.info("code send success"); } + + @PostMapping("api/v1/codes") + public ResponseEntity compile( + @RequestBody CodingDto.CompilerReqeust request) { + + CodingDto.CompilerResponse response = codeService.getCompileResponse(request); + return ResponseEntity.ok(ResultResponse.of(ResultCode.GET_COMPILE_RESULT, response)); + } + } diff --git a/backend/src/main/java/com/rising/backend/domain/sharecoding/dto/CodingDto.java b/backend/src/main/java/com/rising/backend/domain/sharecoding/dto/CodingDto.java new file mode 100644 index 0000000..1202937 --- /dev/null +++ b/backend/src/main/java/com/rising/backend/domain/sharecoding/dto/CodingDto.java @@ -0,0 +1,61 @@ +package com.rising.backend.domain.sharecoding.dto; + +import lombok.*; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +public class CodingDto { + @AllArgsConstructor(access = AccessLevel.PRIVATE) + @NoArgsConstructor(access = AccessLevel.PRIVATE) + @Getter + public static class CompilerReqeust { + @NotEmpty + private String language; + + @NotEmpty + private String version; + + @NotEmpty + private String code; + + private String input; + } + + @Builder + @AllArgsConstructor(access = AccessLevel.PRIVATE) + @NoArgsConstructor(access = AccessLevel.PRIVATE) + @Getter + public static class CompilerResponse { + + @NotNull + private String cpuTime; + + @NotNull + private String memory; + + @NotNull + private String output; + + @NotNull + private LanguageDto language; + + } + + @Builder + @AllArgsConstructor(access = AccessLevel.PRIVATE) + @NoArgsConstructor(access = AccessLevel.PRIVATE) + @Getter + public static class LanguageDto { + + @NotNull + private String id; + + @NotNull + private int version; + + @NotNull + private String version_name; + + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/rising/backend/domain/sharecoding/service/CodeService.java b/backend/src/main/java/com/rising/backend/domain/sharecoding/service/CodeService.java new file mode 100644 index 0000000..3a59967 --- /dev/null +++ b/backend/src/main/java/com/rising/backend/domain/sharecoding/service/CodeService.java @@ -0,0 +1,43 @@ +package com.rising.backend.domain.sharecoding.service; + +import com.rising.backend.domain.sharecoding.dto.CodingDto; +import com.rising.backend.global.config.CompileConfig; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.util.DefaultUriBuilderFactory; + +@Service +@RequiredArgsConstructor +@Slf4j +public class CodeService { + + private final CompileConfig compileConfig; + public CodingDto.CompilerResponse getCompileResponse(CodingDto.CompilerReqeust request) { + DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(compileConfig.COMPILE_API_URL); + + WebClient webclient = WebClient.builder() + .uriBuilderFactory(factory) + .build(); + + CodingDto.CompilerResponse responseMono = webclient.post() + .header("X-RapidAPI-Key", compileConfig.getKey()) + .header("X-RapidAPI-Host", compileConfig.COMPILE_API_HOST) + .header("content-type", "application/json") + .bodyValue(request)// await + .exchangeToMono(response -> { + Integer httpStatusCode = response.statusCode().value(); + HttpStatus httpStatus = HttpStatus.valueOf(httpStatusCode); + if (httpStatus.is2xxSuccessful()) { + return response.bodyToMono(CodingDto.CompilerResponse.class); + } else { + log.error("Exception occurred - status: {}, message: {}", httpStatus, httpStatus.getReasonPhrase()); + throw new RuntimeException(); + } + }).block(); + return responseMono; + } + +} diff --git a/backend/src/main/java/com/rising/backend/domain/user/controller/UserController.java b/backend/src/main/java/com/rising/backend/domain/user/controller/UserController.java index a452d32..800988a 100644 --- a/backend/src/main/java/com/rising/backend/domain/user/controller/UserController.java +++ b/backend/src/main/java/com/rising/backend/domain/user/controller/UserController.java @@ -56,7 +56,10 @@ public ResponseEntity login(@RequestBody @Valid UserDto.UserLogi loginService.login(member.getId(), request.getSession()); //세션에 로그인 정보 저장 return ResponseEntity.ok(ResultResponse.of(ResultCode.USER_LOGIN_SUCCESS, - UserDto.UserLoginResponse.builder().name(member.getName()).build())); + UserDto.UserLoginResponse.builder() + .name(member.getName()) + .id(member.getId()) + .build())); } @GetMapping("/logout") diff --git a/backend/src/main/java/com/rising/backend/domain/user/dto/UserDto.java b/backend/src/main/java/com/rising/backend/domain/user/dto/UserDto.java index 73ff7b7..4e68ced 100644 --- a/backend/src/main/java/com/rising/backend/domain/user/dto/UserDto.java +++ b/backend/src/main/java/com/rising/backend/domain/user/dto/UserDto.java @@ -47,6 +47,8 @@ public static class UserLoginResponse { @NotNull private String name; + + private Long id; } @Builder diff --git a/backend/src/main/java/com/rising/backend/global/config/CompileConfig.java b/backend/src/main/java/com/rising/backend/global/config/CompileConfig.java new file mode 100644 index 0000000..a316f44 --- /dev/null +++ b/backend/src/main/java/com/rising/backend/global/config/CompileConfig.java @@ -0,0 +1,19 @@ +package com.rising.backend.global.config; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; + +@Configuration +@Getter +@PropertySource("classpath:application-secret.yml") +public class CompileConfig { + + @Value("${compile.key}") + private String key; + + public static final String COMPILE_API_URL = "https://online-code-compiler.p.rapidapi.com/v1/"; + public static final String COMPILE_API_HOST = "online-code-compiler.p.rapidapi.com"; + +} diff --git a/backend/src/main/java/com/rising/backend/global/result/ResultCode.java b/backend/src/main/java/com/rising/backend/global/result/ResultCode.java index 69d4ae1..41f0f15 100644 --- a/backend/src/main/java/com/rising/backend/global/result/ResultCode.java +++ b/backend/src/main/java/com/rising/backend/global/result/ResultCode.java @@ -16,17 +16,21 @@ public enum ResultCode { //POST POST_CREATE_SUCCESS(201, "게시글 등록 성공"), - + POST_UPDATE_SUCCESS(200, "게시글 수정 성공"), POST_DELETE_SUCCESS(200, "게시글 삭제 성공"), POST_PAGINATION_SUCCESS(200, "게시글 리스트 조회 성공"), POST_FIND_SUCCESS(200, "게시글 id로 단일 게시글 조회 성공"), POSTLIST_FIND_BY_USERID_SUCCESS(200, "유저 id로 게시글 리스트 조회 성공"), + POST_SOLVED(200, "질문 해결"), //SESSION SESSION_GET_SUCCESS(201, "세션 반환 성공"), USER_NOT_POST_AUTHOR(400, "사용자가 게시글의 작성자가 아님"), + // CODE EDITOR + GET_COMPILE_RESULT(200, "컴파일 결과 반환 성공"), + //COMMENT COMMENT_CREATE_SUCCESS(201, "댓글 등록 성공"), COMMENT_GET_SUCCESS(200, "댓글 조회 성공"), diff --git a/frontend/src/components/NavBar/MypageNavBar.tsx b/frontend/src/components/NavBar/MypageNavBar.tsx new file mode 100644 index 0000000..21db993 --- /dev/null +++ b/frontend/src/components/NavBar/MypageNavBar.tsx @@ -0,0 +1,65 @@ +import logo from 'images/logo.png'; +import { useNavigate } from 'react-router-dom'; + +function QuesNavBar() { + const navigate = useNavigate(); + const goToQueslistPage = () => { + navigate('/queslistpage'); + }; + const goToMain = () => { + navigate('/mainpage'); + }; + const goToQues = () => { + navigate('/quespage'); + }; + const goToPrivateQues = () => { + navigate('/privatequespage'); + }; + const goToMypage = () => { + navigate('/mypage'); + }; + + return ( +
+
+ + + + + +
+
+ ); +} + +export default QuesNavBar; diff --git a/frontend/src/components/NavBar/NavBar.tsx b/frontend/src/components/NavBar/NavBar.tsx index 557967b..6059362 100644 --- a/frontend/src/components/NavBar/NavBar.tsx +++ b/frontend/src/components/NavBar/NavBar.tsx @@ -7,7 +7,7 @@ function NavBar() { navigate('/queslistpage'); }; const goToMain = () => { - navigate('/mainpage'); + navigate('/'); }; const goToQues = () => { navigate('/quespage'); diff --git a/frontend/src/components/NavBar/PrivateQuesNavBar.tsx b/frontend/src/components/NavBar/PrivateQuesNavBar.tsx index 6f99cde..0794639 100644 --- a/frontend/src/components/NavBar/PrivateQuesNavBar.tsx +++ b/frontend/src/components/NavBar/PrivateQuesNavBar.tsx @@ -50,13 +50,13 @@ function PrivateQuesNavBar() { > 질문게시판 - {/* */} + ); diff --git a/frontend/src/components/NavBar/QuesListNavBar.tsx b/frontend/src/components/NavBar/QuesListNavBar.tsx index d733eac..57a514a 100644 --- a/frontend/src/components/NavBar/QuesListNavBar.tsx +++ b/frontend/src/components/NavBar/QuesListNavBar.tsx @@ -50,13 +50,13 @@ function QuesListNavBar() { > 질문게시판 - {/* */} + ); diff --git a/frontend/src/components/NavBar/QuesNavBar.tsx b/frontend/src/components/NavBar/QuesNavBar.tsx index f98dc99..6266385 100644 --- a/frontend/src/components/NavBar/QuesNavBar.tsx +++ b/frontend/src/components/NavBar/QuesNavBar.tsx @@ -50,13 +50,13 @@ function QuesNavBar() { > 질문게시판 - {/* */} + ); diff --git a/frontend/src/components/Ques.tsx b/frontend/src/components/Ques.tsx index e7966f1..a8c3f31 100644 --- a/frontend/src/components/Ques.tsx +++ b/frontend/src/components/Ques.tsx @@ -9,13 +9,16 @@ interface Props { type: string; postId: number; tags: string[]; + solved: boolean; } -function Ques({ count, title, date, type, postId, tags }: Props) { +function Ques({ count, title, date, type, postId, tags, solved }: Props) { const navigate = useNavigate(); const goToAnsPage = () => { if (type === 'QUESTION') { navigate('/anspage', { state: { id: postId } }); + } else if (solved === true) { + navigate('/privateanscheckpage', { state: { id: postId } }); } else { navigate('/privateanspage', { state: { id: postId } }); } @@ -27,10 +30,20 @@ function Ques({ count, title, date, type, postId, tags }: Props) { {/* 답변 수 */}
-
-

{count}

-

답변

-
+ {type === 'QUESTION' ? ( +
+

{count}

+

답변

+
+ ) : ( +
+ {solved === true ? ( +

멘토링 완료

+ ) : ( +

멘토링 예정

+ )} +
+ )}
{/* 질문 제목 */} diff --git a/frontend/src/components/Select/OptionSelect.tsx b/frontend/src/components/Select/OptionSelect.tsx index 132277d..07068ab 100644 --- a/frontend/src/components/Select/OptionSelect.tsx +++ b/frontend/src/components/Select/OptionSelect.tsx @@ -1,38 +1,50 @@ -import Select, { StylesConfig } from 'react-select'; +import Select, { StylesConfig, ActionMeta } from 'react-select'; import chroma from 'chroma-js'; import { SelectOption, Options } from 'components/Select/QuesData'; -const colourStyles: StylesConfig = { +const colourStyles: StylesConfig = { control: (styles) => ({ ...styles, backgroundColor: 'white' }), - multiValue: (styles, { data }) => { + option: (styles, { data, isFocused, isSelected }) => { const color = chroma(data.color); + let backgroundColor = null; + let textColor = data.color; + + if (isSelected) { + backgroundColor = data.color; + textColor = chroma.contrast(color, 'white') > 2 ? 'white' : 'black'; + } else if (isFocused) { + backgroundColor = color.alpha(0.1).css(); + } else { + backgroundColor = 'white'; // set default color when not selected and not focused + } + return { ...styles, - backgroundColor: color.alpha(0.1).css(), + backgroundColor, + color: textColor, }; }, - multiValueLabel: (styles, { data }) => ({ - ...styles, - color: data.color, - }), - multiValueRemove: (styles, { data }) => ({ - ...styles, - color: data.color, - ':hover': { - backgroundColor: data.color, - color: 'white', - }, - }), }; -export default function OptionSelect() { +export default function OptionSelect({ setOption }: { setOption: (value: string) => void }) { + const handleOptionChange = ( + newValue: SelectOption | null, + actionMeta: ActionMeta + ) => { + if (newValue) { + setOption(newValue.value); + } else { + setOption(''); + } + }; + return ( CONTENT + {/* 컴파일러 실행 버튼 */} +
+ +
+ {/* 컴파일러 결과 */}
+
+
+
+              {compileResult}
+            
+
+ + RESULT +
+
+
diff --git a/frontend/src/page/MyPage.tsx b/frontend/src/page/MyPage.tsx index 88f62d5..57b484a 100644 --- a/frontend/src/page/MyPage.tsx +++ b/frontend/src/page/MyPage.tsx @@ -1,7 +1,7 @@ import 'tailwindcss/tailwind.css'; import 'utils/pageStyle.css'; import ColorSystem from 'utils/ColorSystem'; -import NavBar from 'components/NavBar/NavBar'; +import MypageNavBar from 'components/NavBar/MypageNavBar'; import { useEffect, useState } from 'react'; import Box from '@mui/material/Box'; import Tab from '@mui/material/Tab'; @@ -10,11 +10,11 @@ import TabList from '@mui/lab/TabList'; import TabPanel from '@mui/lab/TabPanel'; import BasicProfile from 'images/BasicProfile.png'; import pencil from 'images/pencil.png'; -// import Tag from 'components/Tags/Tag'; import ChatBox from 'components/ChatBox'; import Profile from 'components/Profile'; import axios from 'axios'; import { useDispatch } from 'react-redux'; +import Ques from 'components/Ques'; import { setUserName } from '../components/redux/userSlice'; function MyPage() { @@ -27,6 +27,8 @@ function MyPage() { const [menteeChatInfo, setMenteeChatInfo] = useState([]); const [mentorCharInfo, setMentorChatInfo] = useState([]); const [profileInfo, setProfileInfo] = useState(''); + const [userId, setUserId] = useState(0); + const [userPostInfo, setUserPostInfo] = useState([]); const dispatch = useDispatch(); @@ -64,6 +66,7 @@ function MyPage() { .get(`/api/v1/users/info`) .then((res) => { setProfileInfo(res.data.data.name); + setUserId(res.data.data.userId); dispatch(setUserName(res.data.data.name)); }) .catch((error) => { @@ -72,13 +75,27 @@ function MyPage() { })(); }, []); + useEffect(() => { + (async () => { + await axios + .get(`/api/v1/posts/mypages/${userId}`) + .then((res) => { + console.log(res.data.data); + setUserPostInfo(res.data.data); + }) + .catch((error) => { + console.log(error); + }); + })(); + }, [userId]); + return (
{/* 상단바 */} - + {/* 유저 이름과 프로필 사진 */}
@@ -119,7 +136,18 @@ function MyPage() { >
- 추후 업데이트 될 예정입니다. + {userPostInfo.map((data: any) => ( + + ))}
diff --git a/frontend/src/page/PrivateAnsCheckPage.tsx b/frontend/src/page/PrivateAnsCheckPage.tsx index 24caf3b..145b963 100644 --- a/frontend/src/page/PrivateAnsCheckPage.tsx +++ b/frontend/src/page/PrivateAnsCheckPage.tsx @@ -1,7 +1,7 @@ import 'tailwindcss/tailwind.css'; import 'utils/pageStyle.css'; import ColorSystem from 'utils/ColorSystem'; -import NavBar from 'components/NavBar/NavBar'; +import NavBar from 'components/NavBar/QuesListNavBar'; import Tag from 'components/Tags/Tag'; import Date from 'components/Tags/Date'; import TitleIndex from 'components/Index/AnsTitleIndex'; @@ -10,15 +10,31 @@ import EndIndex from 'components/Index/EndIndex'; import { useEffect, useState } from 'react'; import axios from 'axios'; import EditorViewer from 'components/Editor/EditorViewer'; +import MonacoEditor from 'react-monaco-editor'; +import 'monaco-editor/esm/vs/basic-languages/python/python.contribution'; +import 'monaco-editor/esm/vs/basic-languages/java/java.contribution'; +import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution'; +import 'monaco-editor/esm/vs/basic-languages/cpp/cpp.contribution'; +import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution'; +import { useLocation } from 'react-router-dom'; function PrivateAnsCheckPage() { - localStorage.getItem('postId'); - const postId = localStorage.getItem('postId'); + const location = useLocation(); + const state = location.state as { + id: number; + roomId: number; + }; + const [postId, setPostId] = useState(state.id); + + useEffect(() => { + setPostId(state.id); + }, [state.id]); const [title, setTitle] = useState(''); const [content, setContent] = useState(''); const [tags, setTags] = useState([]); const [date, setDate] = useState(''); + const [code, setCode] = useState(''); useEffect(() => { (async () => { @@ -30,6 +46,16 @@ function PrivateAnsCheckPage() { setContent(res.data.data.content); setTags(res.data.data.tags); setDate(res.data.data.created_at); + setCode(res.data.data.solvedCode); + + // 언어 태그를 찾아 초기값으로 설정하기 + const languageTags = ['Python', 'Java', 'JavaScript', 'TypeScript']; + const language = res.data.data.tags.find((tag: string) => + languageTags.includes(tag), + ); + if (language) { + setSelectedLanguage(language.toLowerCase()); + } }) .catch((error) => { console.log(error); @@ -37,6 +63,20 @@ function PrivateAnsCheckPage() { })(); }, []); + const [selectedLanguage, setSelectedLanguage] = useState('python'); + + const handleLanguageChange = (e: React.ChangeEvent) => { + const { value } = e.target; + + if (value === 'spring') { + setSelectedLanguage('java'); + } else if (value === 'react') { + setSelectedLanguage('javascript'); + } else { + setSelectedLanguage(value); + } + }; + return (
-
+ {/* rounded-xl h-[20rem] w-full mx-1 my-2 pt-1.5 px-1 bg-white border-4 border-violet-300 overflow-y-auto */} +
{/* 추후 구현 예정 */} - - 기록된 영상이 업로드 될 예정입니다. - + +
- RECORDED VIDEO + CODE
diff --git a/frontend/src/page/PrivateAnsPage.tsx b/frontend/src/page/PrivateAnsPage.tsx index 15c1647..6955abf 100644 --- a/frontend/src/page/PrivateAnsPage.tsx +++ b/frontend/src/page/PrivateAnsPage.tsx @@ -4,7 +4,7 @@ import 'tailwindcss/tailwind.css'; import 'utils/pageStyle.css'; import ColorSystem from 'utils/ColorSystem'; -import QuesNavBar from 'components/NavBar/QuesNavBar'; +import QuesNavBar from 'components/NavBar/QuesListNavBar'; import Tag from 'components/Tags/Tag'; import Date from 'components/Tags/Date'; import TitleIndex from 'components/Index/AnsTitleIndex'; @@ -19,7 +19,7 @@ import { useDispatch } from 'react-redux'; import { setUserName } from '../components/redux/userSlice'; export type ChatError = { - errorMessage: string; + businessCode: string; }; function PrivateAnsPage() { @@ -53,7 +53,7 @@ function PrivateAnsPage() { .catch((error) => { if ( (error as AxiosError).response?.data - .errorMessage !== null + .businessCode === 'CR002' ) { alert('질문자는 마이페이지의 채팅방을 이용해주세요:)'); navigate('/mypage'); diff --git a/frontend/src/page/QuesListPage.tsx b/frontend/src/page/QuesListPage.tsx index 94cbc77..350e36d 100644 --- a/frontend/src/page/QuesListPage.tsx +++ b/frontend/src/page/QuesListPage.tsx @@ -3,7 +3,7 @@ import 'utils/pageStyle.css'; import ColorSystem from 'utils/ColorSystem'; import QuesListNavBar from 'components/NavBar/QuesListNavBar'; import Ques from 'components/Ques'; -import KeyWordOptionSelect from 'components/Select/KeyWordOptionSelect'; +// import KeyWordOptionSelect from 'components/Select/KeyWordOptionSelect'; import OptionSelect from 'components/Select/OptionSelect'; import { useEffect, useState } from 'react'; import axios from 'axios'; @@ -15,6 +15,8 @@ function QuesListPage() { const [sumId, setSumId] = useState(0); const [pageCount, setPageCount] = useState(1); const [searchParams, setSearchParams] = useSearchParams(); + const [option, setOption] = useState(null); + const [page, setPage] = useState(1); useEffect(() => { (async () => { @@ -28,21 +30,27 @@ function QuesListPage() { console.log(error); }); })(); - const pageNumber = searchParams.get('page'); - (async () => { - await axios - .get( - `/api/v1/posts?page=${pageNumber}`, - ) - .then((res) => { - setQuesInfo(res.data.data); - }) - .catch((error) => { - console.log(error); - }); - })(); }, []); + useEffect(() => { + const fetchQuesInfo = async () => { + try { + let url = `/api/v1/posts?page=${page}&size=10`; + + if (option) { + url += `&type=${option}`; + } + + const res = await axios.get(url); + setQuesInfo(res.data.data); + } catch (error) { + console.error(error); + } + }; + + fetchQuesInfo(); + }, [option, page]); + useEffect(() => { (async () => { await axios @@ -61,18 +69,15 @@ function QuesListPage() { className="h-screen" style={{ backgroundColor: ColorSystem.MainColor.Primary }} > - {/* 상단바 */}
- {/* 필터 */}
-
+ {/*
-
- +
*/} +
- {/* 질문 리스트 */}
{ e.preventDefault(); - window.location.href = `/queslistpage?page=${value}`; + setPage(value); + setSearchParams((prevParams) => { + return { ...prevParams, page: value.toString(), option: option || undefined }; + }); }} showFirstButton showLastButton diff --git a/frontend/src/page/SignUpPage.tsx b/frontend/src/page/SignUpPage.tsx index b5f8f71..10935c2 100644 --- a/frontend/src/page/SignUpPage.tsx +++ b/frontend/src/page/SignUpPage.tsx @@ -24,8 +24,8 @@ function SignUpPage() { }) .then((response) => { console.log('회원가입 완료!'); - console.log('유저 이름 :', name); - console.log('유저 이메일 :', username); + console.log('유저 이름:', name); + console.log('유저 이메일:', username); // eslint-disable-next-line no-alert alert('회원가입 성공!'); navigate('/login'); @@ -38,10 +38,7 @@ function SignUpPage() { }; return ( -
+
{/* 상단바 */} {/* 회원가입 틀 */} @@ -100,14 +97,20 @@ function SignUpPage() { required />
-
-
- {password !== passwordConfirm && ( -
비밀번호가 일치하지 않습니다!
- )} - {password === passwordConfirm &&
비밀번호 일치!
} -
+
+ {password !== passwordConfirm && ( +
+ 비밀번호가 일치하지 않습니다! +
+ )} + {password === passwordConfirm && ( +
+ 비밀번호 일치! +
+ )} +