diff --git a/withins_server/api/src/main/java/com/withins/api/auth/auth2/PrincipalDetails.java b/withins_server/api/src/main/java/com/withins/api/auth/auth2/PrincipalDetails.java index 4c75f9a..509782e 100644 --- a/withins_server/api/src/main/java/com/withins/api/auth/auth2/PrincipalDetails.java +++ b/withins_server/api/src/main/java/com/withins/api/auth/auth2/PrincipalDetails.java @@ -1,6 +1,6 @@ package com.withins.api.auth.auth2; -import com.withins.core.user.entity.OrgUser; +import com.withins.core.user.entity.FormUser; import com.withins.core.user.entity.User; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -37,7 +37,7 @@ public String getUsername() { @Override public String getPassword() { return switch (user) { - case OrgUser orgUser -> orgUser.getPassword(); + case FormUser formUser -> formUser.getPassword(); default -> null; }; } diff --git a/withins_server/api/src/main/java/com/withins/api/controller/SignupController.java b/withins_server/api/src/main/java/com/withins/api/controller/SignupController.java new file mode 100644 index 0000000..ccf6609 --- /dev/null +++ b/withins_server/api/src/main/java/com/withins/api/controller/SignupController.java @@ -0,0 +1,42 @@ +package com.withins.api.controller; + +import com.withins.core.signup.RequestSignup; +import com.withins.core.signup.ResponseSignup; +import com.withins.core.user.service.SignupService; +import com.withins.core.user.service.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindingResult; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +//@RestController +//@RequestMapping("/api/v1/signup") +//@RequiredArgsConstructor +//public class SignupController { +// +// private final UserService userService; +// private final SignupService signupService; +// +// @GetMapping("/exists/username") +// public ResponseEntity existsUsername(@RequestParam String username) { +// boolean exists = userService.readByUsername(username).isPresent(); +// return ResponseEntity.ok(exists); +// } +// +// @GetMapping("/exists/email") +// public ResponseEntity existsEmail(@RequestParam String email) { +// boolean exists = userService.readByEmail(email).isPresent(); +// return ResponseEntity.ok(exists); +// } +// +// @PostMapping +// public ResponseEntity signup(@Validated @RequestBody RequestSignup requestSignup, BindingResult result) { +// if (result.hasErrors()) { +// return ResponseEntity.badRequest().body(ResponseSignup.error(result)); +// } +// ResponseSignup signup = signupService.signup(requestSignup); +// return ResponseEntity.ok(signup); +// } +// +//} diff --git a/withins_server/config b/withins_server/config index 6be5d6a..5bd2a17 160000 --- a/withins_server/config +++ b/withins_server/config @@ -1 +1 @@ -Subproject commit 6be5d6a055acff806a00eb686cbeb9dd8933b952 +Subproject commit 5bd2a17104e2d080de3f9b2dc1da52cacc5f6f87 diff --git a/withins_server/core/build.gradle b/withins_server/core/build.gradle index 5118ab0..c946e3f 100644 --- a/withins_server/core/build.gradle +++ b/withins_server/core/build.gradle @@ -1,6 +1,7 @@ dependencies { implementation("org.springframework:spring-context") implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation("org.springframework.boot:spring-boot-starter-validation") runtimeOnly("mysql:mysql-connector-java:${project.property('mysqlVersion')}") implementation("com.querydsl:querydsl-jpa:${project.property('querydslVersion')}:jakarta") diff --git a/withins_server/core/src/main/java/com/withins/core/signup/RequestSignup.java b/withins_server/core/src/main/java/com/withins/core/signup/RequestSignup.java new file mode 100644 index 0000000..3d0c9ac --- /dev/null +++ b/withins_server/core/src/main/java/com/withins/core/signup/RequestSignup.java @@ -0,0 +1,31 @@ +package com.withins.core.signup; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Setter +@Getter +@ToString +public class RequestSignup { + + @NotBlank(message = "사용자명을 입력해주세요.") + @Size(min = 4, max = 20, message = "4 ~ 20자의 영문,숫자만 사용가능합니다.") + @Pattern(regexp = "^[a-zA-Z0-9]+$", message = "4 ~ 20자의 영문,숫자만 사용가능합니다.") + private String username; + + @NotBlank(message = "비밀번호를 입력해주세요.") + @Size(min = 8, message = "8자리 이상 영문 대소문자, 숫자, 특수문자를 조합해주세요.") + @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?]).+$", + message = "8자리 이상 영문 대소문자, 숫자, 특수문자를 조합해주세요.") + private String password; + + @NotBlank(message = "이메일을 입력해주세요.") + @Email(message = "올바른 이메일 형식이 아닙니다.") + private String email; + +} diff --git a/withins_server/core/src/main/java/com/withins/core/signup/ResponseSignup.java b/withins_server/core/src/main/java/com/withins/core/signup/ResponseSignup.java new file mode 100644 index 0000000..46bfd9a --- /dev/null +++ b/withins_server/core/src/main/java/com/withins/core/signup/ResponseSignup.java @@ -0,0 +1,44 @@ +package com.withins.core.signup; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; + +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; + +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class ResponseSignup { + + private final String result; + private final String message; + private final Map errors; + + public static ResponseSignup error(BindingResult result) { + return new ResponseSignup( + "ERROR", + "검증에 실패했습니다.", + result.getFieldErrors().stream().collect(Collectors.toMap( + FieldError::getField, + FieldError::getDefaultMessage, + (existing, replacement) -> replacement + )) + ); + } + public static ResponseSignup error(Map result) { + return new ResponseSignup( + "ERROR", + "검증에 실패했습니다.", + result + ); + } + + public static ResponseSignup ok() { + return new ResponseSignup("OK", "검증 성공", Collections.emptyMap()); + } + +} diff --git a/withins_server/core/src/main/java/com/withins/core/user/component/SignupAuthenticator.java b/withins_server/core/src/main/java/com/withins/core/user/component/SignupAuthenticator.java new file mode 100644 index 0000000..0c0bd4f --- /dev/null +++ b/withins_server/core/src/main/java/com/withins/core/user/component/SignupAuthenticator.java @@ -0,0 +1,44 @@ +package com.withins.core.user.component; + +import com.withins.core.signup.RequestSignup; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class SignupAuthenticator { + + private final Map requestSignupMap = new ConcurrentHashMap<>(); + private static final int EXPIRATION_TIME = 60 * 60; // 1시간 + + public String registry(RequestSignup requestSignup) { + String uuid = UUID.randomUUID().toString(); + requestSignupMap.put(uuid, SignupHolder.of(requestSignup)); + return uuid; + } + + private void clearExpired() { + Date date = new Date(); + + } + + @Getter + @AllArgsConstructor(access = AccessLevel.PRIVATE) + private static class SignupHolder { + private final Date expiration; + private final RequestSignup requestSignup; + + static SignupHolder of(RequestSignup signup) { + return new SignupHolder( + new Date(System.currentTimeMillis() + EXPIRATION_TIME), + signup + ); + } + } +} diff --git a/withins_server/core/src/main/java/com/withins/core/user/component/UserReader.java b/withins_server/core/src/main/java/com/withins/core/user/component/UserReader.java index 8b8286f..193897c 100644 --- a/withins_server/core/src/main/java/com/withins/core/user/component/UserReader.java +++ b/withins_server/core/src/main/java/com/withins/core/user/component/UserReader.java @@ -2,6 +2,7 @@ import com.withins.core.exception.EntityNotFoundException; import com.withins.core.user.entity.User; +import com.withins.core.user.repository.FormUserRepository; import com.withins.core.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -15,10 +16,14 @@ public class UserReader { private final UserRepository userRepository; + private final FormUserRepository formUserRepository; public Optional readByUsername(final String username) { return userRepository.findByUsername(username); } + public Optional readByEmail(final String email) { + return formUserRepository.findByEmail(email); + } public User read(final Long userId) { log.debug("유저 조회 실행 - id={}", userId); diff --git a/withins_server/core/src/main/java/com/withins/core/user/entity/OrgUser.java b/withins_server/core/src/main/java/com/withins/core/user/entity/FormUser.java similarity index 73% rename from withins_server/core/src/main/java/com/withins/core/user/entity/OrgUser.java rename to withins_server/core/src/main/java/com/withins/core/user/entity/FormUser.java index 41f5180..ee395de 100644 --- a/withins_server/core/src/main/java/com/withins/core/user/entity/OrgUser.java +++ b/withins_server/core/src/main/java/com/withins/core/user/entity/FormUser.java @@ -7,18 +7,18 @@ @Entity -@DiscriminatorValue("org") +@DiscriminatorValue("form") @SuperBuilder @Getter @NoArgsConstructor -public final class OrgUser extends User { +public final class FormUser extends User { private String password; + private String email; @Override protected String getDiscriminatorValue() { - return "org"; + return "form"; } - } diff --git a/withins_server/core/src/main/java/com/withins/core/user/repository/FormUserRepository.java b/withins_server/core/src/main/java/com/withins/core/user/repository/FormUserRepository.java new file mode 100644 index 0000000..0444106 --- /dev/null +++ b/withins_server/core/src/main/java/com/withins/core/user/repository/FormUserRepository.java @@ -0,0 +1,12 @@ +package com.withins.core.user.repository; + +import com.withins.core.user.entity.FormUser; +import com.withins.core.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface FormUserRepository extends JpaRepository { + + Optional findByEmail(String email); +} diff --git a/withins_server/core/src/main/java/com/withins/core/user/service/AuthService.java b/withins_server/core/src/main/java/com/withins/core/user/service/AuthService.java index ab523ed..9d63924 100644 --- a/withins_server/core/src/main/java/com/withins/core/user/service/AuthService.java +++ b/withins_server/core/src/main/java/com/withins/core/user/service/AuthService.java @@ -2,7 +2,7 @@ import com.withins.core.user.component.UserReader; import com.withins.core.user.dto.UserAuthToken; -import com.withins.core.user.entity.OrgUser; +import com.withins.core.user.entity.FormUser; import com.withins.core.user.entity.SocialUser; import com.withins.core.user.entity.User; import lombok.RequiredArgsConstructor; @@ -25,7 +25,7 @@ public UserAuthToken getUserAuthToken(Long userId) { private String getProvider(User user) { return switch (user) { - case OrgUser orgUser -> "LOCAL"; + case FormUser formUser -> "LOCAL"; case SocialUser socialUser -> socialUser.getProvider().name(); default -> throw new IllegalStateException("Unexpected value: " + user); }; diff --git a/withins_server/core/src/main/java/com/withins/core/user/service/SignupService.java b/withins_server/core/src/main/java/com/withins/core/user/service/SignupService.java new file mode 100644 index 0000000..30fe5f0 --- /dev/null +++ b/withins_server/core/src/main/java/com/withins/core/user/service/SignupService.java @@ -0,0 +1,40 @@ +package com.withins.core.user.service; + +import com.withins.core.signup.RequestSignup; +import com.withins.core.signup.ResponseSignup; +import com.withins.core.user.component.UserReader; +import com.withins.core.user.component.UserWriter; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class SignupService { + + private final UserReader userReader; + private final UserWriter userWriter; + + public ResponseSignup signup(RequestSignup requestSignup) { + Map errors = validate(requestSignup); + if (!errors.isEmpty()) { + return ResponseSignup.error(errors); + } + return null; + } + + private Map validate(RequestSignup requestSignup) { + Map errors = new HashMap<>(); + boolean isUsernameExists = userReader.readByUsername(requestSignup.getUsername()).isPresent(); + if (isUsernameExists) { + errors.put("username", "이미 사용중인 아이디입니다."); + } + boolean isEmailExists = userReader.readByEmail(requestSignup.getEmail()).isPresent(); + if (isEmailExists) { + errors.put("email", "이미 등록된 이메일입니다."); + } + return errors; + } +} diff --git a/withins_server/core/src/main/java/com/withins/core/user/service/UserService.java b/withins_server/core/src/main/java/com/withins/core/user/service/UserService.java index 32ed5a8..10d8991 100644 --- a/withins_server/core/src/main/java/com/withins/core/user/service/UserService.java +++ b/withins_server/core/src/main/java/com/withins/core/user/service/UserService.java @@ -34,6 +34,9 @@ public User saveOrGet(final String username, Supplier orElseGet) public Optional readByUsername(final String username) { return userReader.readByUsername(username); } + public Optional readByEmail(final String email) { + return userReader.readByEmail(email); + } public UserInfoResponse readUserInfo(Long memberId) { User user = userReader.read(memberId); diff --git a/withins_vue/src/api/ApiClient.ts b/withins_vue/src/api/ApiClient.ts index 071611c..6716215 100644 --- a/withins_vue/src/api/ApiClient.ts +++ b/withins_vue/src/api/ApiClient.ts @@ -1,4 +1,5 @@ -import axios, { AxiosError, AxiosRequestConfig } from "axios"; +import axios, {AxiosError, AxiosRequestConfig} from "axios"; +import {FetchResponse} from "@/api/FetchResponse"; export class ApiClient { private isRefreshing = false; @@ -118,7 +119,7 @@ export class ApiClient { private async refreshToken(): Promise { try { - const response = await axios.post('/api/v1/auth/refresh'); + const response = await post('/api/v1/auth/refresh'); if (response.status === 200) { console.log('Token refreshed successfully'); return true; @@ -133,7 +134,6 @@ export class ApiClient { const apiClient = new ApiClient(); export { apiClient }; -export default axios; export const authApi = { formLogin: async (username: string, password: string) => { @@ -150,20 +150,135 @@ export const authApi = { console.warn('Logout request failed:', error); throw error; } - } + }, } export const userApi = { - loadUser: async () => { - return await axios.get('/api/v1/auth/user'); + loadUser: async () : Promise => { + return await get('/api/v1/auth/user'); } } export const newsApi = { - news: async (apiParams: any) => { - const response = await axios.get('/api/v1/news', { + news: async (apiParams: any) : Promise => { + return await get('/api/v1/news', { params: apiParams }); - return response.data; } -} \ No newline at end of file +} + +export const signupApi = { + existsUsername: async (username: string): Promise => { + const response = await get('http://localhost:8080/api/v1/signup/exists/username', { + params: {username: username} + }); + if (response.status === 200) { + return response.data; + } + return null; + }, + existsEmail: async (email: string): Promise => { + const response = await get('http://localhost:8080/api/v1/signup/exists/email', { + params: {email: email} + }); + return response.status === 200 ? response.data : null; + }, + signup: async (username: string, password: string, email: string) : Promise => { + return await post('http://localhost:8080/api/v1/signup', { + username, password, email + }); + } +} +const get = async (url: string, data?: AxiosRequestConfig) : Promise => { + try { + const response = await axios.get(url, data); + return new FetchResponse( + response.status, + 'SUCCESS', + response.data + ); + } catch (error: any) { + const status : number = Number(error.response?.status) ?? 500; + console.error(messageType[status]); + return new FetchResponse( + status, + error.response.message, + error.response.data, + ); + } +} + +const post = async (url: string, data?: D, config?: AxiosRequestConfig) : Promise => { + try { + const response = await axios.post(url, data, config); + return new FetchResponse( + response.status, + 'SUCCESS', + response.data + ); + } catch (error: any) { + const status : number = Number(error.response?.status) ?? 500; + console.error(messageType[status]); + return new FetchResponse( + status, + error.response.message, + error.response.data, + ); + } +} + +const messageType : Record = { + 100: 'CONTINUE', + 101: 'SWITCHING_PROTOCOL', + 102: 'PROCESSING', + 103: 'EARLY_HINTS', + 200: 'OK', + 201: 'CREATED', + 202: 'ACCEPTED', + 203: 'NONAUTHORITATIVE_INFOMATION', + 204: 'NO_CONTENT', + 205: 'RESET_CONTENT', + 206: 'PARTIAL_CONTENT', + 207: 'MULTI_STATUS', + 208: 'MULTI_STATUS', + 226: 'IM_USED', + 300: 'MULTIPLE_CHOICE', + 301: 'MOVED_PERMANENTLY', + 302: 'FOUND', + 303: 'SEE_OTHER', + 304: 'NOT_MODIFIED', + 305: 'USE_PROXY', + 306: 'UNUSED', + 307: 'TEMPORARY_REDIRECT', + 308: 'PERMANENT_REDIRECT', + 400: 'BAD_REQUEST', + 401: 'UNAUTHORIZED', + 402: 'PAYMENT_REQUIRED', + 403: 'FORBIDDEN', + 404: 'NOT_FOUND', + 405: 'NOT_ACCEPTABLE', + 407: 'PROXY_AUTHENTICATION_REQUIRED', + 408: 'REQUEST_TIMEOUT', + 409: 'CONFLICT', + 410: 'GONE', + 411: 'LENGTH_REQUIRED', + 412: 'PRECONDITION_FAILED', + 413: 'PAYLOAD_TOO_LARGE', + 414: 'URI_TOO_LONG', + 415: 'UNSUPPORTED_MEDIA_TYPE', + 416: 'REQUESTED_RANGE_NOT_SATISFIABLE', + 417: 'EXPECTATION_FAILED', + 418: 'I\'M_A_TEAPOT', + 421: 'MISDIRECTED_REQUEST', + 422: 'UNPROCESSABLE_ENTITY', + 423: 'LOCKED', + 424: 'FAILED_DEPENDENCY', + 426: 'UPGRADE_REQUIRED', + 428: 'PRECONDITION_REQUIRED', + 429: 'TOO_MANY_REQUESTS', + 431: 'REQUEST_HEADER_FIELDS_TOO_LARGE', + 451: 'UNAVAILABLE_FOR_LEGAL_REASONS', + 500: 'SERVER_ERROR', +} + + diff --git a/withins_vue/src/api/ApiServer.ts b/withins_vue/src/api/ApiServer.ts index 3b081f1..f7d7845 100644 --- a/withins_vue/src/api/ApiServer.ts +++ b/withins_vue/src/api/ApiServer.ts @@ -2,46 +2,8 @@ import {Pageable} from "@/domain/Pageable"; import {Recruit} from "@/domain/Recruit"; import {RecruitDetail} from "@/domain/RecruitDetail"; import {Career} from "@/domain/Career"; -import {News} from "@/domain/News"; export class ApiServer { - static server : String = 'http://localhost:8080/api/v1'; - - static fetchPost(url : String, json : Object) { - return fetch(`${ApiServer.server}${url}` , { - method : 'post', - credentials: 'include', - headers : {'Content-Type' : 'application/json'}, - body : JSON.stringify(json) - }) - .then(res => res.json()) - } - - static async fetchGet(url : String, params: object) { - const requestUrl = `${ApiServer.server}${url}${this.toQueryParams(params)}`; - - try { - const response = await fetch(requestUrl, { - method: 'GET', - headers: { - 'Accept': 'application/json' - }, - credentials: 'include' - }); - - // 텍스트가 유효한 JSON인지 확인 후 파싱 - try { - return response.json(); - } catch (parseError) { - console.error('JSON 파싱 오류:', parseError); - throw new Error('응답이 유효한 JSON 형식이 아닙니다'); - } - } catch (error) { - console.error('API 호출 중 오류 발생:', error); - throw error; - } - } - static mockRecruit(url : String) : Pageable { const json = { 'page' : { @@ -167,68 +129,11 @@ export class ApiServer { ] }; - const pageable = new Pageable(json); + const pageable : Pageable = Pageable.of(json); let recruits : Array = json.content.map(value => new Recruit(value)); pageable.setContent(recruits) return pageable; } - static mockNews(url: String, params: object) : Pageable { - const json = { - 'page' : { - 'totalElements': 71, - 'totalPages': 8, - 'pageNumber': 0, - }, - 'condition' : { - 'word': '테스트', - 'region': 'all', - 'type': 'ALL', - }, - 'content': [ - { - 'newsId': 1, - 'title': '2025년 사회교육프로그램 강사모집 [웰빙댄스]', - 'type' : 'NOTICE', - 'link' : 'https://naver.com', - 'organization': { - 'organizationId': 1, - 'name' : '서초구립양재노인종합복지관', - 'region': '인천 남동구' - }, - 'createAt': '2025-01-01T00:00:00', - }, - { - 'newsId': 1, - 'title': '2025년 사회교육프로그램 강사모집 [웰빙댄스]', - 'type' : 'NOTICE', - 'link' : 'https://naver.com', - 'organization': { - 'organizationId': 1, - 'name' : '서초구립양재노인종합복지관', - 'region': '인천 남동구' - }, - 'createAt': '2025-01-01T00:00:00', - }, - { - 'newsId': 1, - 'title': '2025년 사회교육프로그램 강사모집 [웰빙댄스]', - 'type' : 'NOTICE', - 'link' : 'https://naver.com', - 'organization': { - 'organizationId': 1, - 'name' : '서초구립양재노인종합복지관', - 'region': '인천 남동구' - }, - 'createAt': '2025-01-01T00:00:00', - } - ] - }; - - const pageable = new Pageable(json); - let news : Array = json.content.map(value => new News(value)); - pageable.setContent(news) - return pageable; - } static mockRecruitDetail(url : String) : RecruitDetail { const json = { 'recruitId': 1, @@ -363,22 +268,10 @@ export class ApiServer { ] }; - const pageable = new Pageable(json); + const pageable : Pageable = Pageable.of(json); let careers : Array = json.content.map(value => new Career(value)); pageable.setContent(careers) return pageable; } - static toQueryParams(params : object) { - if (params == null) return ''; - const queryParts = Object.entries(params) - // 빈 문자열, null, undefined 값을 가진 키는 필터링 - .filter(([_, value]) => value !== null && value !== undefined && value !== '') - .map(([key, value]) => { - return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; - }); - - return queryParts.length > 0 ? `?${queryParts.join('&')}` : ''; - } - } \ No newline at end of file diff --git a/withins_vue/src/api/FetchResponse.ts b/withins_vue/src/api/FetchResponse.ts new file mode 100644 index 0000000..44e49b6 --- /dev/null +++ b/withins_vue/src/api/FetchResponse.ts @@ -0,0 +1,15 @@ + + +export class FetchResponse { + + readonly status: number; + readonly message: string; + data: any; + + constructor(statusCode: number, message: string, data: any) { + this.status = statusCode; + this.message = message; + this.data = data; + } +} + diff --git a/withins_vue/src/domain/Pageable.ts b/withins_vue/src/domain/Pageable.ts index e045a4b..5442f1d 100644 --- a/withins_vue/src/domain/Pageable.ts +++ b/withins_vue/src/domain/Pageable.ts @@ -7,12 +7,28 @@ export class Pageable { public condition : Object = {}; public content : Array = []; - public constructor(data : any) { + private constructor(data : any) { this.totalElements = data.page.totalElements; this.totalPages = data.page.totalPages; this.pageNumber = data.page.pageNumber; this.condition = data.condition; } + public static of(data : any) : Pageable { + return new Pageable(data); + } + + public static empty() : Pageable { + return new Pageable( + { + page: { + totalElements: 0, + totalPages: 0, + pageNumber: 0, + }, + condition: {} + } + ); + } public setContent(ts : Array) { diff --git a/withins_vue/src/pages/login/FindPage.vue b/withins_vue/src/pages/login/FindPage.vue new file mode 100644 index 0000000..43e8169 --- /dev/null +++ b/withins_vue/src/pages/login/FindPage.vue @@ -0,0 +1,231 @@ + + + + + + \ No newline at end of file diff --git a/withins_vue/src/pages/login/LoginPage.vue b/withins_vue/src/pages/login/LoginPage.vue index 43e8169..b40eac7 100644 --- a/withins_vue/src/pages/login/LoginPage.vue +++ b/withins_vue/src/pages/login/LoginPage.vue @@ -6,53 +6,15 @@ :show="isLoading" type="dots" /> -
- -
- -
- + - -
-
-
- -
-
- -
-
- -
-
@@ -64,12 +26,6 @@ import { useUserStore } from "@/stores/UserStore"; import LoadingIndicator from "@/components/LoadingIndicator.vue"; import { useRouter } from "vue-router"; -const tabList = { - 'user' : '일반회원', - 'institution' : '기관' -} -const tab = ref('user') - const username = ref(''); const password = ref(''); const usernameInput = ref(); @@ -109,7 +65,24 @@ const submit = async () => { form { display: flex; flex-direction: column; - gap: 40px; + gap: 12px; +} +.form-wrap { + display: flex; + flex-direction: column; + gap: 27px; +} +.form-menu { + display: flex; + justify-content: space-between; + font-size: 14px; +} +.form-menu a { + color: var(--f3); + font-weight: 300; +} +.form-menu a:hover { + text-decoration: underline; } .field_wrap { display: flex; @@ -137,6 +110,7 @@ form { } #login-wrap { + position: relative; width: 100%; min-height: calc(100vh - 80px); @@ -146,79 +120,33 @@ form { } #login { width: 400px; - min-height: 450px; border-radius: 16px; border: 1px #eee solid; box-shadow: 10px 10px 30px rgba(0,0,0,0.06); position: relative; - padding: 15px; -} - -#tabs { - display: grid; - grid-template-columns: repeat(2, 1fr); - height: 45px; - position: relative; - margin-bottom: 30px; -} - -#tabs::after { - content: ''; - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 1px; - background-color: #eee; - z-index: 1; -} - -.tab { - padding: 12px 24px; - cursor: pointer; - border-bottom: 2px solid transparent; - font-size: 15px; - color: var(--f3); - transition: all 0.2s ease; - position: relative; - z-index: 2; - border-radius: 6px 6px 0 0; -} + padding: 45px 30px; -.tab:hover { - color: var(--main-color-1); - background-color: rgba(0, 113, 227, 0.03); -} - -.tab.active { - color: var(--main-color-1); - font-weight: 600; + display: flex; + flex-direction: column; + gap: 61px; } -.tab.active::before { - content: ''; - position: absolute; - bottom: -2px; - left: 0; +.social-btn { width: 100%; - height: 2px; - background: var(--main-color-gradient); - border-radius: 2px; -} - -#kakao-btn { height: 50px; - border-radius: 8px; - padding: 0 30px; + border-radius: 16px; + font-size: 1rem; display: flex; align-items: center; background-color: #FEE500; position: relative; + padding: 0 20px; } -#kakao-btn .text { +.social-btn span { position: absolute; left: 50%; transform: translateX(-50%); + color: #3C1E1E; } #kakao-btn:hover { background-color: #fbe006; @@ -228,4 +156,11 @@ button:disabled { background-color: #999999 !important; cursor: not-allowed; } + +#form { + display: flex; + align-items: center; + justify-content: center; + gap: 16px; +} \ No newline at end of file diff --git a/withins_vue/src/pages/login/LoginPage_FormLogin.vue b/withins_vue/src/pages/login/LoginPage_FormLogin.vue new file mode 100644 index 0000000..9647ae3 --- /dev/null +++ b/withins_vue/src/pages/login/LoginPage_FormLogin.vue @@ -0,0 +1,201 @@ + + + + + + \ No newline at end of file diff --git a/withins_vue/src/pages/login/LoginSuccessPage.vue b/withins_vue/src/pages/login/LoginSuccessPage.vue deleted file mode 100644 index 98d6620..0000000 --- a/withins_vue/src/pages/login/LoginSuccessPage.vue +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/withins_vue/src/pages/login/SignupPage.vue b/withins_vue/src/pages/login/SignupPage.vue new file mode 100644 index 0000000..02f9cd9 --- /dev/null +++ b/withins_vue/src/pages/login/SignupPage.vue @@ -0,0 +1,327 @@ + + + + + \ No newline at end of file diff --git a/withins_vue/src/pages/news/CenterNewsPage.vue b/withins_vue/src/pages/news/CenterNewsPage.vue index e72daf3..5b9c81e 100644 --- a/withins_vue/src/pages/news/CenterNewsPage.vue +++ b/withins_vue/src/pages/news/CenterNewsPage.vue @@ -138,9 +138,9 @@ import RecruitSearchComponent from "@/components/RecruitSearchComponent.vue"; import {ref} from "vue"; import NewsPaginationComponent from "@/components/NewsPaginationComponent.vue"; import {Pageable} from "@/domain/Pageable"; -import {ApiServer} from "@/api/ApiServer"; import {News} from "@/domain/News"; -import { newsApi } from "@/api/ApiClient"; +import {newsApi} from "@/api/ApiClient"; +import {FetchResponse} from "@/api/FetchResponse"; document.title = '복지관소식 | WithIns' const tabList = { @@ -198,12 +198,14 @@ const fetchNews = async (page: number, params: any): Promise> => }; // API 호출 - const json: any = await newsApi.news(apiParams); - // const json: any = await ApiServer.fetchGet('/news', apiParams); - const pageable = new Pageable(json); - let news : Array = json.content.map((value:any) => new News(value)); - pageable.setContent(news) - return pageable; + const response : FetchResponse = await newsApi.news(apiParams); + if (response.status == 200) { + const pageable : Pageable = Pageable.of(response.data); + let news : Array = response.data.content.map((value:any) => new News(value)); + pageable.setContent(news) + return pageable; + } + return Pageable.empty(); }; // 검색 이벤트 핸들러 diff --git a/withins_vue/src/router/index.ts b/withins_vue/src/router/index.ts index 8e7512b..338b41e 100644 --- a/withins_vue/src/router/index.ts +++ b/withins_vue/src/router/index.ts @@ -19,6 +19,16 @@ const routes: Array = [ }, ] }, + // { + // path: '/find', + // name: 'Find Account Or Password', + // component: () => import('../pages/login/FindPage.vue'), + // }, + // { + // path: '/signup', + // name: 'sign up', + // component: () => import('../pages/login/SignupPage.vue'), + // }, { path: "/news", component: () => import("../views/CenterNewsView.vue"), @@ -30,36 +40,36 @@ const routes: Array = [ } ] }, - { - path: "/recruit", - component: () => import("../views/RecruitmentView.vue"), - children: [ - { - path: "", - name: "recruitmentList", - component: () => import("../pages/recruitment/RecruitmentPage.vue"), - }, - { - path: ":recruitId", - name: "recruitmentDetail", - component: () => import("../pages/recruitment/RecruitmentDetailPage.vue"), - props: true, - } - ] - }, - { - path: "/career", - name: "careerProfile", - component: () => import("../views/CareerProfileView.vue"), - meta: { requiresAuth: true }, - children: [ - { - path: "", - name: "careerList", - component: () => import("../pages/career/CareerPage.vue"), - } - ] - }, + // { + // path: "/recruit", + // component: () => import("../views/RecruitmentView.vue"), + // children: [ + // { + // path: "", + // name: "recruitmentList", + // component: () => import("../pages/recruitment/RecruitmentPage.vue"), + // }, + // { + // path: ":recruitId", + // name: "recruitmentDetail", + // component: () => import("../pages/recruitment/RecruitmentDetailPage.vue"), + // props: true, + // } + // ] + // }, + // { + // path: "/career", + // name: "careerProfile", + // component: () => import("../views/CareerProfileView.vue"), + // meta: { requiresAuth: true }, + // children: [ + // { + // path: "", + // name: "careerList", + // component: () => import("../pages/career/CareerPage.vue"), + // } + // ] + // }, // { // path: "/admin", // name: "admin", diff --git a/withins_vue/src/stores/UserStore.ts b/withins_vue/src/stores/UserStore.ts index 02f9c36..7e2ab7e 100644 --- a/withins_vue/src/stores/UserStore.ts +++ b/withins_vue/src/stores/UserStore.ts @@ -36,16 +36,12 @@ export const useUserStore = defineStore('user', () => { } const loadUserData = async () => { - await userApi.loadUser() - .then(res => { - if (res.status === 200) { - let data = res.data; - user.value = new User(data); - } - }) - .catch(err => { - user.value = null; - }); + const fetchResponse = await userApi.loadUser(); + if (fetchResponse.status == 200) { + user.value = new User(fetchResponse.data); + } else { + user.value = null; + } } const isAnonymous = computed((): boolean => user.value == null);