diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/ZzimkkongApplication.java b/backend/src/main/java/com/woowacourse/zzimkkong/ZzimkkongApplication.java index 4524f74cb..18455d2ea 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/ZzimkkongApplication.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/ZzimkkongApplication.java @@ -3,8 +3,16 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import javax.annotation.PostConstruct; +import java.util.TimeZone; + @SpringBootApplication public class ZzimkkongApplication { + @PostConstruct + private void setTimeZone(){ + TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); + } + public static void main(String[] args) { SpringApplication.run(ZzimkkongApplication.class, args); } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/AuthenticationPrincipalConfig.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/AuthenticationPrincipalConfig.java similarity index 97% rename from backend/src/main/java/com/woowacourse/zzimkkong/AuthenticationPrincipalConfig.java rename to backend/src/main/java/com/woowacourse/zzimkkong/config/AuthenticationPrincipalConfig.java index 4fd40f96f..d1d45f2ae 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/AuthenticationPrincipalConfig.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/config/AuthenticationPrincipalConfig.java @@ -1,4 +1,4 @@ -package com.woowacourse.zzimkkong; +package com.woowacourse.zzimkkong.config; import com.woowacourse.zzimkkong.infrastructure.AuthenticationPrincipalArgumentResolver; import com.woowacourse.zzimkkong.infrastructure.LoginInterceptor; diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/SecurityConfig.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/SecurityConfig.java similarity index 95% rename from backend/src/main/java/com/woowacourse/zzimkkong/SecurityConfig.java rename to backend/src/main/java/com/woowacourse/zzimkkong/config/SecurityConfig.java index 63ca92087..2cd642b57 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/SecurityConfig.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/config/SecurityConfig.java @@ -1,4 +1,4 @@ -package com.woowacourse.zzimkkong; +package com.woowacourse.zzimkkong.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/SlackConfig.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/SlackConfig.java similarity index 95% rename from backend/src/main/java/com/woowacourse/zzimkkong/SlackConfig.java rename to backend/src/main/java/com/woowacourse/zzimkkong/config/SlackConfig.java index ba0cd9fc1..1144946af 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/SlackConfig.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/config/SlackConfig.java @@ -1,4 +1,4 @@ -package com.woowacourse.zzimkkong; +package com.woowacourse.zzimkkong.config; import com.woowacourse.zzimkkong.domain.SlackUrl; import org.springframework.beans.factory.annotation.Value; diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/StorageConfig.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/StorageConfig.java similarity index 92% rename from backend/src/main/java/com/woowacourse/zzimkkong/StorageConfig.java rename to backend/src/main/java/com/woowacourse/zzimkkong/config/StorageConfig.java index 459f12b70..9f58aaa33 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/StorageConfig.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/config/StorageConfig.java @@ -1,4 +1,4 @@ -package com.woowacourse.zzimkkong; +package com.woowacourse.zzimkkong.config; import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; @@ -15,7 +15,7 @@ @PropertySource("classpath:config/awsS3.properties") public class StorageConfig { @Bean - @Profile("prod") + @Profile({"prod", "dev"}) public AmazonS3 amazonS3() { return AmazonS3ClientBuilder .standard() @@ -24,7 +24,7 @@ public AmazonS3 amazonS3() { } @Bean(name = "amazonS3") - @Profile("!prod") + @Profile({"local", "test"}) public AmazonS3 amazonS3Local( @Value("${aws.access-key}") String accessKey, @Value("${aws.secret-key}") String secretKey) { diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/WebConfig.java b/backend/src/main/java/com/woowacourse/zzimkkong/config/WebConfig.java similarity index 96% rename from backend/src/main/java/com/woowacourse/zzimkkong/WebConfig.java rename to backend/src/main/java/com/woowacourse/zzimkkong/config/WebConfig.java index dcf5ee13e..33135c5e5 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/WebConfig.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/config/WebConfig.java @@ -1,4 +1,4 @@ -package com.woowacourse.zzimkkong; +package com.woowacourse.zzimkkong.config; import org.apache.http.HttpHeaders; import org.springframework.beans.factory.annotation.Value; diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/controller/MemberController.java b/backend/src/main/java/com/woowacourse/zzimkkong/controller/MemberController.java index 5a4042efc..59a5b22c2 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/controller/MemberController.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/controller/MemberController.java @@ -27,7 +27,6 @@ public class MemberController { public MemberController(final MemberService memberService, final PresetService presetService) { this.memberService = memberService; this.presetService = presetService; - } @PostMapping diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/dto/ValidatorMessage.java b/backend/src/main/java/com/woowacourse/zzimkkong/dto/ValidatorMessage.java index ed592030e..999eb88be 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/dto/ValidatorMessage.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/dto/ValidatorMessage.java @@ -10,6 +10,7 @@ private ValidatorMessage() { public static final String RESERVATION_PW_MESSAGE = "비밀번호는 숫자 4자리만 입력 가능합니다."; public static final String ORGANIZATION_MESSAGE = "조직명은 특수문자 없이 20자 이내로 작성 가능합니다."; public static final String NAME_MESSAGE = "이름은 특수문자(-_!?.,)를 포함하여 20자 이내로 작성 가능합니다."; + public static final String PRESET_NAME_MESSAGE = "프리셋 이름은 공백과 특수문자(-_!?.,)를 포함하여 20자 이내로 작성 가능합니다."; public static final String DESCRIPTION_MESSAGE = "예약 내용은 100자 이내로 작성 가능합니다."; public static final String FORMAT_MESSAGE = "날짜 및 시간 데이터 형식이 올바르지 않습니다."; public static final String DAY_OF_WEEK_MESSAGE = "올바른 요일 형식이 아닙니다."; @@ -23,4 +24,6 @@ private ValidatorMessage() { public static final String RESERVATION_PW_FORMAT = "^[0-9]{4}$"; public static final String ORGANIZATION_FORMAT = "^[ a-zA-Z0-9ㄱ-힣]{1,20}$"; public static final String NAMING_FORMAT = "^[-_!?.,a-zA-Z0-9ㄱ-힣]{1,20}$"; + public static final String PRESET_NAME_FORMAT = "^[-_!?., a-zA-Z0-9ㄱ-힣]{1,20}$"; + } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/dto/member/PresetCreateRequest.java b/backend/src/main/java/com/woowacourse/zzimkkong/dto/member/PresetCreateRequest.java index 41fee1b40..f225e35ee 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/dto/member/PresetCreateRequest.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/dto/member/PresetCreateRequest.java @@ -14,7 +14,7 @@ @NoArgsConstructor public class PresetCreateRequest { @NotNull(message = EMPTY_MESSAGE) - @Pattern(regexp = NAMING_FORMAT, message = NAME_MESSAGE) + @Pattern(regexp = PRESET_NAME_FORMAT, message = PRESET_NAME_MESSAGE) private String name; @Valid diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/dto/member/PresetFindResponse.java b/backend/src/main/java/com/woowacourse/zzimkkong/dto/member/PresetFindResponse.java index d9bad8f7e..51bf8d468 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/dto/member/PresetFindResponse.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/dto/member/PresetFindResponse.java @@ -11,9 +11,11 @@ @Getter @NoArgsConstructor public class PresetFindResponse extends SettingResponse { + private Long id; private String name; private PresetFindResponse( + final Long id, final LocalTime availableStartTime, final LocalTime availableEndTime, final Integer reservationTimeUnit, @@ -23,6 +25,7 @@ private PresetFindResponse( final String enabledDayOfWeek, final String name) { super(availableStartTime, availableEndTime, reservationTimeUnit, reservationMinimumTimeUnit, reservationMaximumTimeUnit, reservationEnable, enabledDayOfWeek); + this.id = id; this.name = name; } @@ -30,6 +33,7 @@ public static PresetFindResponse from(final Preset preset) { Setting setting = preset.getSetting(); return new PresetFindResponse( + preset.getId(), setting.getAvailableStartTime(), setting.getAvailableEndTime(), setting.getReservationTimeUnit(), diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/LocalTimeConverter.java b/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/LocalTimeConverter.java deleted file mode 100644 index ade5bdb7f..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/LocalTimeConverter.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.woowacourse.zzimkkong.infrastructure; - -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; -import java.time.ZoneId; - -@Component -@Profile({"test", "local"}) -public class LocalTimeConverter implements TimeConverter { - @Override - public LocalDateTime getNow() { - LocalDateTime localNow = LocalDateTime.now().withSecond(0).withNano(0); - return localNow.atZone(ZoneId.of("Asia/Seoul")) - .toLocalDateTime(); - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/ProductionTimeConverter.java b/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/ProductionTimeConverter.java deleted file mode 100644 index 8a07dc51e..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/ProductionTimeConverter.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.woowacourse.zzimkkong.infrastructure; - -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Component; - -import java.time.LocalDateTime; -import java.time.ZoneId; - -@Component -@Profile("prod") -public class ProductionTimeConverter implements TimeConverter { - @Override - public LocalDateTime getNow() { - LocalDateTime localNow = LocalDateTime.now().withSecond(0).withNano(0); - return localNow.atZone(ZoneId.of("UTC")) - .withZoneSameInstant(ZoneId.of("Asia/Seoul")) - .toLocalDateTime(); - } -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/TimeConverter.java b/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/TimeConverter.java deleted file mode 100644 index fa5f0e280..000000000 --- a/backend/src/main/java/com/woowacourse/zzimkkong/infrastructure/TimeConverter.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.woowacourse.zzimkkong.infrastructure; - -import java.time.LocalDateTime; - -public interface TimeConverter { - LocalDateTime getNow(); -} diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/repository/MapRepository.java b/backend/src/main/java/com/woowacourse/zzimkkong/repository/MapRepository.java index 326b3939a..2ef8f5693 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/repository/MapRepository.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/repository/MapRepository.java @@ -5,7 +5,6 @@ import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; -import java.util.Optional; public interface MapRepository extends JpaRepository { List findAllByMember(final Member member); diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/repository/PresetRepository.java b/backend/src/main/java/com/woowacourse/zzimkkong/repository/PresetRepository.java index 1789161c0..383ec9020 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/repository/PresetRepository.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/repository/PresetRepository.java @@ -1,10 +1,7 @@ package com.woowacourse.zzimkkong.repository; -import com.woowacourse.zzimkkong.domain.Member; import com.woowacourse.zzimkkong.domain.Preset; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.List; - public interface PresetRepository extends JpaRepository { } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/repository/SpaceRepository.java b/backend/src/main/java/com/woowacourse/zzimkkong/repository/SpaceRepository.java index bde2d0764..160d973aa 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/repository/SpaceRepository.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/repository/SpaceRepository.java @@ -3,7 +3,5 @@ import com.woowacourse.zzimkkong.domain.Space; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.List; - public interface SpaceRepository extends JpaRepository { } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/service/MapService.java b/backend/src/main/java/com/woowacourse/zzimkkong/service/MapService.java index 2af1c4522..fb7b650f4 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/service/MapService.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/service/MapService.java @@ -12,12 +12,12 @@ import com.woowacourse.zzimkkong.exception.space.ReservationExistOnSpaceException; import com.woowacourse.zzimkkong.infrastructure.SharingIdGenerator; import com.woowacourse.zzimkkong.infrastructure.ThumbnailManager; -import com.woowacourse.zzimkkong.infrastructure.TimeConverter; import com.woowacourse.zzimkkong.repository.MapRepository; import com.woowacourse.zzimkkong.repository.ReservationRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; import java.util.List; import static java.util.stream.Collectors.collectingAndThen; @@ -28,19 +28,16 @@ public class MapService { private final MapRepository maps; private final ReservationRepository reservations; - private final TimeConverter timeConverter; private final ThumbnailManager thumbnailManager; private final SharingIdGenerator sharingIdGenerator; public MapService( final MapRepository maps, final ReservationRepository reservations, - final TimeConverter timeConverter, final ThumbnailManager thumbnailManager, final SharingIdGenerator sharingIdGenerator) { this.maps = maps; this.reservations = reservations; - this.timeConverter = timeConverter; this.thumbnailManager = thumbnailManager; this.sharingIdGenerator = sharingIdGenerator; } @@ -103,7 +100,7 @@ private void validateExistReservations(final Map map) { List findSpaces = map.getSpaces(); boolean isExistReservationInAnySpace = findSpaces.stream() - .anyMatch(space -> reservations.existsBySpaceIdAndEndTimeAfter(space.getId(), timeConverter.getNow())); + .anyMatch(space -> reservations.existsBySpaceIdAndEndTimeAfter(space.getId(), LocalDateTime.now())); if (isExistReservationInAnySpace) { throw new ReservationExistOnSpaceException(); diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/service/ReservationService.java b/backend/src/main/java/com/woowacourse/zzimkkong/service/ReservationService.java index 8ffc61def..edd47144f 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/service/ReservationService.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/service/ReservationService.java @@ -9,7 +9,6 @@ import com.woowacourse.zzimkkong.exception.map.NoSuchMapException; import com.woowacourse.zzimkkong.exception.reservation.*; import com.woowacourse.zzimkkong.exception.space.NoSuchSpaceException; -import com.woowacourse.zzimkkong.infrastructure.TimeConverter; import com.woowacourse.zzimkkong.repository.MapRepository; import com.woowacourse.zzimkkong.repository.ReservationRepository; import com.woowacourse.zzimkkong.service.strategy.ReservationStrategy; @@ -34,15 +33,12 @@ public class ReservationService { private final MapRepository maps; private final ReservationRepository reservations; - private final TimeConverter timeConverter; public ReservationService( final MapRepository maps, - final ReservationRepository reservations, - final TimeConverter timeConverter) { + final ReservationRepository reservations) { this.maps = maps; this.reservations = reservations; - this.timeConverter = timeConverter; } public ReservationCreateResponse saveReservation( @@ -87,9 +83,9 @@ public ReservationFindAllResponse findAllReservations( List findSpaces = map.getSpaces(); LocalDate date = reservationFindAllDto.getDate(); - List reservations = getReservations(findSpaces, date); + List findReservations = getReservations(findSpaces, date); - return ReservationFindAllResponse.of(findSpaces, reservations); + return ReservationFindAllResponse.of(findSpaces, findReservations); } @Transactional(readOnly = true) @@ -106,9 +102,9 @@ public ReservationFindResponse findReservations( LocalDate date = reservationFindDto.getDate(); Space space = map.findSpaceById(spaceId) .orElseThrow(NoSuchSpaceException::new); - List reservations = getReservations(Collections.singletonList(space), date); + List findReservations = getReservations(Collections.singletonList(space), date); - return ReservationFindResponse.from(reservations); + return ReservationFindResponse.from(findReservations); } @Transactional(readOnly = true) @@ -198,7 +194,7 @@ private void validateTime(final ReservationCreateDto reservationCreateDto) { LocalDateTime startDateTime = reservationCreateDto.getStartDateTime().withSecond(0).withNano(0); LocalDateTime endDateTime = reservationCreateDto.getEndDateTime().withSecond(0).withNano(0); - if (startDateTime.isBefore(timeConverter.getNow())) { + if (startDateTime.isBefore(LocalDateTime.now())) { throw new ImpossibleStartTimeException(); } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/service/SpaceService.java b/backend/src/main/java/com/woowacourse/zzimkkong/service/SpaceService.java index 07f784a08..4905086cd 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/service/SpaceService.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/service/SpaceService.java @@ -9,13 +9,13 @@ import com.woowacourse.zzimkkong.exception.space.NoSuchSpaceException; import com.woowacourse.zzimkkong.exception.space.ReservationExistOnSpaceException; import com.woowacourse.zzimkkong.infrastructure.ThumbnailManager; -import com.woowacourse.zzimkkong.infrastructure.TimeConverter; import com.woowacourse.zzimkkong.repository.MapRepository; import com.woowacourse.zzimkkong.repository.ReservationRepository; import com.woowacourse.zzimkkong.repository.SpaceRepository; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; import java.util.List; import static com.woowacourse.zzimkkong.service.MapService.validateManagerOfMap; @@ -26,19 +26,16 @@ public class SpaceService { private final MapRepository maps; private final SpaceRepository spaces; private final ReservationRepository reservations; - private final TimeConverter timeConverter; private final ThumbnailManager thumbnailManager; public SpaceService( final MapRepository maps, final SpaceRepository spaces, final ReservationRepository reservations, - final TimeConverter timeConverter, final ThumbnailManager thumbnailManager) { this.maps = maps; this.spaces = spaces; this.reservations = reservations; - this.timeConverter = timeConverter; this.thumbnailManager = thumbnailManager; } @@ -159,7 +156,7 @@ private Setting getSetting(final SpaceCreateUpdateRequest spaceCreateUpdateReque } private void validateReservationExistence(final Long spaceId) { - if (reservations.existsBySpaceIdAndEndTimeAfter(spaceId, timeConverter.getNow())) { + if (reservations.existsBySpaceIdAndEndTimeAfter(spaceId, LocalDateTime.now())) { throw new ReservationExistOnSpaceException(); } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/service/strategy/ExcludeReservationCreateStrategy.java b/backend/src/main/java/com/woowacourse/zzimkkong/service/strategy/ExcludeReservationCreateStrategy.java index 62774137d..e7aa5f7e1 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/service/strategy/ExcludeReservationCreateStrategy.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/service/strategy/ExcludeReservationCreateStrategy.java @@ -8,5 +8,6 @@ public class ExcludeReservationCreateStrategy implements ExcludeReservationStrategy { @Override public void apply(final Space space, final List reservationsOnDate) { + // 예약 생성 시는 검증 전 예약을 제외하지 않아도 되므로 생략합니다 } } diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/service/strategy/GuestReservationStrategy.java b/backend/src/main/java/com/woowacourse/zzimkkong/service/strategy/GuestReservationStrategy.java index 4e1731b6c..8943037ca 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/service/strategy/GuestReservationStrategy.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/service/strategy/GuestReservationStrategy.java @@ -9,6 +9,7 @@ public class GuestReservationStrategy implements ReservationStrategy { @Override public void validateManagerOfMap(final Map map, final Member manager) { + // guest는 맵의 관리자 확인과정 생략 } @Override diff --git a/backend/src/main/java/com/woowacourse/zzimkkong/service/strategy/ManagerReservationStrategy.java b/backend/src/main/java/com/woowacourse/zzimkkong/service/strategy/ManagerReservationStrategy.java index 2666afb4f..694792d08 100644 --- a/backend/src/main/java/com/woowacourse/zzimkkong/service/strategy/ManagerReservationStrategy.java +++ b/backend/src/main/java/com/woowacourse/zzimkkong/service/strategy/ManagerReservationStrategy.java @@ -16,6 +16,7 @@ public void validateManagerOfMap(final Map map, final Member manager) { @Override public void checkCorrectPassword(final Reservation reservation, final String password) { + // manager는 비밀번호 확인과정이 없으므로 생략 } @Override diff --git a/backend/src/main/resources/application-dev.properties b/backend/src/main/resources/application-dev.properties index 8e293ef6c..7195f0516 100644 --- a/backend/src/main/resources/application-dev.properties +++ b/backend/src/main/resources/application-dev.properties @@ -3,6 +3,8 @@ spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/zzimkkong?characterEncoding=UTF-8 spring.datasource.username=root spring.datasource.password=1234 +spring.datasource.hikari.maximum-pool-size=45 +spring.datasource.hikari.connection-timeout=50000 # flyway spring.flyway.enabled=true diff --git a/backend/src/main/resources/config b/backend/src/main/resources/config index 1919327c5..1eba39047 160000 --- a/backend/src/main/resources/config +++ b/backend/src/main/resources/config @@ -1 +1 @@ -Subproject commit 1919327c5976116c5864197b8be30d35a8b85078 +Subproject commit 1eba39047270b72c09b9b5e63af6e51bd7b6c8fa diff --git a/backend/src/main/resources/logback-spring.xml b/backend/src/main/resources/logback-spring.xml index d36f41e78..bdaad2ea7 100644 --- a/backend/src/main/resources/logback-spring.xml +++ b/backend/src/main/resources/logback-spring.xml @@ -22,6 +22,18 @@ + + + + + + + + + + + + diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/controller/MemberControllerTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/controller/MemberControllerTest.java index 31cdeba6f..f033aa5fe 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/controller/MemberControllerTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/controller/MemberControllerTest.java @@ -114,7 +114,7 @@ void findAllPreset() { //then assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); - assertThat(actual).usingRecursiveComparison().isEqualTo(expected); + assertThat(actual).usingRecursiveComparison().ignoringExpectedNullFields().isEqualTo(expected); } @Test diff --git a/backend/src/test/java/com/woowacourse/zzimkkong/dto/PresetCreateRequestTest.java b/backend/src/test/java/com/woowacourse/zzimkkong/dto/PresetCreateRequestTest.java index 779b4b24c..480e7673c 100644 --- a/backend/src/test/java/com/woowacourse/zzimkkong/dto/PresetCreateRequestTest.java +++ b/backend/src/test/java/com/woowacourse/zzimkkong/dto/PresetCreateRequestTest.java @@ -8,8 +8,7 @@ import org.junit.jupiter.params.provider.NullSource; import static com.woowacourse.zzimkkong.Constants.*; -import static com.woowacourse.zzimkkong.dto.ValidatorMessage.EMPTY_MESSAGE; -import static com.woowacourse.zzimkkong.dto.ValidatorMessage.NAME_MESSAGE; +import static com.woowacourse.zzimkkong.dto.ValidatorMessage.*; import static org.assertj.core.api.Assertions.assertThat; class PresetCreateRequestTest extends RequestTest { @@ -35,13 +34,13 @@ void blankName(String name) { } @ParameterizedTest - @CsvSource(value = {"옳지 않은 프리셋!:true", "옳지않은프리셋옳지않은프리셋옳지않은프리셋:true", "옳은프리셋!:false"}, delimiter = ':') + @CsvSource(value = {"공백 입력 가능!:false", "옳지않은프리셋옳지않은프리셋옳지않은프리셋:true", "옳은프리셋!:false"}, delimiter = ':') @DisplayName("예약 생성의 이름에 옳지 않은 형식의 문자열이 들어오면 처리한다.") void invalidName(String name, boolean flag) { PresetCreateRequest nameRequest = new PresetCreateRequest(name, settingsRequest); assertThat(getConstraintViolations(nameRequest).stream() - .anyMatch(violation -> violation.getMessage().equals(NAME_MESSAGE))) + .anyMatch(violation -> violation.getMessage().equals(PRESET_NAME_MESSAGE))) .isEqualTo(flag); } } diff --git a/frontend/jest.config.js b/frontend/jest.config.js index c4fd368ec..6ec8ec7bf 100644 --- a/frontend/jest.config.js +++ b/frontend/jest.config.js @@ -8,5 +8,5 @@ module.exports = { '\\.(jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/src/__mocks__/fileMock.ts', }, - setupFilesAfterEnv: ['./src/__mocks__/setupTest.ts'], + setupFilesAfterEnv: ['./src/__mocks__/setupTest.ts', '@testing-library/jest-dom/extend-expect'], }; diff --git a/frontend/package.json b/frontend/package.json index 2f4b6c680..beb41db36 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "zzimkkong-frontend", - "version": "1.0.0", + "version": "1.0.1", "main": "src/index.tsx", "license": "MIT", "homepage": "https://github.com/woowacourse-teams/2021-zzimkkong", @@ -22,9 +22,11 @@ } ], "scripts": { - "prod": "cross-env NODE_ENV=production webpack serve", - "start": "cross-env NODE_ENV=development webpack serve", - "build": "cross-env NODE_ENV=production webpack", + "prod": "cross-env NODE_ENV=production DEPLOY_ENV=production webpack serve", + "prod:dev": "cross-env NODE_ENV=production DEPLOY_ENV=development webpack serve", + "start": "cross-env NODE_ENV=development DEPLOY_ENV=development webpack serve", + "build": "cross-env NODE_ENV=production DEPLOY_ENV=production webpack", + "build:dev": "cross-env NODE_ENV=production DEPLOY_ENV=development webpack", "storybook": "start-storybook -s ./src/assets -p 6006", "build-storybook": "build-storybook -s ./src/assets", "test": "jest --watch --passWithNoTests", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e84150797..abbd8fa20 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -12,7 +12,7 @@ import NotFound from './pages/NotFound/NotFound'; export const history = createBrowserHistory(); -const queryClient = new QueryClient(); +export const queryClient = new QueryClient(); const App = (): JSX.Element => { return ( diff --git a/frontend/src/__mocks__/handlers.ts b/frontend/src/__mocks__/handlers.ts index 8ee3efffd..c22fe76f7 100644 --- a/frontend/src/__mocks__/handlers.ts +++ b/frontend/src/__mocks__/handlers.ts @@ -1,21 +1,86 @@ -import { rest, RestHandler } from 'msw'; +import { DefaultRequestBody, rest, RestHandler } from 'msw'; +import { BASE_URL } from 'constants/api'; +import { MapItemResponse } from 'types/response'; import { formatDate } from 'utils/datetime'; -import { Reservation } from './../types/common'; -import { reservations } from './mockData'; +import { ManagerSpaceAPI, Reservation } from './../types/common'; +import { guestMaps, reservations, spaces } from './mockData'; -const BASE_URL = 'https://zzimkkong-proxy.o-r.kr/api'; +const ENDPOINT = process.env.DEPLOY_ENV !== 'production' ? BASE_URL.DEV : BASE_URL.PROD; + +type GetGuestMapResponseBody = MapItemResponse; + +interface GetGuestSpacesResponseBody { + spaces: ManagerSpaceAPI[]; +} + +interface GetGuestSpacesParams { + mapId: number; +} interface GetReservationsResponseBody { reservations: Reservation[]; } + interface GetReservationsParams { mapId: number; spaceId: number; } +interface GuestReservationRequestBody { + reservation: { + startDateTime: Date; + endDateTime: Date; + password: string; + name: string; + description: string; + }; +} +interface PostReservationParams { + mapId: number; + spaceId: number; +} + +interface DeleteReservationRequestBody { + password: string; +} + +interface DeleteReservationParams { + mapId: number; + spaceId: number; + reservationId: number; +} + const handlers: RestHandler[] = [ + rest.get(`${ENDPOINT}/guests/maps`, (req, res, ctx) => { + const sharingMapId = req.url.searchParams.get('sharingMapId'); + + if (!sharingMapId) { + return res(ctx.status(400)); + } + + if (!guestMaps[sharingMapId]) { + return res(ctx.status(404)); + } + + return res(ctx.status(200), ctx.json(guestMaps[sharingMapId])); + }), + + rest.get( + `${ENDPOINT}/guests/maps/:mapId/spaces`, + (req, res, ctx) => { + const { mapId } = req.params; + + return res( + ctx.status(200), + ctx.json({ + spaces: spaces[mapId] ?? [], + }) + ); + } + ), + rest.get( - `${BASE_URL}/guests/maps/:mapId/spaces/:spaceId/reservations`, + `${ENDPOINT}/guests/maps/:mapId/spaces/:spaceId/reservations`, (req, res, ctx) => { const { mapId, spaceId } = req.params; const date = req.url.searchParams.get('date'); @@ -31,6 +96,43 @@ const handlers: RestHandler[] = [ ); } ), + + rest.post( + `${ENDPOINT}/guests/maps/:mapId/spaces/:spaceId/reservations`, + (req, res, ctx) => { + const { mapId, spaceId } = req.params; + + if (!spaces[mapId] || !!spaces[mapId].find(({ id }) => id === spaceId)) { + return res(ctx.status(400)); + } + + return res(ctx.status(201)); + } + ), + + rest.delete( + `${ENDPOINT}/guests/maps/:mapId/spaces/:spaceId/reservations/:reservationId`, + (req, res, ctx) => { + const { mapId, spaceId, reservationId } = req.params; + const { password } = req.body; + + if (!spaces[mapId] || !!spaces[mapId].find(({ id }) => id === spaceId)) { + return res(ctx.status(400)); + } + + const target = reservations[mapId][spaceId].find( + (reservation) => reservation.id === Number(reservationId) + ); + + if (!target) return res(ctx.status(400)); + + if (password !== target.password) { + return res(ctx.status(400)); + } + + return res(ctx.status(204)); + } + ), ]; export default handlers; diff --git a/frontend/src/__mocks__/mockData.ts b/frontend/src/__mocks__/mockData.ts index f41f26cb7..0f89dab13 100644 --- a/frontend/src/__mocks__/mockData.ts +++ b/frontend/src/__mocks__/mockData.ts @@ -1,4 +1,22 @@ -import { Reservation } from './../types/common'; +import { MapItemResponse } from 'types/response'; +import { ManagerSpaceAPI } from './../types/common'; + +interface GuestMaps { + [sharingMapId: string]: MapItemResponse; +} + +interface Spaces { + [mapId: number]: ManagerSpaceAPI[]; +} + +interface Reservation { + id: number; + startDateTime: string; + endDateTime: string; + password: string; + name: string; + description: string; +} interface Reservations { [mapId: number]: { @@ -6,6 +24,38 @@ interface Reservations { }; } +export const guestMaps: GuestMaps = { + JMTGR: { + mapId: 1, + mapName: 'guestMap-1', + mapDrawing: + '{"width":800,"height":600,"mapElements":[{"id":2,"type":"rect","stroke":"#333333","points":["210,90","650,230"]},{"id":3,"type":"rect","stroke":"#333333","width":440,"height":140,"x":210,"y":90,"points":["210, 90","650, 230"]}]}', + mapImageUrl: '', + sharingMapId: 'JMTGR', + }, +}; + +export const spaces: Spaces = { + 1: [ + { + id: 1, + name: 'testSpace', + color: '#EB3933', + description: 'testMap', + area: '{"shape":"rect","x":210,"y":90,"width":440,"height":140}', + settings: { + availableStartTime: '07:00:00', + availableEndTime: '23:00:00', + reservationTimeUnit: 10, + reservationMinimumTimeUnit: 10, + reservationMaximumTimeUnit: 1440, + reservationEnable: true, + enabledDayOfWeek: 'monday,tuesday,wednesday,thursday,friday,saturday,sunday', + }, + }, + ], +}; + export const reservations: Reservations = { 1: { 1: [ @@ -15,27 +65,31 @@ export const reservations: Reservations = { endDateTime: '2021-07-01T01:00:00', name: '찜꽁', description: '찜꽁 5차 회의', + password: '1111', }, { id: 2, startDateTime: '2021-07-01T03:30:00', endDateTime: '2021-07-01T04:30:00', - name: 'sally', + name: '샐리', description: '찜꽁 데모데이 회의.', + password: '1111', }, { id: 3, - startDateTime: '2022-07-01T00:00:00', - endDateTime: '2022-07-01T01:00:00', - name: '찜꽁', + startDateTime: '2021-08-30T00:00:00', + endDateTime: '2021-08-30T01:00:00', + name: '체프', description: '찜꽁 165차 회의', + password: '1111', }, { id: 4, - startDateTime: '2022-07-01T03:30:00', - endDateTime: '2022-07-01T04:30:00', - name: 'sally', + startDateTime: '2021-08-30T03:30:00', + endDateTime: '2021-08-30T04:30:00', + name: '썬', description: '찜꽁 A시리즈 투자 관련 회의.', + password: '1111', }, ], }, @@ -48,6 +102,7 @@ export const reservations: Reservations = { endDateTime: '2021-07-01T01:00:00', name: '체프', description: '야식 먹자', + password: '1111', }, { id: 11, @@ -55,6 +110,7 @@ export const reservations: Reservations = { endDateTime: '2021-07-02T04:30:00', name: '샐리', description: '바다와 함께하는 페어프로그래밍.', + password: '1111', }, ], 2: [ @@ -64,6 +120,7 @@ export const reservations: Reservations = { endDateTime: '2021-07-01T01:00:00', name: '유조', description: '제천 여행 계획', + password: '1111', }, { id: 13, @@ -71,6 +128,7 @@ export const reservations: Reservations = { endDateTime: '2021-07-02T16:30:00', name: '썬', description: '도예 클래스', + password: '1111', }, ], }, diff --git a/frontend/src/__mocks__/setupTest.ts b/frontend/src/__mocks__/setupTest.ts index 01fc6c48a..8bd9e27aa 100644 --- a/frontend/src/__mocks__/setupTest.ts +++ b/frontend/src/__mocks__/setupTest.ts @@ -1,5 +1,9 @@ import server from './server'; +global.scrollTo = jest.fn(); +global.alert = jest.fn(); +window.alert = jest.fn(); + beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); diff --git a/frontend/src/__tests__/reservation.test.tsx b/frontend/src/__tests__/reservation.test.tsx new file mode 100644 index 000000000..e3595ad38 --- /dev/null +++ b/frontend/src/__tests__/reservation.test.tsx @@ -0,0 +1,143 @@ +import userEvent from '@testing-library/user-event'; + +import { MemoryRouter, Route, Switch } from 'react-router-dom'; +import MESSAGE from 'constants/message'; +import { HREF } from 'constants/path'; +import { PUBLIC_ROUTES } from 'constants/routes'; +import { render, screen, waitFor, within } from 'test-utils'; +import { formatDate } from 'utils/datetime'; + +describe('예약 페이지 조회', () => { + beforeEach(() => { + const sharingMapId = 'JMTGR'; + + render( + + + {PUBLIC_ROUTES.map(({ path, component }) => ( + + {component} + + ))} + + + ); + }); + + it('예약 목록을 조회할 수 있다.', async () => { + const date = '2021-07-01'; + const spaceId = 1; + + const $targetSpace = await waitFor(() => screen.getByTestId(spaceId)); + const $targetDateInput = screen.getByDisplayValue(formatDate(new Date())); + + userEvent.type($targetDateInput, date); + userEvent.click($targetSpace); + + const reservations = await waitFor(() => screen.getAllByRole('listitem')); + + expect(reservations).toHaveLength(2); + }); +}); + +describe('예약 추가', () => { + beforeAll(() => { + const sharingMapId = 'JMTGR'; + + render( + + + {PUBLIC_ROUTES.map(({ path, component }) => ( + + {component} + + ))} + + + ); + }); + + it('예약페이지에 진입할 수 있다.', async () => { + const date = new Date(); + date.setDate(date.getDate() + 1); + const targetDate = formatDate(date); + const spaceId = 1; + + const $targetSpace = await waitFor(() => screen.getByTestId(spaceId)); + const $targetDateInput = screen.getByDisplayValue(formatDate(new Date())); + + userEvent.type($targetDateInput, targetDate); + userEvent.click($targetSpace); + + const $reservationButton = await waitFor(() => screen.getByRole('link', { name: /예약하기/i })); + userEvent.click($reservationButton); + + const $pageTitle = screen.getByTestId(/spaceName/i); + + expect($pageTitle).toHaveTextContent('testSpace'); + + const $nameInput = await waitFor(() => screen.getByRole('textbox', { name: /이름/i })); + const $descriptionInput = screen.getByRole('textbox', { name: /사용 목적/i }); + const $startTimeInput = screen.getByLabelText(/시작 시간/i); + const $endTimeInput = screen.getByLabelText(/종료 시간/i); + const $passwordInput = screen.getByLabelText(/비밀번호숫자 4자리를 입력해주세요\./i); + const $submitButton = screen.getByRole('button', { name: /예약하기/i }); + + userEvent.type($nameInput, '유조'); + userEvent.type($descriptionInput, '찜꽁 회의'); + userEvent.type($startTimeInput, '15:30'); + userEvent.type($endTimeInput, '16:30'); + userEvent.type($passwordInput, '1117'); + userEvent.click($submitButton); + + // TODO 예약 완료 랜딩 페이지 작성 후 추가 테스트 작성 + }); +}); + +describe('예약 삭제', () => { + beforeAll(() => { + const sharingMapId = 'JMTGR'; + + render( + + + {PUBLIC_ROUTES.map(({ path, component }) => ( + + {component} + + ))} + + + ); + }); + + it('특정 예약을 삭제할 수 있다.', async () => { + const date = '2021-07-01'; + const spaceId = 1; + const reservationId = 2; + + const $targetSpace = await waitFor(() => screen.getByTestId(spaceId)); + const $targetDateInput = screen.getByDisplayValue(formatDate(new Date())); + + userEvent.type($targetDateInput, date); + userEvent.click($targetSpace); + + const $targetReservation = await waitFor(() => + screen.getByTestId(`reservation-${reservationId}`) + ); + + const $deleteButton = within($targetReservation).getByRole('button', { name: /삭제/i }); + + userEvent.click($deleteButton); + + const $passwordInput = screen.getByLabelText(/비밀번호/i); + const $submitButton = screen.getByRole('button', { name: /확인/i }); + + userEvent.type($passwordInput, '1111'); + userEvent.click($submitButton); + + await waitFor(() => + expect(window.alert).toHaveBeenCalledWith(MESSAGE.RESERVATION.DELETE_SUCCESS) + ); + }); +}); diff --git a/frontend/src/api/api.ts b/frontend/src/api/api.ts index a2554cd96..0559e7b65 100644 --- a/frontend/src/api/api.ts +++ b/frontend/src/api/api.ts @@ -1,12 +1,13 @@ import axios, { AxiosError } from 'axios'; import { history } from 'App'; +import { BASE_URL } from 'constants/api'; import PATH from 'constants/path'; import { LOCAL_STORAGE_KEY } from 'constants/storage'; import { ErrorResponse } from 'types/response'; import { getLocalStorageItem } from 'utils/localStorage'; const api = axios.create({ - baseURL: 'https://zzimkkong-proxy.o-r.kr/api', + baseURL: process.env.DEPLOY_ENV !== 'production' ? BASE_URL.DEV : BASE_URL.PROD, headers: { 'Content-type': 'application/json', }, diff --git a/frontend/src/api/presets.ts b/frontend/src/api/presets.ts new file mode 100644 index 000000000..b4a1c51d1 --- /dev/null +++ b/frontend/src/api/presets.ts @@ -0,0 +1,26 @@ +import { AxiosResponse } from 'axios'; +import { QueryFunction, QueryKey } from 'react-query'; +import { ReservationSettings } from 'types/common'; +import { QueryPresetsSuccess } from 'types/response'; +import api from './api'; + +export interface PostPresetParams { + name: string; + settingsRequest: ReservationSettings; +} + +export interface DeletePresetParams { + id: number; +} + +export const queryPresets: QueryFunction, [QueryKey]> = () => + api.get('/members/presets'); + +export const postPreset = ({ + name, + settingsRequest, +}: PostPresetParams): Promise> => + api.post('/members/presets', { name, settingsRequest }); + +export const deletePreset = ({ id }: DeletePresetParams): Promise> => + api.delete(`/members/presets/${id}`); diff --git a/frontend/src/assets/images/bada.png b/frontend/src/assets/images/bada.png index 33f96ac83..55800a22c 100644 Binary files a/frontend/src/assets/images/bada.png and b/frontend/src/assets/images/bada.png differ diff --git a/frontend/src/assets/images/cheffe.png b/frontend/src/assets/images/cheffe.png index f8181011e..9eded75cb 100644 Binary files a/frontend/src/assets/images/cheffe.png and b/frontend/src/assets/images/cheffe.png differ diff --git a/frontend/src/assets/images/github-logo.png b/frontend/src/assets/images/github-logo.png index 377f808a7..72cc3f768 100644 Binary files a/frontend/src/assets/images/github-logo.png and b/frontend/src/assets/images/github-logo.png differ diff --git a/frontend/src/assets/images/kimkim.png b/frontend/src/assets/images/kimkim.png index be087f67b..1fc3aac26 100644 Binary files a/frontend/src/assets/images/kimkim.png and b/frontend/src/assets/images/kimkim.png differ diff --git a/frontend/src/assets/images/map-default.jpg b/frontend/src/assets/images/map-default.jpg index 7fd1ba2d5..e70849c77 100644 Binary files a/frontend/src/assets/images/map-default.jpg and b/frontend/src/assets/images/map-default.jpg differ diff --git a/frontend/src/assets/images/map-editor.png b/frontend/src/assets/images/map-editor.png index 8d1286bd9..f60563e5b 100644 Binary files a/frontend/src/assets/images/map-editor.png and b/frontend/src/assets/images/map-editor.png differ diff --git a/frontend/src/assets/images/reservation-page.png b/frontend/src/assets/images/reservation-page.png index e6040857f..ad6c71f54 100644 Binary files a/frontend/src/assets/images/reservation-page.png and b/frontend/src/assets/images/reservation-page.png differ diff --git a/frontend/src/assets/images/sakjung.png b/frontend/src/assets/images/sakjung.png index b80740cc0..07a213df5 100644 Binary files a/frontend/src/assets/images/sakjung.png and b/frontend/src/assets/images/sakjung.png differ diff --git a/frontend/src/assets/images/sally.png b/frontend/src/assets/images/sally.png index 1575f4b33..0242e7bc3 100644 Binary files a/frontend/src/assets/images/sally.png and b/frontend/src/assets/images/sally.png differ diff --git a/frontend/src/assets/images/space-editor.png b/frontend/src/assets/images/space-editor.png index 59f5d3202..4fb513a0c 100644 Binary files a/frontend/src/assets/images/space-editor.png and b/frontend/src/assets/images/space-editor.png differ diff --git a/frontend/src/assets/images/sun.png b/frontend/src/assets/images/sun.png index aea6ad207..1d4cb0aa4 100644 Binary files a/frontend/src/assets/images/sun.png and b/frontend/src/assets/images/sun.png differ diff --git a/frontend/src/assets/images/youtube-logo.png b/frontend/src/assets/images/youtube-logo.png index 4e48aaef8..9b2a942e7 100644 Binary files a/frontend/src/assets/images/youtube-logo.png and b/frontend/src/assets/images/youtube-logo.png differ diff --git a/frontend/src/assets/images/yujo.png b/frontend/src/assets/images/yujo.png index 708be82f8..8f0a6a8b7 100644 Binary files a/frontend/src/assets/images/yujo.png and b/frontend/src/assets/images/yujo.png differ diff --git a/frontend/src/components/Header/Header.tsx b/frontend/src/components/Header/Header.tsx index 09ca50a4e..75be8e2fd 100644 --- a/frontend/src/components/Header/Header.tsx +++ b/frontend/src/components/Header/Header.tsx @@ -1,5 +1,6 @@ import { useHistory, useParams } from 'react-router-dom'; import { useRecoilState } from 'recoil'; +import { queryClient } from 'App'; import { ReactComponent as LogoIcon } from 'assets/svg/logo.svg'; import PATH, { HREF } from 'constants/path'; import { LOCAL_STORAGE_KEY } from 'constants/storage'; @@ -29,6 +30,7 @@ const Header = (): JSX.Element => { const handleLogout = () => { setAccessToken(''); + queryClient.clear(); localStorage.removeItem(LOCAL_STORAGE_KEY.ACCESS_TOKEN); history.push(PATH.MANAGER_LOGIN); diff --git a/frontend/src/components/Modal/Modal.tsx b/frontend/src/components/Modal/Modal.tsx index 1215712c7..2cf5052ee 100644 --- a/frontend/src/components/Modal/Modal.tsx +++ b/frontend/src/components/Modal/Modal.tsx @@ -16,14 +16,14 @@ const Modal = ({ onClose, children, }: PropsWithChildren): JSX.Element => { - const handleDimmedClick: MouseEventHandler = ({ target, currentTarget }) => { + const handleMouseDownOverlay: MouseEventHandler = ({ target, currentTarget }) => { if (isClosableDimmer && target === currentTarget) { onClose(); } }; return ( - + {open && showCloseButton && ( diff --git a/frontend/src/components/Panel/Panel.styles.ts b/frontend/src/components/Panel/Panel.styles.ts index a8e7fd297..06fd7566c 100644 --- a/frontend/src/components/Panel/Panel.styles.ts +++ b/frontend/src/components/Panel/Panel.styles.ts @@ -6,6 +6,14 @@ interface PanelProps { export const Panel = styled.div` margin: ${({ expanded }) => (expanded ? '1.5rem 0' : '0')}; + + &:first-of-type { + margin-top: 0; + } + + &:last-of-type { + margin-bottom: 0; + } `; export const Title = styled.p` diff --git a/frontend/src/components/Panel/Panel.tsx b/frontend/src/components/Panel/Panel.tsx index d178feb35..1fea1f760 100644 --- a/frontend/src/components/Panel/Panel.tsx +++ b/frontend/src/components/Panel/Panel.tsx @@ -1,10 +1,10 @@ -import { PropsWithChildren, useState } from 'react'; +import { HTMLAttributes, PropsWithChildren, useState } from 'react'; import * as Styled from './Panel.styles'; import PanelContent from './PanelContent'; import PanelContext from './PanelContext'; import PanelHeader from './PanelHeader'; -export interface Props { +export interface Props extends HTMLAttributes { expandable?: boolean; initialExpanded?: boolean; } diff --git a/frontend/src/components/ReservationListItem/ReservationListItem.tsx b/frontend/src/components/ReservationListItem/ReservationListItem.tsx index df17c6d23..5e47d79a5 100644 --- a/frontend/src/components/ReservationListItem/ReservationListItem.tsx +++ b/frontend/src/components/ReservationListItem/ReservationListItem.tsx @@ -8,14 +8,14 @@ export interface Props { control?: ReactNode; } -const ReservationListItem = ({ reservation, control }: Props): JSX.Element => { +const ReservationListItem = ({ reservation, control, ...props }: Props): JSX.Element => { const { name, description, startDateTime, endDateTime } = reservation; const start = formatTime(new Date(startDateTime)); const end = formatTime(new Date(endDateTime)); return ( - + {name} {description} diff --git a/frontend/src/components/Select/Select.styles.ts b/frontend/src/components/Select/Select.styles.ts index 8c0cdba8b..0547a617b 100644 --- a/frontend/src/components/Select/Select.styles.ts +++ b/frontend/src/components/Select/Select.styles.ts @@ -44,6 +44,10 @@ export const ListBoxButton = styled.button` } `; +export const ListBoxLabel = styled.span` + color: ${({ theme }) => theme.gray[400]}; +`; + export const OptionChildrenWrapper = styled.div` flex: 1; `; diff --git a/frontend/src/components/Select/Select.tsx b/frontend/src/components/Select/Select.tsx index a69e8b500..a075a37d2 100644 --- a/frontend/src/components/Select/Select.tsx +++ b/frontend/src/components/Select/Select.tsx @@ -1,16 +1,18 @@ -import { useState, PropsWithChildren } from 'react'; +import { useState, ReactNode, useEffect } from 'react'; import { ReactComponent as CaretDownIcon } from 'assets/svg/caret-down.svg'; import PALETTE from 'constants/palette'; import * as Styled from './Select.styles'; interface Option { value: string; + title?: string; + children: ReactNode; } export interface Props { name: string; label: string; - options: PropsWithChildren