diff --git a/customer/pom.xml b/customer/pom.xml index 6c7bdc245e..420f810b56 100644 --- a/customer/pom.xml +++ b/customer/pom.xml @@ -87,6 +87,19 @@ commons-validator 1.7 + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.postgresql + postgresql + runtime + + + org.liquibase + liquibase-core + diff --git a/customer/src/main/java/com/yas/customer/config/DatabaseAutoConfig.java b/customer/src/main/java/com/yas/customer/config/DatabaseAutoConfig.java new file mode 100644 index 0000000000..11f5057881 --- /dev/null +++ b/customer/src/main/java/com/yas/customer/config/DatabaseAutoConfig.java @@ -0,0 +1,28 @@ +package com.yas.customer.config; + +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.domain.AuditorAware; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.Optional; + +@Configuration +@EnableJpaRepositories("com.yas.customer.repository") +@EntityScan({"com.yas.customer.model"}) +@EnableJpaAuditing(auditorAwareRef = "auditorAware") +public class DatabaseAutoConfig { + + @Bean + public AuditorAware auditorAware() { + return () -> { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth == null) return Optional.of(""); + return Optional.of(auth.getName()); + }; + } +} diff --git a/customer/src/main/java/com/yas/customer/controller/UserAddressController.java b/customer/src/main/java/com/yas/customer/controller/UserAddressController.java new file mode 100644 index 0000000000..c27960905f --- /dev/null +++ b/customer/src/main/java/com/yas/customer/controller/UserAddressController.java @@ -0,0 +1,37 @@ +package com.yas.customer.controller; + +import com.yas.customer.service.UserAddressService; +import com.yas.customer.viewmodel.AddressVm; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +public class UserAddressController { + + private final UserAddressService userAddressService; + + @GetMapping("/storefront/user-address") + public ResponseEntity> getUserAddresses() { + return ResponseEntity.ok(userAddressService.getUserAddressList()); + } + + @PostMapping("/storefront/user-address/{id}") + public ResponseEntity createAddress(@PathVariable Long id) { + userAddressService.createAddress(id); + return ResponseEntity.ok().build(); + } + + @DeleteMapping("/storefront/user-address/{id}") + public ResponseEntity deleteAddress(@PathVariable Long id) { + userAddressService.deleteAddress(id); + return ResponseEntity.ok().build(); + } +} diff --git a/customer/src/main/java/com/yas/customer/listener/CustomAuditingEntityListener.java b/customer/src/main/java/com/yas/customer/listener/CustomAuditingEntityListener.java new file mode 100644 index 0000000000..5f91f713ea --- /dev/null +++ b/customer/src/main/java/com/yas/customer/listener/CustomAuditingEntityListener.java @@ -0,0 +1,38 @@ +package com.yas.customer.listener; + +import com.yas.customer.model.AbstractAuditEntity; +import jakarta.persistence.PrePersist; +import jakarta.persistence.PreUpdate; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.auditing.AuditingHandler; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@Configuration +public class CustomAuditingEntityListener extends AuditingEntityListener { + public CustomAuditingEntityListener(ObjectFactory handler) { + super.setAuditingHandler(handler); + } + + @Override + @PrePersist + public void touchForCreate(Object target) { + AbstractAuditEntity entity = (AbstractAuditEntity) target; + if (entity.getCreatedBy() == null) { + super.touchForCreate(target); + } else { + if (entity.getLastModifiedBy() == null) { + entity.setLastModifiedBy(entity.getCreatedBy()); + } + } + } + + @Override + @PreUpdate + public void touchForUpdate(Object target) { + AbstractAuditEntity entity = (AbstractAuditEntity) target; + if (entity.getLastModifiedBy() == null) { + super.touchForUpdate(target); + } + } +} diff --git a/customer/src/main/java/com/yas/customer/model/AbstractAuditEntity.java b/customer/src/main/java/com/yas/customer/model/AbstractAuditEntity.java new file mode 100644 index 0000000000..29aad56072 --- /dev/null +++ b/customer/src/main/java/com/yas/customer/model/AbstractAuditEntity.java @@ -0,0 +1,32 @@ +package com.yas.customer.model; + +import com.yas.customer.listener.CustomAuditingEntityListener; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.LastModifiedBy; + +import java.time.ZonedDateTime; + +@MappedSuperclass +@Getter +@Setter +@EntityListeners(CustomAuditingEntityListener.class) +public class AbstractAuditEntity { + + @CreationTimestamp + private ZonedDateTime createdOn; + + @CreatedBy + private String createdBy; + + @UpdateTimestamp + private ZonedDateTime lastModifiedOn; + + @LastModifiedBy + private String lastModifiedBy; +} diff --git a/customer/src/main/java/com/yas/customer/model/UserAddress.java b/customer/src/main/java/com/yas/customer/model/UserAddress.java new file mode 100644 index 0000000000..a28e30492f --- /dev/null +++ b/customer/src/main/java/com/yas/customer/model/UserAddress.java @@ -0,0 +1,37 @@ +package com.yas.customer.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.LastModifiedBy; + +import java.time.ZonedDateTime; + +@Entity +@Table(name = "user_address") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class UserAddress extends AbstractAuditEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String userId; + + private Long addressId; + + private Boolean isActive; +} diff --git a/customer/src/main/java/com/yas/customer/repository/UserAddressRepository.java b/customer/src/main/java/com/yas/customer/repository/UserAddressRepository.java new file mode 100644 index 0000000000..1f191bc0da --- /dev/null +++ b/customer/src/main/java/com/yas/customer/repository/UserAddressRepository.java @@ -0,0 +1,12 @@ +package com.yas.customer.repository; + +import com.yas.customer.model.UserAddress; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface UserAddressRepository extends JpaRepository { + List findAllByUserId(String userId); + + UserAddress findOneByUserIdAndAddressId(String userId, Long id); +} diff --git a/customer/src/main/java/com/yas/customer/service/UserAddressService.java b/customer/src/main/java/com/yas/customer/service/UserAddressService.java new file mode 100644 index 0000000000..93632964ee --- /dev/null +++ b/customer/src/main/java/com/yas/customer/service/UserAddressService.java @@ -0,0 +1,47 @@ +package com.yas.customer.service; + +import com.yas.customer.exception.NotFoundException; +import com.yas.customer.model.UserAddress; +import com.yas.customer.repository.UserAddressRepository; +import com.yas.customer.utils.Constants; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +public class UserAddressService { + private final UserAddressRepository userAddressRepository; + + public List getUserAddressList() { + String userId = SecurityContextHolder.getContext().getAuthentication().getName(); + + List userAddressList = userAddressRepository.findAllByUserId(userId); + return userAddressList.stream() + .map(UserAddress::getAddressId) + .toList(); + } + + public void createAddress(Long addressId) { + String userId = SecurityContextHolder.getContext().getAuthentication().getName(); + UserAddress userAddress = UserAddress.builder() + .userId(userId) + .addressId(addressId) + .isActive(false) + .build(); + + userAddressRepository.save(userAddress); + } + + public void deleteAddress(Long id) { + String userId = SecurityContextHolder.getContext().getAuthentication().getName(); + UserAddress userAddress = userAddressRepository.findOneByUserIdAndAddressId(userId, id); + if (userAddress == null) { + throw new NotFoundException(Constants.ERROR_CODE.USER_ADDRESS_NOT_FOUND); + } + userAddressRepository.delete(userAddress); + } +} diff --git a/customer/src/main/java/com/yas/customer/utils/Constants.java b/customer/src/main/java/com/yas/customer/utils/Constants.java index fcc23d67ea..60bdfa666c 100644 --- a/customer/src/main/java/com/yas/customer/utils/Constants.java +++ b/customer/src/main/java/com/yas/customer/utils/Constants.java @@ -6,5 +6,6 @@ public final class ERROR_CODE { public static final String USER_WITH_EMAIL_NOT_FOUND = "USER_WITH_EMAIL_NOT_FOUND"; public static final String WRONG_EMAIL_FORMAT = "WRONG_EMAIL_FORMAT"; public static final String USER_NOT_FOUND = "USER_NOT_FOUND"; + public static final String USER_ADDRESS_NOT_FOUND = "USER_ADDRESS_NOT_FOUND"; } } diff --git a/customer/src/main/java/com/yas/customer/viewmodel/AddressVm.java b/customer/src/main/java/com/yas/customer/viewmodel/AddressVm.java new file mode 100644 index 0000000000..452b571f0d --- /dev/null +++ b/customer/src/main/java/com/yas/customer/viewmodel/AddressVm.java @@ -0,0 +1,4 @@ +package com.yas.customer.viewmodel; + +public record AddressVm(Long id, String contactName, String phone, String addressLine1) { +} diff --git a/customer/src/main/resources/application.properties b/customer/src/main/resources/application.properties index d494386bb1..f3f911a7da 100644 --- a/customer/src/main/resources/application.properties +++ b/customer/src/main/resources/application.properties @@ -9,6 +9,20 @@ logging.pattern.level=%5p [${spring.application.name:},%X{traceId:-},%X{spanId:- spring.security.oauth2.resourceserver.jwt.issuer-uri=http://identity/realms/Yas +spring.datasource.driver-class-name=org.postgresql.Driver +spring.datasource.url=jdbc:postgresql://localhost:5432/customer +spring.datasource.username=admin +spring.datasource.password=admin + +# The SQL dialect makes Hibernate generate better SQL for the chosen database +spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect + +# Hibernate ddl auto (none, create, create-drop, validate, update) +spring.jpa.hibernate.ddl-auto = none + +#Enable liquibase +spring.liquibase.enabled=true + keycloak.auth-server-url=http://identity keycloak.realm=Yas keycloak.resource=customer-management diff --git a/customer/src/main/resources/db/changelog/data/changelog-0001.sql b/customer/src/main/resources/db/changelog/data/changelog-0001.sql new file mode 100644 index 0000000000..5cecd1273c --- /dev/null +++ b/customer/src/main/resources/db/changelog/data/changelog-0001.sql @@ -0,0 +1 @@ +--liquibase formatted sql diff --git a/customer/src/main/resources/db/changelog/db.changelog-master.yaml b/customer/src/main/resources/db/changelog/db.changelog-master.yaml new file mode 100644 index 0000000000..419ad29d64 --- /dev/null +++ b/customer/src/main/resources/db/changelog/db.changelog-master.yaml @@ -0,0 +1,5 @@ +databaseChangeLog: + - includeAll: + path: db/changelog/ddl/ + - includeAll: + path: db/changelog/data/ \ No newline at end of file diff --git a/customer/src/main/resources/db/changelog/ddl/changelog-0001.sql b/customer/src/main/resources/db/changelog/ddl/changelog-0001.sql new file mode 100644 index 0000000000..7d758208b1 --- /dev/null +++ b/customer/src/main/resources/db/changelog/ddl/changelog-0001.sql @@ -0,0 +1 @@ +create table user_address (id bigserial not null, created_by varchar(255), created_on timestamp(6), last_modified_by varchar(255), last_modified_on timestamp(6), user_id varchar(255) not null, address_id bigserial not null, is_active boolean, primary key(id)) \ No newline at end of file diff --git a/customer/src/main/resources/messages/messages.properties b/customer/src/main/resources/messages/messages.properties index 375b873b8f..9a1e9870b8 100644 --- a/customer/src/main/resources/messages/messages.properties +++ b/customer/src/main/resources/messages/messages.properties @@ -1,3 +1,4 @@ -USER_WITH_EMAIL_NOT_FOUND = User with email {} not found -WRONG_EMAIL_FORMAT = Wrong email format for {} -USER_NOT_FOUND = User not found +USER_WITH_EMAIL_NOT_FOUND=User with email {} not found +WRONG_EMAIL_FORMAT=Wrong email format for {} +USER_NOT_FOUND=User not found +USER_ADDRESS_NOT_FOUND=User address not found diff --git a/customer/src/test/java/com/yas/customer/CustomerApplicationTests.java b/customer/src/test/java/com/yas/customer/CustomerApplicationTests.java deleted file mode 100644 index ccd373e644..0000000000 --- a/customer/src/test/java/com/yas/customer/CustomerApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.yas.customer; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class CustomerApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/customer/src/test/java/service/CustomerServiceTest.java b/customer/src/test/java/service/CustomerServiceTest.java new file mode 100644 index 0000000000..21a5288c3a --- /dev/null +++ b/customer/src/test/java/service/CustomerServiceTest.java @@ -0,0 +1,15 @@ +package service; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class CustomerServiceTest { + @BeforeEach + void setUp() { + + } + + @Test + void Test() { + } +} diff --git a/location/src/main/java/com/yas/location/controller/AddressController.java b/location/src/main/java/com/yas/location/controller/AddressController.java new file mode 100644 index 0000000000..b2614aea7d --- /dev/null +++ b/location/src/main/java/com/yas/location/controller/AddressController.java @@ -0,0 +1,51 @@ +package com.yas.location.controller; + +import com.yas.location.mapper.AddressResponseMapper; +import com.yas.location.service.AddressService; +import com.yas.location.viewmodel.address.AddressGetVm; +import com.yas.location.viewmodel.address.AddressPostVm; +import com.yas.location.viewmodel.address.RequestAddressGetListVm; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +public class AddressController { + private final AddressService addressService; + + @PostMapping("/storefront/address") + public ResponseEntity createAddress(@RequestBody AddressPostVm dto) { + return ResponseEntity.ok(addressService.createAddress(dto).getId()); + } + + @PutMapping("/storefront/address/{id}") + public ResponseEntity updateAddress(@PathVariable Long id, @RequestBody AddressPostVm dto) { + addressService.updateAddress(id, dto); + return ResponseEntity.noContent().build(); + } + + @GetMapping("/storefront/address/{id}") + public ResponseEntity getAddress(@PathVariable Long id) { + return ResponseEntity.ok(addressService.getAddress(id)); + } + + @PostMapping("/storefront/addresses") + public ResponseEntity> getAddressList(@RequestBody RequestAddressGetListVm dto) { + return ResponseEntity.ok(addressService.getAddressList(dto.ids())); + } + + @DeleteMapping("/storefront/address/{id}") + public ResponseEntity deleteAddress(@PathVariable Long id) { + addressService.deleteAddress(id); + return ResponseEntity.ok().build(); + } +} diff --git a/location/src/main/java/com/yas/location/controller/CountryStorefrontController.java b/location/src/main/java/com/yas/location/controller/CountryStorefrontController.java new file mode 100644 index 0000000000..8c8b950cc6 --- /dev/null +++ b/location/src/main/java/com/yas/location/controller/CountryStorefrontController.java @@ -0,0 +1,24 @@ +package com.yas.location.controller; + +import com.yas.location.service.CountryService; +import com.yas.location.utils.Constants; +import com.yas.location.viewmodel.country.CountryVm; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping(Constants.ApiConstant.COUNTRIES_STOREFRONT_URL) +@RequiredArgsConstructor +public class CountryStorefrontController { + private final CountryService countryService; + + @GetMapping + public ResponseEntity> listCountries() { + return ResponseEntity.ok(countryService.findAllCountries()); + } +} diff --git a/location/src/main/java/com/yas/location/controller/DistrictStorefrontController.java b/location/src/main/java/com/yas/location/controller/DistrictStorefrontController.java new file mode 100644 index 0000000000..ea05cbc0fe --- /dev/null +++ b/location/src/main/java/com/yas/location/controller/DistrictStorefrontController.java @@ -0,0 +1,22 @@ +package com.yas.location.controller; + +import com.yas.location.service.DistrictService; +import com.yas.location.viewmodel.district.DistrictGetVm; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +public class DistrictStorefrontController { + private final DistrictService districtService; + + @GetMapping("/storefront/district/{id}") + public ResponseEntity> getList(@PathVariable Long id) { + return ResponseEntity.ok(districtService.getList(id)); + } +} diff --git a/location/src/main/java/com/yas/location/controller/StateOrProvinceStoreFrontController.java b/location/src/main/java/com/yas/location/controller/StateOrProvinceStoreFrontController.java new file mode 100644 index 0000000000..fafa6f7fc8 --- /dev/null +++ b/location/src/main/java/com/yas/location/controller/StateOrProvinceStoreFrontController.java @@ -0,0 +1,34 @@ +package com.yas.location.controller; + +import com.yas.location.service.StateOrProvinceService; +import com.yas.location.utils.Constants; +import com.yas.location.viewmodel.error.ErrorVm; +import com.yas.location.viewmodel.stateorprovince.StateOrProvinceVm; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping(Constants.ApiConstant.STATE_OR_PROVINCES_STOREFRONT_URL) +public class StateOrProvinceStoreFrontController { + private final StateOrProvinceService stateOrProvinceService; + + @GetMapping("/{countryId}") + @ApiResponses(value = { + @ApiResponse(responseCode = Constants.ApiConstant.CODE_200, description = Constants.ApiConstant.OK, content = @Content(schema = @Schema(implementation = StateOrProvinceVm.class))), + @ApiResponse(responseCode = Constants.ApiConstant.CODE_404, description = Constants.ApiConstant.NOT_FOUND, content = @Content(schema = @Schema(implementation = ErrorVm.class)))}) + public ResponseEntity> getStateOrProvince(@PathVariable("countryId") final Long id) { + return ResponseEntity.ok(stateOrProvinceService.getAllByCountryId(id)); + } + +} diff --git a/location/src/main/java/com/yas/location/mapper/AddressResponseMapper.java b/location/src/main/java/com/yas/location/mapper/AddressResponseMapper.java new file mode 100644 index 0000000000..558a305130 --- /dev/null +++ b/location/src/main/java/com/yas/location/mapper/AddressResponseMapper.java @@ -0,0 +1,11 @@ +package com.yas.location.mapper; + +public interface AddressResponseMapper { + Long getId(); + + String getContactName(); + + String getPhone(); + + String getAddressLine1(); +} diff --git a/location/src/main/java/com/yas/location/model/Address.java b/location/src/main/java/com/yas/location/model/Address.java index e594165164..89aab284f5 100644 --- a/location/src/main/java/com/yas/location/model/Address.java +++ b/location/src/main/java/com/yas/location/model/Address.java @@ -34,10 +34,10 @@ public class Address extends AbstractAuditEntity { @Column(length = 25) private String phone; - @Column(length = 450) + @Column(length = 450, name = "address_line_1") private String addressLine1; - @Column(length = 450) + @Column(length = 450, name = "address_line_2") private String addressLine2; @Column(length = 450) diff --git a/location/src/main/java/com/yas/location/model/District.java b/location/src/main/java/com/yas/location/model/District.java index 134ab9e9f4..3069bdb860 100644 --- a/location/src/main/java/com/yas/location/model/District.java +++ b/location/src/main/java/com/yas/location/model/District.java @@ -38,5 +38,5 @@ public class District extends AbstractAuditEntity { @ManyToOne @JoinColumn(name = "state_or_province_id", nullable = false) - private StateOrProvince stateOrProvince; + private StateOrProvince stateProvince; } diff --git a/location/src/main/java/com/yas/location/repository/AddressRepository.java b/location/src/main/java/com/yas/location/repository/AddressRepository.java new file mode 100644 index 0000000000..d25dfaa92d --- /dev/null +++ b/location/src/main/java/com/yas/location/repository/AddressRepository.java @@ -0,0 +1,11 @@ +package com.yas.location.repository; + +import com.yas.location.mapper.AddressResponseMapper; +import com.yas.location.model.Address; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface AddressRepository extends JpaRepository { + List findAllByIdIn(List ids); +} diff --git a/location/src/main/java/com/yas/location/repository/DistrictRepository.java b/location/src/main/java/com/yas/location/repository/DistrictRepository.java new file mode 100644 index 0000000000..95fc42f624 --- /dev/null +++ b/location/src/main/java/com/yas/location/repository/DistrictRepository.java @@ -0,0 +1,11 @@ +package com.yas.location.repository; + +import com.yas.location.model.District; +import com.yas.location.viewmodel.district.DistrictGetVm; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface DistrictRepository extends JpaRepository { + List findAllByStateProvinceId(Long stateProvinceId); +} diff --git a/location/src/main/java/com/yas/location/repository/StateOrProvinceRepository.java b/location/src/main/java/com/yas/location/repository/StateOrProvinceRepository.java index 0dc58477a9..62f899e152 100644 --- a/location/src/main/java/com/yas/location/repository/StateOrProvinceRepository.java +++ b/location/src/main/java/com/yas/location/repository/StateOrProvinceRepository.java @@ -8,6 +8,8 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface StateOrProvinceRepository extends JpaRepository { @@ -34,4 +36,6 @@ WHEN count(1)> 0 THEN TRUE """) Page getStateOrProvinceByCountry(@Param("countryId") final Long countryId, final Pageable pageable); + + List findAllByCountryId(Long countryId); } diff --git a/location/src/main/java/com/yas/location/service/AddressService.java b/location/src/main/java/com/yas/location/service/AddressService.java new file mode 100644 index 0000000000..d0f2037a66 --- /dev/null +++ b/location/src/main/java/com/yas/location/service/AddressService.java @@ -0,0 +1,71 @@ +package com.yas.location.service; + +import com.yas.location.exception.NotFoundException; +import com.yas.location.mapper.AddressResponseMapper; +import com.yas.location.model.Address; +import com.yas.location.model.Country; +import com.yas.location.repository.AddressRepository; +import com.yas.location.repository.CountryRepository; +import com.yas.location.repository.DistrictRepository; +import com.yas.location.repository.StateOrProvinceRepository; +import com.yas.location.utils.Constants; +import com.yas.location.viewmodel.address.AddressGetVm; +import com.yas.location.viewmodel.address.AddressPostVm; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +public class AddressService { + + private final AddressRepository addressRepository; + private final StateOrProvinceRepository stateOrProvinceRepository; + private final CountryRepository countryRepository; + private final DistrictRepository districtRepository; + + public Address createAddress(AddressPostVm dto) { + Address address = AddressPostVm.fromModel(dto); + stateOrProvinceRepository.findById(dto.stateOrProvinceId()).ifPresent(address::setStateOrProvince); + Country country = countryRepository.findById(dto.countryId()) + .orElseThrow(() -> new NotFoundException(Constants.ERROR_CODE.COUNTRY_NOT_FOUND, dto.countryId())); + address.setCountry(country); + districtRepository.findById(dto.districtId()).ifPresent(address::setDistrict); + return addressRepository.save(address); + } + + public void updateAddress(Long id, AddressPostVm dto) { + Address address = addressRepository.findById(id).orElseThrow(() -> + new NotFoundException(Constants.ERROR_CODE.ADDRESS_NOT_FOUND, id)); + + address.setContactName(dto.contactName()); + address.setAddressLine1(dto.addressLine1()); + address.setPhone(dto.phone()); + address.setCity(dto.city()); + address.setZipCode(dto.zipCode()); + + stateOrProvinceRepository.findById(dto.stateOrProvinceId()).ifPresent(address::setStateOrProvince); + countryRepository.findById(dto.countryId()).ifPresent(address::setCountry); + districtRepository.findById(dto.districtId()).ifPresent(address::setDistrict); + addressRepository.saveAndFlush(address); + } + + public List getAddressList(List ids) { + return addressRepository.findAllByIdIn(ids); + } + + public AddressGetVm getAddress(Long id) { + Address address = addressRepository.findById(id).orElseThrow(() -> + new NotFoundException(Constants.ERROR_CODE.ADDRESS_NOT_FOUND, id)); + return AddressGetVm.fromModel(address); + } + + public void deleteAddress(Long id) { + Address address = addressRepository.findById(id).orElseThrow(() -> + new NotFoundException(Constants.ERROR_CODE.ADDRESS_NOT_FOUND, id)); + addressRepository.delete(address); + } +} diff --git a/location/src/main/java/com/yas/location/service/DistrictService.java b/location/src/main/java/com/yas/location/service/DistrictService.java new file mode 100644 index 0000000000..03990f271a --- /dev/null +++ b/location/src/main/java/com/yas/location/service/DistrictService.java @@ -0,0 +1,19 @@ +package com.yas.location.service; + +import com.yas.location.repository.DistrictRepository; +import com.yas.location.viewmodel.district.DistrictGetVm; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +public class DistrictService { + private final DistrictRepository districtRepository; + public List getList(Long id) { + return districtRepository.findAllByStateProvinceId(id); + } +} diff --git a/location/src/main/java/com/yas/location/service/StateOrProvinceService.java b/location/src/main/java/com/yas/location/service/StateOrProvinceService.java index 70cb4ce4cc..71663f5967 100644 --- a/location/src/main/java/com/yas/location/service/StateOrProvinceService.java +++ b/location/src/main/java/com/yas/location/service/StateOrProvinceService.java @@ -10,6 +10,8 @@ import com.yas.location.viewmodel.stateorprovince.StateOrProvinceListGetVm; import com.yas.location.viewmodel.stateorprovince.StateOrProvincePostVm; import com.yas.location.viewmodel.stateorprovince.StateOrProvinceVm; + +import java.util.ArrayList; import java.util.List; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -144,4 +146,11 @@ public StateOrProvinceListGetVm getPageableStateOrProvinces(int pageNo, int page stateOrProvincePage.isLast() ); } + + @Transactional(readOnly = true) + public List getAllByCountryId(Long countryId) { + return stateOrProvinceRepository.findAllByCountryId(countryId).stream() + .map(stateOrProvinceMapper::toStateOrProvinceViewModelFromStateOrProvince) + .toList(); + } } diff --git a/location/src/main/java/com/yas/location/utils/Constants.java b/location/src/main/java/com/yas/location/utils/Constants.java index b378a63d3f..042c17502a 100644 --- a/location/src/main/java/com/yas/location/utils/Constants.java +++ b/location/src/main/java/com/yas/location/utils/Constants.java @@ -7,6 +7,7 @@ public final class ERROR_CODE { public static final String COUNTRY_NOT_FOUND = "COUNTRY_NOT_FOUND"; public static final String NAME_ALREADY_EXITED = "NAME_ALREADY_EXITED"; public static final String STATE_OR_PROVINCE_NOT_FOUND = "STATE_OR_PROVINCE_NOT_FOUND"; + public static final String ADDRESS_NOT_FOUND = "ADDRESS_NOT_FOUND"; } public final class PageableConstant { @@ -18,8 +19,13 @@ public final class PageableConstant { public final class ApiConstant { public static final String STATE_OR_PROVINCES_URL = "/backoffice/state-or-provinces"; + + public static final String STATE_OR_PROVINCES_STOREFRONT_URL = "/storefront/state-or-provinces"; + public static final String COUNTRIES_URL = "/backoffice/countries"; + public static final String COUNTRIES_STOREFRONT_URL = "/storefront/countries"; + public static final String CODE_200 = "200"; public static final String OK = "Ok"; public static final String CODE_404 = "404"; diff --git a/location/src/main/java/com/yas/location/viewmodel/address/AddressGetVm.java b/location/src/main/java/com/yas/location/viewmodel/address/AddressGetVm.java new file mode 100644 index 0000000000..4600c0905d --- /dev/null +++ b/location/src/main/java/com/yas/location/viewmodel/address/AddressGetVm.java @@ -0,0 +1,12 @@ +package com.yas.location.viewmodel.address; + +import com.yas.location.model.Address; + +public record AddressGetVm(String contactName, String phone, String addressLine1, String city, String zipCode, + Long districtId, Long stateOrProvinceId, Long countryId) { + public static AddressGetVm fromModel(Address address) { + return new AddressGetVm(address.getContactName(), address.getPhone(), address.getAddressLine1(), + address.getCity(), address.getZipCode(), address.getDistrict().getId(), + address.getStateOrProvince().getId(), address.getCountry().getId()); + } +} diff --git a/location/src/main/java/com/yas/location/viewmodel/address/AddressPostVm.java b/location/src/main/java/com/yas/location/viewmodel/address/AddressPostVm.java new file mode 100644 index 0000000000..fa0d0a8b86 --- /dev/null +++ b/location/src/main/java/com/yas/location/viewmodel/address/AddressPostVm.java @@ -0,0 +1,17 @@ +package com.yas.location.viewmodel.address; + +import com.yas.location.model.Address; + +public record AddressPostVm(String contactName, String phone, String addressLine1, String city, String zipCode, + Long districtId, Long stateOrProvinceId, Long countryId) { + + public static Address fromModel(AddressPostVm dto) { + return Address.builder() + .contactName(dto.contactName) + .phone(dto.phone) + .addressLine1(dto.addressLine1) + .city(dto.city()) + .zipCode(dto.zipCode) + .build(); + } +} diff --git a/location/src/main/java/com/yas/location/viewmodel/address/RequestAddressGetListVm.java b/location/src/main/java/com/yas/location/viewmodel/address/RequestAddressGetListVm.java new file mode 100644 index 0000000000..2a4a5210f5 --- /dev/null +++ b/location/src/main/java/com/yas/location/viewmodel/address/RequestAddressGetListVm.java @@ -0,0 +1,6 @@ +package com.yas.location.viewmodel.address; + +import java.util.List; + +public record RequestAddressGetListVm(List ids) { +} diff --git a/location/src/main/java/com/yas/location/viewmodel/district/DistrictGetVm.java b/location/src/main/java/com/yas/location/viewmodel/district/DistrictGetVm.java new file mode 100644 index 0000000000..8564dc98d0 --- /dev/null +++ b/location/src/main/java/com/yas/location/viewmodel/district/DistrictGetVm.java @@ -0,0 +1,4 @@ +package com.yas.location.viewmodel.district; + +public record DistrictGetVm(Long id, String name) { +} diff --git a/location/src/main/resources/messages/messages.properties b/location/src/main/resources/messages/messages.properties index e1539d57e8..82dd3f20bf 100644 --- a/location/src/main/resources/messages/messages.properties +++ b/location/src/main/resources/messages/messages.properties @@ -1,3 +1,5 @@ COUNTRY_NOT_FOUND=The country {} is not found NAME_ALREADY_EXITED=Request name {} is already existed STATE_OR_PROVINCE_NOT_FOUND=The state or province {} is not found +ADDRESS_NOT_FOUND=The address {} is not found + diff --git a/postgres_init.sql b/postgres_init.sql index 0f96f64c97..40f007dda1 100644 --- a/postgres_init.sql +++ b/postgres_init.sql @@ -27,3 +27,6 @@ LIMIT = -1; CREATE DATABASE tax WITH OWNER = admin ENCODING = 'UTF8' LC_COLLATE = 'en_US.utf8' LC_CTYPE = 'en_US.utf8' TABLESPACE = pg_default CONNECTION LIMIT = -1; +CREATE DATABASE customer WITH OWNER = admin ENCODING = 'UTF8' LC_COLLATE = 'en_US.utf8' LC_CTYPE = 'en_US.utf8' TABLESPACE = pg_default CONNECTION +LIMIT = -1; + diff --git a/storefront-bff/src/main/resources/application.yaml b/storefront-bff/src/main/resources/application.yaml index bcde3ca9d4..d94bf5bcc6 100644 --- a/storefront-bff/src/main/resources/application.yaml +++ b/storefront-bff/src/main/resources/application.yaml @@ -43,6 +43,14 @@ spring: cloud: gateway: routes: + - id: api_location_local + uri: http://localhost:8086 +# uri: http://api.yas.local + predicates: + - Path=/api/location/** + filters: + - RewritePath=/api/(?.*), /$\{segment} + - TokenRelay= - id: api_product_local uri: http://localhost:8080 # uri: http://api.yas.local diff --git a/storefront/common/constants/Common.ts b/storefront/common/constants/Common.ts index 791c73bedb..53b80ae127 100644 --- a/storefront/common/constants/Common.ts +++ b/storefront/common/constants/Common.ts @@ -1 +1,3 @@ export const UPDATE_SUCCESSFULLY = 'Update successfully'; +export const CREATE_SUCCESSFULLY = 'Create successfully'; +export const DELETE_SUCCESSFULLY = 'Delete successfully'; diff --git a/storefront/common/items/ModalDeleteCustom.tsx b/storefront/common/items/ModalDeleteCustom.tsx new file mode 100644 index 0000000000..703e4c7edf --- /dev/null +++ b/storefront/common/items/ModalDeleteCustom.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Button, Modal } from 'react-bootstrap'; + +type Props = { + showModalDelete: boolean; + handleClose: () => void; + handleDelete: () => void; + action: string; +}; + +const ModalDeleteCustom = ({ showModalDelete, action, handleClose, handleDelete }: Props) => { + return ( + + {`Are you sure you want to ${action} ?`} + + + + + + ); +}; + +export default ModalDeleteCustom; diff --git a/storefront/common/items/OptionSelect.tsx b/storefront/common/items/OptionSelect.tsx new file mode 100644 index 0000000000..3393524183 --- /dev/null +++ b/storefront/common/items/OptionSelect.tsx @@ -0,0 +1,53 @@ +import { Path, RegisterOptions, UseFormRegister, FieldValues } from 'react-hook-form'; + +type Option = { + id: string | number; + name: string; +}; + +type OptionSelectProps = { + labelText: string; + field: Path; + register: UseFormRegister; + registerOptions?: RegisterOptions; + error?: string; + options?: Option[]; + defaultValue?: string | number; + placeholder?: string; + disabled?: boolean; +}; + +export const OptionSelect = ({ + labelText, + field, + register, + registerOptions, + error, + options, + defaultValue, + placeholder, + disabled, +}: OptionSelectProps) => ( +
+ + +

{error}

+
+); diff --git a/storefront/modules/address/components/AddressForm.tsx b/storefront/modules/address/components/AddressForm.tsx new file mode 100644 index 0000000000..4942c8d76a --- /dev/null +++ b/storefront/modules/address/components/AddressForm.tsx @@ -0,0 +1,150 @@ +import { useEffect, useState } from 'react'; +import { FieldErrorsImpl, UseFormRegister } from 'react-hook-form'; +import { Input } from '../../../common/items/Input'; +import { OptionSelect } from '../../../common/items/OptionSelect'; +import { Address } from '../../../modules/address/models/AddressModel'; +import { Country } from '../../country/models/Country'; +import { District } from '../../district/models/District'; +import { StateOrProvince } from '../../stateAndProvince/models/StateOrProvince'; +import { useRouter } from 'next/router'; +import { getCountries } from '../../country/services/CountryService'; +import { getStatesOrProvinces } from '../../stateAndProvince/services/StatesOrProvicesService'; +import { getDistricts } from '../../district/services/DistrictService'; + +type AddressFormProps = { + register: UseFormRegister
; + errors: FieldErrorsImpl
; + address: Address | undefined; +}; +const AddressForm = ({ register, errors, address }: AddressFormProps) => { + const router = useRouter(); + const { id } = router.query; + + const [countries, setCountries] = useState([]); + const [statesOrProvinces, setStatesOrProvinces] = useState([]); + const [districts, setDistricts] = useState([]); + + useEffect(() => { + getCountries().then((data) => { + setCountries(data); + }); + }, []); + + useEffect(() => { + if (address) { + getStatesOrProvinces(address.countryId).then((data) => { + setStatesOrProvinces(data); + }); + getDistricts(address.stateOrProvinceId).then((data) => { + setDistricts(data); + }); + } + }, [id]); + + const onCountryChange = async (event: any) => { + getStatesOrProvinces(event.target.value).then((data) => { + setStatesOrProvinces(data); + getDistricts(event.target.value).then((data) => { + if (data) { + setDistricts(data); + } else { + setDistricts([]); + } + }); + }); + }; + + const onStateOrProvinceChange = async (event: any) => { + getDistricts(event.target.value).then((data) => { + setDistricts(data); + }); + }; + + if (id && (countries.length == 0 || statesOrProvinces.length == 0 || districts.length == 0)) + return <>; + return ( +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ); +}; + +export default AddressForm; diff --git a/storefront/modules/address/models/AddressModel.ts b/storefront/modules/address/models/AddressModel.ts new file mode 100644 index 0000000000..d4dfdc3a81 --- /dev/null +++ b/storefront/modules/address/models/AddressModel.ts @@ -0,0 +1,11 @@ +export type Address = { + id?: number; + contactName: string; + phone: string; + addressLine1: string; + city: string; + zipCode?: string; + districtId: number; + stateOrProvinceId: number; + countryId: number; +}; diff --git a/storefront/modules/address/services/AddressService.tsx b/storefront/modules/address/services/AddressService.tsx new file mode 100644 index 0000000000..23bbacac56 --- /dev/null +++ b/storefront/modules/address/services/AddressService.tsx @@ -0,0 +1,41 @@ +import { Address } from '../models/AddressModel'; + +export async function createAddress(address: Address) { + const response = await fetch(`/api/location/storefront/address`, { + method: 'POST', + headers: { 'Content-type': 'application/json' }, + body: JSON.stringify(address), + }); + return response.json(); +} + +export async function updateAddress(id: string, address: Address) { + const response = await fetch(`/api/location/storefront/address/${id}`, { + method: 'PUT', + headers: { 'Content-type': 'application/json' }, + body: JSON.stringify(address), + }); + return response; +} + +export async function getAddress(id: string): Promise
{ + const response = await fetch(`/api/location/storefront/address/${id}`); + return await response.json(); +} + +export async function getAddresses(ids: number[]) { + const data = { ids }; + const response = await fetch(`/api/location/storefront/addresses`, { + method: 'POST', + headers: { 'Content-type': 'application/json' }, + body: JSON.stringify(data), + }); + return await response.json(); +} + +export async function deleteAddress(id: number) { + const response = await fetch(`/api/location/storefront/address/${id}`, { + method: 'DELETE', + }); + return response; +} diff --git a/storefront/modules/country/models/Country.ts b/storefront/modules/country/models/Country.ts new file mode 100644 index 0000000000..704f596247 --- /dev/null +++ b/storefront/modules/country/models/Country.ts @@ -0,0 +1,4 @@ +export type Country = { + id: number; + name: string; +}; diff --git a/storefront/modules/country/services/CountryService.ts b/storefront/modules/country/services/CountryService.ts new file mode 100644 index 0000000000..840b1f76d3 --- /dev/null +++ b/storefront/modules/country/services/CountryService.ts @@ -0,0 +1,6 @@ +import { Country } from '../models/Country'; + +export async function getCountries(): Promise { + const response = await fetch(`/api/location/storefront/countries`); + return await response.json(); +} diff --git a/storefront/modules/customer/services/CustomerService.tsx b/storefront/modules/customer/services/CustomerService.tsx new file mode 100644 index 0000000000..074b268951 --- /dev/null +++ b/storefront/modules/customer/services/CustomerService.tsx @@ -0,0 +1,18 @@ +export async function createUserAddress(id: number) { + const response = await fetch(`/api/customer/storefront/user-address/${id}`, { + method: 'POST', + }); + return response; +} + +export async function getAddressIds() { + const response = await fetch(`/api/customer/storefront/user-address`); + return response.json(); +} + +export async function deleteUserAddress(id: number) { + const response = await fetch(`/api/customer/storefront/user-address/${id}`, { + method: 'DELETE', + }); + return await response; +} diff --git a/storefront/modules/district/models/District.ts b/storefront/modules/district/models/District.ts new file mode 100644 index 0000000000..75d05aa13e --- /dev/null +++ b/storefront/modules/district/models/District.ts @@ -0,0 +1,4 @@ +export type District = { + id: number; + name: string; +}; diff --git a/storefront/modules/district/services/DistrictService.tsx b/storefront/modules/district/services/DistrictService.tsx new file mode 100644 index 0000000000..a95c0fc45a --- /dev/null +++ b/storefront/modules/district/services/DistrictService.tsx @@ -0,0 +1,4 @@ +export async function getDistricts(id: number) { + const response = await fetch(`/api/location/storefront/district/${id}`); + return response.json(); +} diff --git a/storefront/modules/stateAndProvince/models/StateOrProvince.ts b/storefront/modules/stateAndProvince/models/StateOrProvince.ts new file mode 100644 index 0000000000..ae3033cf2c --- /dev/null +++ b/storefront/modules/stateAndProvince/models/StateOrProvince.ts @@ -0,0 +1,4 @@ +export type StateOrProvince = { + id: number; + name: string; +}; diff --git a/storefront/modules/stateAndProvince/services/StatesOrProvicesService.tsx b/storefront/modules/stateAndProvince/services/StatesOrProvicesService.tsx new file mode 100644 index 0000000000..55d272af2f --- /dev/null +++ b/storefront/modules/stateAndProvince/services/StatesOrProvicesService.tsx @@ -0,0 +1,4 @@ +export async function getStatesOrProvinces(id: number) { + const response = await fetch(`/api/location/storefront/state-or-provinces/${id}`); + return response.json(); +} diff --git a/storefront/package-lock.json b/storefront/package-lock.json index af56061474..59abb0bdb4 100644 --- a/storefront/package-lock.json +++ b/storefront/package-lock.json @@ -18,6 +18,7 @@ "react-bootstrap": "^2.7.2", "react-dom": "^18.2.0", "react-hook-form": "^7.43.3", + "react-icons": "^4.4.0", "react-moment": "^1.1.3", "react-paginate": "^8.1.3", "react-star-ratings": "^2.3.0", @@ -2876,6 +2877,14 @@ "react": "^16.8.0 || ^17 || ^18" } }, + "node_modules/react-icons": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz", + "integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -5525,6 +5534,12 @@ "integrity": "sha512-LV6Fixh+hirrl6dXbM78aB6n//82aKbsNbcofF3wc6nx1UJLu3Jj/gsg1E5C9iISnLX+du8VTUyBUz2aCy+H7w==", "requires": {} }, + "react-icons": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz", + "integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==", + "requires": {} + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/storefront/package.json b/storefront/package.json index a0e7dcd1c1..2b36f781a4 100644 --- a/storefront/package.json +++ b/storefront/package.json @@ -23,7 +23,8 @@ "react-moment": "^1.1.3", "react-paginate": "^8.1.3", "react-star-ratings": "^2.3.0", - "react-toastify": "^9.0.8" + "react-toastify": "^9.0.8", + "react-icons": "^4.4.0" }, "devDependencies": { "@types/node": "18.0.0", diff --git a/storefront/pages/_app.tsx b/storefront/pages/_app.tsx index 6e5f8c00bf..e77bdbacae 100644 --- a/storefront/pages/_app.tsx +++ b/storefront/pages/_app.tsx @@ -15,6 +15,7 @@ import '../styles/Header.css'; import '../styles/HomePage.css'; import '../styles/productDetail.css'; import '../styles/util.css'; +import '../styles/form.css'; import type { AppProps } from 'next/app'; diff --git a/storefront/pages/address/[id]/edit.tsx b/storefront/pages/address/[id]/edit.tsx new file mode 100644 index 0000000000..0ad1477ef2 --- /dev/null +++ b/storefront/pages/address/[id]/edit.tsx @@ -0,0 +1,85 @@ +import { NextPage } from 'next'; +import Head from 'next/head'; +import AddressForm from '../../../modules/address/components/AddressForm'; +import { getAddress, updateAddress } from '../../../modules/address/services/AddressService'; +import { Address } from '../../../modules/address/models/AddressModel'; +import { useForm } from 'react-hook-form'; +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/router'; +import { toast } from 'react-toastify'; +import { UPDATE_SUCCESSFULLY } from '../../../common/constants/Common'; +import clsx from 'clsx'; +import styles from '../../../styles/address.module.css'; + +const EditAddress: NextPage = () => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm
(); + + const router = useRouter(); + const { id } = router.query; + router.isReady = true; + const [address, setAddress] = useState
(); + + useEffect(() => { + if (id) { + getAddress(id as string).then((data) => { + setAddress(data); + }); + } + }, [id]); + + const onSubmit = async (data: any) => { + const { + contactName, + phone, + addressLine1, + city, + zipCode, + districtId, + stateOrProvinceId, + countryId, + } = data; + const request: Address = { + contactName, + phone, + addressLine1, + city, + zipCode, + districtId: parseInt(districtId), + stateOrProvinceId: parseInt(stateOrProvinceId), + countryId: parseInt(countryId), + }; + updateAddress(id as string, request) + .then(() => { + toast.success(UPDATE_SUCCESSFULLY); + router.push('/address'); + }) + .catch((e) => console.log(e)); + }; + + if (!id) return <>; + if (id && !address) return

No address

; + return ( + <> + + Edit Address + +
+

Edit Address

+
+ +
+ +
+ +
+ + ); +}; + +export default EditAddress; diff --git a/storefront/pages/address/create.tsx b/storefront/pages/address/create.tsx new file mode 100644 index 0000000000..3369d4bba8 --- /dev/null +++ b/storefront/pages/address/create.tsx @@ -0,0 +1,77 @@ +import { NextPage } from 'next'; +import Head from 'next/head'; +import { useForm } from 'react-hook-form'; +import AddressForm from '../../modules/address/components/AddressForm'; +import { Address } from '../../modules/address/models/AddressModel'; +import { createAddress } from '../../modules/address/services/AddressService'; +import { createUserAddress } from '../../modules/customer/services/CustomerService'; +import { toast } from 'react-toastify'; +import { CREATE_SUCCESSFULLY } from '../../common/constants/Common'; +import { useRouter } from 'next/router'; +import styles from '../../styles/address.module.css'; +import clsx from 'clsx'; + +const CreateAddress: NextPage = () => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm
(); + + const router = useRouter(); + + const onSubmit = async (data: any) => { + const { + contactName, + phone, + addressLine1, + city, + zipCode, + districtId, + stateOrProvinceId, + countryId, + } = data; + const request: Address = { + contactName, + phone, + addressLine1, + city, + zipCode, + districtId: parseInt(districtId), + stateOrProvinceId: parseInt(stateOrProvinceId), + countryId: parseInt(countryId), + }; + createAddress(request) + .then((data) => { + createUserAddress(data); + }) + .then(() => { + toast.success(CREATE_SUCCESSFULLY); + router.push('/address'); + }) + .catch((e) => { + console.log(e); + }); + }; + + return ( + <> + + Create Address + +
+

Create Address

+
+ +
+ +
+ +
+ + ); +}; + +export default CreateAddress; diff --git a/storefront/pages/address/index.tsx b/storefront/pages/address/index.tsx new file mode 100644 index 0000000000..286436bf35 --- /dev/null +++ b/storefront/pages/address/index.tsx @@ -0,0 +1,167 @@ +import { NextPage } from 'next'; +import Head from 'next/head'; +import React, { useEffect, useState } from 'react'; + +import { Address } from '../../modules/address/models/AddressModel'; +import { deleteAddress, getAddresses } from '../../modules/address/services/AddressService'; +import { deleteUserAddress, getAddressIds } from '../../modules/customer/services/CustomerService'; +import ModalDeleteCustom from '../../common/items/ModalDeleteCustom'; +import { TiContacts } from 'react-icons/ti'; +import { HiCheckCircle } from 'react-icons/hi'; +import { FiEdit } from 'react-icons/fi'; +import { BiPlusMedical } from 'react-icons/bi'; +import { toast } from 'react-toastify'; +import { DELETE_SUCCESSFULLY } from '../../common/constants/Common'; +import { FaTrash } from 'react-icons/fa'; +import Link from 'next/link'; +import styles from '../../styles/address.module.css'; +import clsx from 'clsx'; + +const Address: NextPage = () => { + const [addresses, setAddresses] = useState([]); + + const [showModalDelete, setShowModalDelete] = useState(false); + const [addressIdWantToDelete, setAddressIdWantToDelete] = useState(0); + + const handleClose: any = () => setShowModalDelete(false); + const handleDelete: any = () => { + if (addressIdWantToDelete == 0) { + return; + } + deleteUserAddress(addressIdWantToDelete || 0) + .then(() => { + deleteAddress(addressIdWantToDelete || 0); + getAddressIds() + .then((data) => { + return getAddresses(data); + }) + .then((data) => setAddresses(data)); + setShowModalDelete(false); + toast.success(DELETE_SUCCESSFULLY); + }) + .catch((e) => { + console.log(e); + }); + }; + + useEffect(() => { + getAddressIds() + .then((data) => { + return getAddresses(data); + }) + .then((data) => setAddresses(data)); + }, []); + if (addresses.length == 0) return <>No address; + return ( + <> + + Address + +
+
+

Address list

+ +
+
+
+ {addresses.map((address) => { + return ( +
+
+
+
+
+ +
+
+
+
+ {/*
+
+
*/} +

Contact name: {address.contactName}

+

+ Address: {address.addressLine1} +

+

Phone number: {address.phone}

+
+
+ {/*
+ +
*/} +
+ + + +
+
{ + setShowModalDelete(true); + setAddressIdWantToDelete(address.id || 0); + }} + > + +
+
+
+
+
+
+ ); + })} +
+
+
+ + + ); +}; + +export default Address; diff --git a/storefront/pages/profile/index.tsx b/storefront/pages/profile/index.tsx index f8b2ba7141..c103e9ba31 100644 --- a/storefront/pages/profile/index.tsx +++ b/storefront/pages/profile/index.tsx @@ -10,6 +10,9 @@ import { Input } from '../../common/items/Input'; import { Customer } from '../../modules/profile/models/Customer'; import { ProfileRequest } from '../../modules/profile/models/ProfileRequest'; import { getMyProfile, updateCustomer } from '../../modules/profile/services/ProfileService'; +import styles from '../../styles/address.module.css'; +import { FaArrowRight } from 'react-icons/fa'; +import clsx from 'clsx'; const Profile: NextPage = () => { const { handleSubmit, register } = useForm(); @@ -41,7 +44,19 @@ const Profile: NextPage = () => { Profile -

User Profile

+
+

User Profile

+ +
diff --git a/storefront/styles/address.module.css b/storefront/styles/address.module.css new file mode 100644 index 0000000000..9bf1f9341d --- /dev/null +++ b/storefront/styles/address.module.css @@ -0,0 +1,27 @@ +.link-redirect { + font-weight: 600; + font-size: 18px; +} + +.link-redirect:hover { + color: #ffffff; +} + +.remove-address:hover { + cursor: pointer; +} + +.card-wrapper { + width: 100%; + margin-bottom: 22px; + border-radius: 5px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.1), 0 6px 20px 0 rgba(0, 0, 0, 0.1); + color: #ffffff; + font-weight: 600; +} + +.card-layout { + font-size: 20px; + height: 100%; + border-radius: 5px; +} diff --git a/storefront/styles/form.css b/storefront/styles/form.css new file mode 100644 index 0000000000..800787079c --- /dev/null +++ b/storefront/styles/form.css @@ -0,0 +1,5 @@ +.form-wrapper { + background: #f0f0f0; + border-radius: 15px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); +} diff --git a/storefront/styles/globals.css b/storefront/styles/globals.css index 11fa70739c..bb326b48a7 100644 --- a/storefront/styles/globals.css +++ b/storefront/styles/globals.css @@ -261,3 +261,9 @@ a { display: inline-block; font-size: 16px; } + +.form-select:focus, +.form-select:focus-visible { + border-color: #ced4da; + box-shadow: inherit; +}