diff --git a/framework/commerce/types/cart.ts b/framework/commerce/types/cart.ts index b3d3b1e59..9192436d0 100644 --- a/framework/commerce/types/cart.ts +++ b/framework/commerce/types/cart.ts @@ -1,4 +1,5 @@ -import type { Discount, Measurement, Image } from './common' +import { ShippingMethod } from '@framework/schema'; +import type { Discount, Image, Measurement } from './common'; export type SelectedOption = { // The option's id. @@ -83,6 +84,50 @@ export type Cart = { discounts?: Discount[] } +export type CartCheckout = { + id: string + // ID of the customer to which the cart belongs. + customerId?: string + customer?: { + firstName: string, + lastName: string, + emailAddress: string, + } + shippingAddress?: { + streetLine1: string, + city: string, + province: string, + postalCode: string, + countryCode: string, + phoneNumber: string, + } + // The email assigned to this cart + email?: string + // The date and time when the cart was created. + createdAt: string + // The currency used for this cart + currency: { code: string } + // Specifies if taxes are included in the line items. + taxesIncluded: boolean + lineItems: LineItem[] + // The sum of all the prices of all the items in the cart. + // Duties, taxes, shipping and discounts excluded. + lineItemsSubtotalPrice: number + // Price of the cart before duties, shipping and taxes. + subtotalPrice: number + // The sum of all the prices of all the items in the cart. + // Duties, taxes and discounts included. + totalPrice: number + totalQuantity: number + // Discounts that have been applied on the cart. + discounts?: Discount[] + totalDiscount: number + shippingLine?: { + priceWithTax: number + shippingMethod: ShippingMethod + } +} + /** * Base cart item body used for cart mutations */ diff --git a/framework/commerce/types/common.ts b/framework/commerce/types/common.ts index 06908c464..d3eca1e68 100644 --- a/framework/commerce/types/common.ts +++ b/framework/commerce/types/common.ts @@ -1,6 +1,7 @@ export type Discount = { // The value of the discount, can be an amount or percentage value: number + description?: string } export type Measurement = { diff --git a/framework/vendure/api/operations/get-product.ts b/framework/vendure/api/operations/get-product.ts index 66f798ee1..c16041468 100644 --- a/framework/vendure/api/operations/get-product.ts +++ b/framework/vendure/api/operations/get-product.ts @@ -2,7 +2,7 @@ import { Product } from '@commerce/types/product' import { OperationContext } from '@commerce/api/operations' import { Provider, VendureConfig } from '../' import { GetProductQuery } from '../../schema' -import { getProductQuery, getProductDetailQuery } from '../../utils/queries/get-product-query' +import { getProductQuery } from '../../utils/queries/get-product-query' export default function getProductOperation({ commerce, diff --git a/framework/vendure/schema.d.ts b/framework/vendure/schema.d.ts index 542af51fb..a1a6b7b49 100644 --- a/framework/vendure/schema.d.ts +++ b/framework/vendure/schema.d.ts @@ -306,9 +306,44 @@ export type MutationResetPasswordArgs = { password: Scalars['String'] } +export type ActiveOrderCustomerFragment = Pick & { + customer?: Maybe>; + lines: Array>; +}; + +export type SetCustomerForOrderMutationVariables = Exact<{ + input: CreateCustomerInput; +}>; + +export type SetCustomerForOrderMutation = { __typename?: 'Mutation' } & { + setCustomerForOrder: + | ({ __typename: 'ActiveOrderCustomerFragment' } & Pick) + | ({ __typename: 'AlreadyLoggedInError' } & Pick< + AlreadyLoggedInError, + 'errorCode' | 'message' + >) + | ({ __typename: 'EmailAddressConflictError' } & Pick< + EmailAddressConflictError, + 'errorCode' | 'message' + >) + | ({ __typename: 'NoActiveOrderError' } & Pick< + NoActiveOrderError, + 'errorCode' | 'message' + >) +} + +export type SetOrderShippingAddressMutation = { __typename?: 'Mutation' } & { + setOrderShippingAddress: + | ({ __typename: 'Order' } & Pick) + | ({ __typename: 'NoActiveOrderError' } & Pick< + NoActiveOrderError, + 'errorCode' | 'message' + >) +} + export type Address = Node & { updateCustomerAddress: - | { + | { __typename?: 'Address' id: Scalars['ID'] } @@ -330,8 +365,24 @@ export type Address = Node & { customFields?: Maybe } +export type SetShippingMethodMutationVariables = Exact<{ + id: Scalars['ID']; +}>; + +export type SetShippingMethodMutation = { + setOrderShippingMethod: + | TestOrderFragmentFragment + | Pick + | Pick + | Pick; +}; +export type GetEligibleMethodsQuery = { + eligibleShippingMethods: Array< + Pick + >; +}; export type Asset = Node & { __typename?: 'Asset' @@ -1471,7 +1522,7 @@ export type CustomerListOptions = { export type Customer = Node & { updateCustomer: - | { + | { __typename?: 'Customer' id: Scalars['ID'] } @@ -1482,7 +1533,7 @@ export type Customer = Node & { title?: Maybe firstName: Scalars['String'] lastName: Scalars['String'] - phoneNumber?: Maybe + phoneNumber?: Maybe emailAddress: Scalars['String'] addresses?: Maybe> orders: OrderList @@ -3066,56 +3117,65 @@ export type CartFragment = { __typename?: 'Order' } & Pick< | 'totalWithTax' | 'currencyCode' > & { - customer?: Maybe<{ __typename?: 'Customer' } & Pick> - lines: Array< - { __typename?: 'OrderLine' } & Pick< - OrderLine, - | 'id' - | 'quantity' - | 'linePriceWithTax' - | 'discountedLinePriceWithTax' - | 'unitPriceWithTax' - | 'discountedUnitPriceWithTax' - > & { - featuredAsset?: Maybe< - { __typename?: 'Asset' } & Pick - > - discounts: Array< - { __typename?: 'Discount' } & Pick< - Discount, - 'description' | 'amount' - > - > - productVariant: { __typename?: 'ProductVariant' } & Pick< - ProductVariant, - | 'id' - | 'name' - | 'sku' - | 'price' - | 'priceWithTax' - | 'stockLevel' - | 'productId' - > & { product: { __typename?: 'Product' } & Pick } + shippingAddress?: Maybe<{ __typename?: 'OrderAddress' } & Pick> + discounts: Array< + { __typename?: 'Discount' } & Pick + > + customer?: Maybe<{ __typename?: 'Customer' } & Pick> + shippingLines: Array< + Pick & { + shippingMethod: Pick; } > - } + lines: Array< + { __typename?: 'OrderLine' } & Pick< + OrderLine, + | 'id' + | 'quantity' + | 'linePriceWithTax' + | 'discountedLinePriceWithTax' + | 'unitPriceWithTax' + | 'discountedUnitPriceWithTax' + > & { + featuredAsset?: Maybe< + { __typename?: 'Asset' } & Pick + > + discounts: Array< + { __typename?: 'Discount' } & Pick< + Discount, + 'description' | 'amount' + > + > + productVariant: { __typename?: 'ProductVariant' } & Pick< + ProductVariant, + | 'id' + | 'name' + | 'sku' + | 'price' + | 'priceWithTax' + | 'stockLevel' + | 'productId' + > & { product: { __typename?: 'Product' } & Pick } + } + > +} export type SearchResultFragment = { __typename?: 'SearchResult' } & Pick< SearchResult, 'productId' | 'sku' | 'productName' | 'description' | 'slug' | 'sku' | 'currencyCode' - | 'productAsset' | 'price' | 'priceWithTax' | 'currencyCode' + | 'productAsset' | 'price' | 'priceWithTax' | 'currencyCode' | 'collectionIds' | 'productVariantId' | 'facetValueIds' | "productVariantName" > & { - productAsset?: Maybe< - { __typename?: 'SearchResultAsset' } & Pick< - SearchResultAsset, - 'id' | 'preview' - > + productAsset?: Maybe< + { __typename?: 'SearchResultAsset' } & Pick< + SearchResultAsset, + 'id' | 'preview' > - priceWithTax: - | ({ __typename?: 'PriceRange' } & Pick) - | ({ __typename?: 'SinglePrice' } & Pick) - } + > + priceWithTax: + | ({ __typename?: 'PriceRange' } & Pick) + | ({ __typename?: 'SinglePrice' } & Pick) +} export type AddItemToOrderMutationVariables = Exact<{ variantId: Scalars['ID'] @@ -3124,23 +3184,23 @@ export type AddItemToOrderMutationVariables = Exact<{ export type AddItemToOrderMutation = { __typename?: 'Mutation' } & { addItemToOrder: - | ({ __typename: 'Order' } & CartFragment) - | ({ __typename: 'OrderModificationError' } & Pick< - OrderModificationError, - 'errorCode' | 'message' - >) - | ({ __typename: 'OrderLimitError' } & Pick< - OrderLimitError, - 'errorCode' | 'message' - >) - | ({ __typename: 'NegativeQuantityError' } & Pick< - NegativeQuantityError, - 'errorCode' | 'message' - >) - | ({ __typename: 'InsufficientStockError' } & Pick< - InsufficientStockError, - 'errorCode' | 'message' - >) + | ({ __typename: 'Order' } & CartFragment) + | ({ __typename: 'OrderModificationError' } & Pick< + OrderModificationError, + 'errorCode' | 'message' + >) + | ({ __typename: 'OrderLimitError' } & Pick< + OrderLimitError, + 'errorCode' | 'message' + >) + | ({ __typename: 'NegativeQuantityError' } & Pick< + NegativeQuantityError, + 'errorCode' | 'message' + >) + | ({ __typename: 'InsufficientStockError' } & Pick< + InsufficientStockError, + 'errorCode' | 'message' + >) } export type AdjustOrderLineMutationVariables = Exact<{ @@ -3150,23 +3210,53 @@ export type AdjustOrderLineMutationVariables = Exact<{ export type AdjustOrderLineMutation = { __typename?: 'Mutation' } & { adjustOrderLine: - | ({ __typename: 'Order' } & CartFragment) - | ({ __typename: 'OrderModificationError' } & Pick< - OrderModificationError, - 'errorCode' | 'message' - >) - | ({ __typename: 'OrderLimitError' } & Pick< - OrderLimitError, - 'errorCode' | 'message' - >) - | ({ __typename: 'NegativeQuantityError' } & Pick< - NegativeQuantityError, - 'errorCode' | 'message' - >) - | ({ __typename: 'InsufficientStockError' } & Pick< - InsufficientStockError, - 'errorCode' | 'message' - >) + | ({ __typename: 'Order' } & CartFragment) + | ({ __typename: 'OrderModificationError' } & Pick< + OrderModificationError, + 'errorCode' | 'message' + >) + | ({ __typename: 'OrderLimitError' } & Pick< + OrderLimitError, + 'errorCode' | 'message' + >) + | ({ __typename: 'NegativeQuantityError' } & Pick< + NegativeQuantityError, + 'errorCode' | 'message' + >) + | ({ __typename: 'InsufficientStockError' } & Pick< + InsufficientStockError, + 'errorCode' | 'message' + >) +} + + +export type ApplyCouponCodeMutationVariables = Exact<{ + couponCode: Scalars['String']; +}>; + +export type ApplyCouponCodeMutation = { + applyCouponCode: + | TestOrderFragmentFragment + | Pick + | Pick + | Pick; +}; + +export type ApplyCouponCodeMutation = { __typename?: 'Mutation' } & { + applyCouponCode: + | ({ __typename: 'Order' } & CartFragment) + | ({ __typename: 'CouponCodeExpiredError' } & Pick< + CouponCodeExpiredError, + 'errorCode' | 'message' + >) + | ({ __typename: 'CouponCodeInvalidError' } & Pick< + CouponCodeInvalidError, + 'errorCode' | 'message' + >) + | ({ __typename: 'CouponCodeLimitError' } & Pick< + CouponCodeLimitError, + 'errorCode' | 'message' + >) } export type LoginMutationVariables = Exact<{ @@ -3176,49 +3266,49 @@ export type LoginMutationVariables = Exact<{ export type LoginMutation = { __typename?: 'Mutation' } & { login: - | ({ __typename: 'CurrentUser' } & Pick) - | ({ __typename: 'InvalidCredentialsError' } & Pick< - InvalidCredentialsError, - 'errorCode' | 'message' - >) - | ({ __typename: 'NotVerifiedError' } & Pick< - NotVerifiedError, - 'errorCode' | 'message' - >) - | ({ __typename: 'NativeAuthStrategyError' } & Pick< - NativeAuthStrategyError, - 'errorCode' | 'message' - >) + | ({ __typename: 'CurrentUser' } & Pick) + | ({ __typename: 'InvalidCredentialsError' } & Pick< + InvalidCredentialsError, + 'errorCode' | 'message' + >) + | ({ __typename: 'NotVerifiedError' } & Pick< + NotVerifiedError, + 'errorCode' | 'message' + >) + | ({ __typename: 'NativeAuthStrategyError' } & Pick< + NativeAuthStrategyError, + 'errorCode' | 'message' + >) } export type ResetPasswordMutation = { __typename?: 'Mutation' } & { resetPassword: - | ({ __typename: 'CurrentUser' } & Pick) - | ({ __typename: 'PasswordResetTokenInvalidError' } & Pick< - PasswordResetTokenInvalidError, - 'errorCode' | 'message' - >) - | ({ __typename: 'PasswordResetTokenExpiredError' } & Pick< - PasswordResetTokenExpiredError, - 'errorCode' | 'message' - >) - | ({ __typename: 'NativeAuthStrategyError' } & Pick< - NativeAuthStrategyError, - 'errorCode' | 'message' - >) + | ({ __typename: 'CurrentUser' } & Pick) + | ({ __typename: 'PasswordResetTokenInvalidError' } & Pick< + PasswordResetTokenInvalidError, + 'errorCode' | 'message' + >) + | ({ __typename: 'PasswordResetTokenExpiredError' } & Pick< + PasswordResetTokenExpiredError, + 'errorCode' | 'message' + >) + | ({ __typename: 'NativeAuthStrategyError' } & Pick< + NativeAuthStrategyError, + 'errorCode' | 'message' + >) } export type SignupMutation = { __typename?: 'Mutation' } & { registerCustomerAccount: - | ({ __typename: 'Success' } & Pick) - | ({ __typename: 'MissingPasswordError' } & Pick< - MissingPasswordError, - 'errorCode' | 'message' - >) - | ({ __typename: 'NativeAuthStrategyError' } & Pick< - NativeAuthStrategyError, - 'errorCode' | 'message' - >) + | ({ __typename: 'Success' } & Pick) + | ({ __typename: 'MissingPasswordError' } & Pick< + MissingPasswordError, + 'errorCode' | 'message' + >) + | ({ __typename: 'NativeAuthStrategyError' } & Pick< + NativeAuthStrategyError, + 'errorCode' | 'message' + >) } export type VerifyCustomerAccountVariables = Exact<{ @@ -3228,27 +3318,27 @@ export type VerifyCustomerAccountVariables = Exact<{ export type VerifyCustomerAccountMutation = { __typename?: 'Mutation' } & { verifyCustomerAccount: - | ({ __typename: 'CurrentUser' } & Pick) - | ({ __typename: 'VerificationTokenInvalidError' } & Pick< - VerificationTokenInvalidError, - 'errorCode' | 'message' - >) - | ({ __typename: 'VerificationTokenExpiredError' } & Pick< - VerificationTokenExpiredError, - 'errorCode' | 'message' - >) - | ({ __typename: 'MissingPasswordError' } & Pick< - MissingPasswordError, - 'errorCode' | 'message' - >) - | ({ __typename: 'PasswordAlreadySetError' } & Pick< - PasswordAlreadySetError, - 'errorCode' | 'message' - >) - | ({ __typename: 'NativeAuthStrategyError' } & Pick< - NativeAuthStrategyError, - 'errorCode' | 'message' - >) + | ({ __typename: 'CurrentUser' } & Pick) + | ({ __typename: 'VerificationTokenInvalidError' } & Pick< + VerificationTokenInvalidError, + 'errorCode' | 'message' + >) + | ({ __typename: 'VerificationTokenExpiredError' } & Pick< + VerificationTokenExpiredError, + 'errorCode' | 'message' + >) + | ({ __typename: 'MissingPasswordError' } & Pick< + MissingPasswordError, + 'errorCode' | 'message' + >) + | ({ __typename: 'PasswordAlreadySetError' } & Pick< + PasswordAlreadySetError, + 'errorCode' | 'message' + >) + | ({ __typename: 'NativeAuthStrategyError' } & Pick< + NativeAuthStrategyError, + 'errorCode' | 'message' + >) } export type LogoutMutationVariables = Exact<{ [key: string]: never }> @@ -3263,11 +3353,11 @@ export type RemoveOrderLineMutationVariables = Exact<{ export type RemoveOrderLineMutation = { __typename?: 'Mutation' } & { removeOrderLine: - | ({ __typename: 'Order' } & CartFragment) - | ({ __typename: 'OrderModificationError' } & Pick< - OrderModificationError, - 'errorCode' | 'message' - >) + | ({ __typename: 'Order' } & CartFragment) + | ({ __typename: 'OrderModificationError' } & Pick< + OrderModificationError, + 'errorCode' | 'message' + >) } export type SignupMutationVariables = Exact<{ @@ -3277,15 +3367,15 @@ export type SignupMutationVariables = Exact<{ export type RequestPasswordReset = { __typename?: 'Mutation' } & { requestPasswordReset: - | ({ __typename: 'Success' } & Pick) - | ({ __typename: 'MissingPasswordError' } & Pick< - MissingPasswordError, - 'errorCode' | 'message' - >) - | ({ __typename: 'NativeAuthStrategyError' } & Pick< - NativeAuthStrategyError, - 'errorCode' | 'message' - >) + | ({ __typename: 'Success' } & Pick) + | ({ __typename: 'MissingPasswordError' } & Pick< + MissingPasswordError, + 'errorCode' | 'message' + >) + | ({ __typename: 'NativeAuthStrategyError' } & Pick< + NativeAuthStrategyError, + 'errorCode' | 'message' + >) } @@ -3297,7 +3387,7 @@ export type ActiveCustomerQuery = { __typename?: 'Query' } & { { __typename?: 'Customer' } & Pick< Customer, Favorite, - 'id' | 'firstName' | 'lastName' | 'emailAddress' | 'addresses' | 'phoneNumber'| 'orders' + 'id' | 'firstName' | 'lastName' | 'emailAddress' | 'addresses' | 'phoneNumber' | 'orders' > > } @@ -3316,7 +3406,7 @@ export type FavoriteList = PaginatedList & { totalItems: Int! } -type Favorite = Node & { +type Favorite = Node & { id: ID! createdAt: DateTime! updatedAt: DateTime! @@ -3324,9 +3414,28 @@ type Favorite = Node & { customer: Customer! } +export type GetAvailableCountriesQueryVariables = Exact<{ [key: string]: never; }>; -type FavouriteOption = Customer & { +// export type GetAvailableCountriesQuery = { countries: ( +// { __typename?: 'CountryList' } +// & { items: Array<( +// { __typename?: 'Country' } +// & Pick +// )> } +// ) }; + +export type GetAvailableCountriesQuery = { + availableCountries: + { __typename?: 'CountryList' } + & Array<( + { __typename?: 'Country' } + & Pick + )> +}; + + +type FavouriteOption = Customer & { favorites(options: FavoriteListOptions): FavoriteList! } @@ -3357,13 +3466,13 @@ export type GetAllFacetsQuery = { __typename?: 'Query' } & { { __typename?: 'Facet' } & Pick< Facet, 'id' | 'name' | 'code' | 'values' - > + > & { - parent?: Maybe<{ __typename?: 'Facet' } & Pick> - children?: Maybe< - Array<{ __typename?: 'Facet' } & Pick> - > - } + parent?: Maybe<{ __typename?: 'Facet' } & Pick> + children?: Maybe< + Array<{ __typename?: 'Facet' } & Pick> + > + } >, 'totalItems' } @@ -3376,11 +3485,11 @@ export type GetAllCollectionsQuery = { __typename?: 'Query' } & { Collection, 'id' | 'name' | 'slug' > & { - parent?: Maybe<{ __typename?: 'Collection' } & Pick> - children?: Maybe< - Array<{ __typename?: 'Collection' } & Pick> - > - } + parent?: Maybe<{ __typename?: 'Collection' } & Pick> + children?: Maybe< + Array<{ __typename?: 'Collection' } & Pick> + > + } >, 'totalItems' } @@ -3401,15 +3510,15 @@ export type GetCollectionsQuery = { __typename?: 'Query' } & { Collection, 'id' | 'name' | 'description' | 'slug' > & { - productVariants: { __typename?: 'ProductVariantList' } & Pick< - ProductVariantList, - 'totalItems' - > - parent?: Maybe<{ __typename?: 'Collection' } & Pick> - children?: Maybe< - Array<{ __typename?: 'Collection' } & Pick> - > - } + productVariants: { __typename?: 'ProductVariantList' } & Pick< + ProductVariantList, + 'totalItems' + > + parent?: Maybe<{ __typename?: 'Collection' } & Pick> + children?: Maybe< + Array<{ __typename?: 'Collection' } & Pick> + > + } > } } @@ -3424,60 +3533,60 @@ export type GetProductQuery = { __typename?: 'Query' } & { Product, 'id' | 'name' | 'slug' | 'description' > & { - assets: Array< - { __typename?: 'Asset' } & Pick - > - variants: Array< - { __typename?: 'ProductVariant' } & Pick< - ProductVariant, - 'id' | 'priceWithTax' | 'currencyCode' | 'price' | "name" - > & { - options: Array< - { __typename?: 'ProductOption' } & Pick< - ProductOption, - 'id' | 'name' | 'code' | 'groupId' - > & { - group: { __typename?: 'ProductOptionGroup' } & Pick< - ProductOptionGroup, - 'id' - > & { - options: Array< - { __typename?: 'ProductOption' } & Pick< - ProductOption, - 'name' - > - > - } - } - > - } - > - optionGroups: Array< - { __typename?: 'ProductOptionGroup' } & Pick< - ProductOptionGroup, - 'id' | 'code' | 'name' - > & { - options: Array< - { __typename?: 'ProductOption' } & Pick< - ProductOption, - 'id' | 'name' + assets: Array< + { __typename?: 'Asset' } & Pick + > + variants: Array< + { __typename?: 'ProductVariant' } & Pick< + ProductVariant, + 'id' | 'priceWithTax' | 'currencyCode' | 'price' | "name" + > & { + options: Array< + { __typename?: 'ProductOption' } & Pick< + ProductOption, + 'id' | 'name' | 'code' | 'groupId' + > & { + group: { __typename?: 'ProductOptionGroup' } & Pick< + ProductOptionGroup, + 'id' + > & { + options: Array< + { __typename?: 'ProductOption' } & Pick< + ProductOption, + 'name' + > > - > + } } - > - facetValues: Array< - { __typename?: 'FacetValue' } & Pick< - FacetValue, - 'id' > - > - collections: Array< - { __typename?: 'Collection' } & Pick< - Collection, - 'id'|"name" + } + > + optionGroups: Array< + { __typename?: 'ProductOptionGroup' } & Pick< + ProductOptionGroup, + 'id' | 'code' | 'name' + > & { + options: Array< + { __typename?: 'ProductOption' } & Pick< + ProductOption, + 'id' | 'name' + > > + } + > + facetValues: Array< + { __typename?: 'FacetValue' } & Pick< + FacetValue, + 'id' > - } + > + collections: Array< + { __typename?: 'Collection' } & Pick< + Collection, + 'id' | "name" + > + > + } > } diff --git a/framework/vendure/utils/mutations/apply-coupon-code-mutation.ts b/framework/vendure/utils/mutations/apply-coupon-code-mutation.ts new file mode 100644 index 000000000..c5b1a9a90 --- /dev/null +++ b/framework/vendure/utils/mutations/apply-coupon-code-mutation.ts @@ -0,0 +1,21 @@ +export const applyCouponCodeMutation = /* GraphQL */ ` +mutation applyCouponCode($couponCode: String!) { + applyCouponCode(couponCode: $couponCode) { + __typename + ... on Order { + id + createdAt + updatedAt + discounts { + type + amount + amountWithTax + } + } + ... on ErrorResult { + errorCode + message + } + } +} +` diff --git a/framework/vendure/utils/mutations/set-customer-order-mutation.ts b/framework/vendure/utils/mutations/set-customer-order-mutation.ts new file mode 100644 index 000000000..d4face1e9 --- /dev/null +++ b/framework/vendure/utils/mutations/set-customer-order-mutation.ts @@ -0,0 +1,24 @@ +export const setCustomerForOrderMutation = /* GraphQL */ ` +mutation setCustomerForOrder($input: CreateCustomerInput!) { + setCustomerForOrder(input: $input) { + __typename + ... on Order { + id + createdAt + updatedAt + code + customer { + id + firstName + lastName + emailAddress + phoneNumber + } + } + ... on ErrorResult { + errorCode + message + } + } +} +` diff --git a/framework/vendure/utils/mutations/set-order-shipping-address-mutation.ts b/framework/vendure/utils/mutations/set-order-shipping-address-mutation.ts new file mode 100644 index 000000000..ee36fb68d --- /dev/null +++ b/framework/vendure/utils/mutations/set-order-shipping-address-mutation.ts @@ -0,0 +1,25 @@ +export const setOrderShippingAddressMutation = /* GraphQL */ ` +mutation setOrderShippingAddress($input: CreateAddressInput!) { + setOrderShippingAddress(input: $input) { + __typename + ... on Order { + id + createdAt + updatedAt + code + shippingAddress { + streetLine1 + city + province + postalCode + countryCode + phoneNumber + } + } + ... on ErrorResult { + errorCode + message + } + } +} +` diff --git a/framework/vendure/utils/mutations/set-order-shipping-method-mutation.ts b/framework/vendure/utils/mutations/set-order-shipping-method-mutation.ts new file mode 100644 index 000000000..e43331908 --- /dev/null +++ b/framework/vendure/utils/mutations/set-order-shipping-method-mutation.ts @@ -0,0 +1,35 @@ +export const setShippingMethodMutation = /* GraphQL */ ` +mutation SetShippingMethod($id: ID!) { + setOrderShippingMethod(shippingMethodId: $id) { + ...Cart + ...ErrorResult + __typename + } +} + +fragment Cart on Order { + id + code + state + active + shippingLines { + priceWithTax + shippingMethod { + id + code + name + description + __typename + } + __typename + } + + __typename +} + +fragment ErrorResult on ErrorResult { + errorCode + message + __typename +} +` diff --git a/framework/vendure/utils/normalize.ts b/framework/vendure/utils/normalize.ts index c095ec030..b9c55680e 100644 --- a/framework/vendure/utils/normalize.ts +++ b/framework/vendure/utils/normalize.ts @@ -1,6 +1,6 @@ -import { Cart } from '@commerce/types/cart' -import { ProductCard, Product } from '@commerce/types/product' -import { CartFragment, SearchResultFragment,Favorite, BlogList } from '../schema' +import { Cart, CartCheckout } from '@commerce/types/cart' +import { Product, ProductCard } from '@commerce/types/product' +import { CartFragment, Favorite, SearchResultFragment, ShippingMethod, BlogList } from '../schema' export function normalizeSearchResult(item: SearchResultFragment): ProductCard { return { @@ -11,10 +11,10 @@ export function normalizeSearchResult(item: SearchResultFragment): ProductCard { price: (item.priceWithTax as any).min / 100, currencyCode: item.currencyCode, productVariantId: item.productVariantId, - productVariantName:item.productVariantName, + productVariantName: item.productVariantName, facetValueIds: item.facetValueIds, collectionIds: item.collectionIds, - + // TODO: // oldPrice: item.price // discount @@ -70,6 +70,63 @@ export function normalizeCart(order: CartFragment): Cart { } } +export function normalizeCartForCheckout(order: CartFragment): CartCheckout { + return { + id: order.id.toString(), + createdAt: order.createdAt, + taxesIncluded: true, + totalQuantity: order.totalQuantity, + lineItemsSubtotalPrice: order.subTotalWithTax / 100, + currency: { code: order.currencyCode }, + subtotalPrice: order.subTotalWithTax / 100, + totalPrice: order.totalWithTax / 100, + customerId: order.customer?.id, + customer: { + firstName: order.customer?.firstName || '', + lastName: order.customer?.lastName || '', + emailAddress: order.customer?.emailAddress || '', + }, + shippingAddress: { + streetLine1: order.shippingAddress?.streetLine1 || '', + city: order.shippingAddress?.city || '', + province: order.shippingAddress?.province || '', + postalCode: order.shippingAddress?.postalCode || '', + countryCode: order.shippingAddress?.countryCode || '', + phoneNumber: order.shippingAddress?.phoneNumber || '', + }, + shippingLine: order.shippingLines[0] ? { + priceWithTax: order.shippingLines[0]?.priceWithTax / 100, + shippingMethod: order.shippingLines[0]?.shippingMethod as ShippingMethod + }: undefined, + totalDiscount: order.discounts?.reduce((total, item) => total + item.amountWithTax, 0) / 100 || 0, + discounts: order.discounts.map(item => { + return { value: item.amountWithTax, description: item.description } + }), + lineItems: order.lines?.map((l) => ({ + id: l.id, + name: l.productVariant.name, + quantity: l.quantity, + slug: l.productVariant.product.slug, + variantId: l.productVariant.id, + productId: l.productVariant.productId, + images: [{ url: l.featuredAsset?.preview + '?preset=thumb' || '' }], + discounts: l.discounts.map((d) => ({ value: d.amount / 100 })), + path: '', + variant: { + id: l.productVariant.id, + name: l.productVariant.name, + sku: l.productVariant.sku, + price: l.discountedUnitPriceWithTax / 100, + listPrice: l.unitPriceWithTax / 100, + image: { + url: l.featuredAsset?.preview + '?preset=thumb' || '', + }, + requiresShipping: true, + }, + })), + } +} + export function normalizeProductCard(product: Product): ProductCard { return { id: product.id, @@ -79,7 +136,7 @@ export function normalizeProductCard(product: Product): ProductCard { price: product.price, currencyCode: product.currencyCode, productVariantId: product.variants?.[0].id.toString(), - productVariantName:product.variants?.[0].name, + productVariantName: product.variants?.[0].name, facetValueIds: product.facetValueIds, collectionIds: product.collectionIds, } diff --git a/framework/vendure/utils/queries/active-order-for-checkout-query.ts b/framework/vendure/utils/queries/active-order-for-checkout-query.ts new file mode 100644 index 000000000..7acedc492 --- /dev/null +++ b/framework/vendure/utils/queries/active-order-for-checkout-query.ts @@ -0,0 +1,116 @@ +export const getActiveOrderForCheckoutQuery = /* GraphQL */ ` +query getActiveOrderForCheckout { + activeOrder { + ...Cart + shippingAddress { + ...OrderAddress + __typename + } + __typename + } +} + +fragment Cart on Order { + id + code + state + active + customer { + id + firstName + lastName + emailAddress + } + lines { + id + featuredAsset { + ...Asset + __typename + } + unitPrice + unitPriceWithTax + quantity + linePriceWithTax + discountedLinePriceWithTax + unitPriceWithTax + discountedUnitPriceWithTax + productVariant { + id + name + price + priceWithTax + stockLevel + productId + product { + slug + } + __typename + } + discounts { + amount + amountWithTax + description + adjustmentSource + type + __typename + } + __typename + } + totalQuantity + subTotal + subTotalWithTax + total + totalWithTax + shipping + shippingWithTax + currencyCode + shippingLines { + priceWithTax + shippingMethod { + id + code + name + description + __typename + } + __typename + } + discounts { + amount + amountWithTax + description + adjustmentSource + type + __typename + } + __typename +} + +fragment Asset on Asset { + id + width + height + name + preview + focalPoint { + x + y + __typename + } + __typename +} + +fragment OrderAddress on OrderAddress { + fullName + company + streetLine1 + streetLine2 + city + province + postalCode + country + phoneNumber + __typename +} +` + diff --git a/framework/vendure/utils/queries/available-countries-query.ts b/framework/vendure/utils/queries/available-countries-query.ts new file mode 100644 index 000000000..f1da8c408 --- /dev/null +++ b/framework/vendure/utils/queries/available-countries-query.ts @@ -0,0 +1,16 @@ +export const availableCountriesQuery = /* GraphQL */ ` +query availableCountriesQuery { + availableCountries { + ...Country + __typename + } +} + +fragment Country on Country { + id + code + name + enabled + __typename +} +` diff --git a/framework/vendure/utils/queries/eligible-shipping-methods-query.ts b/framework/vendure/utils/queries/eligible-shipping-methods-query.ts new file mode 100644 index 000000000..a85b6f102 --- /dev/null +++ b/framework/vendure/utils/queries/eligible-shipping-methods-query.ts @@ -0,0 +1,13 @@ +export const getEligibleShippingMethods = /* GraphQL */ ` +query getEligibleShippingMethods { + eligibleShippingMethods { + id + name + description + price + priceWithTax + metadata + __typename + } +} +` diff --git a/package.json b/package.json index 8474be667..6db6e1de1 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,9 @@ "name": "nextjs-commerce", "version": "1.0.0", "scripts": { - "dev": "NODE_OPTIONS='--inspect' PORT=3005 next dev", + "dev": "set NODE_OPTIONS='--inspect' && set PORT=3005 && next dev", "dev-windows": "set NODE_OPTIONS='--inspect' && set PORT=3005 && next dev", + "dev-macos": "set NODE_OPTIONS='--inspect' && set PORT=3005 && next dev", "build": "next build", "start": "PORT=3005 next start", "analyze": "BUNDLE_ANALYZE=both yarn build", diff --git a/pages/checkout.tsx b/pages/checkout.tsx index ae502f0e9..860f04bc1 100644 --- a/pages/checkout.tsx +++ b/pages/checkout.tsx @@ -4,7 +4,8 @@ import { CheckoutPage } from 'src/components/modules/checkout'; export default function Checkout() { return ( <> - + + ) } diff --git a/pages/index.tsx b/pages/index.tsx index 62aeca0ab..2bbe38cc9 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -31,8 +31,8 @@ export default function Home({ featuredAndDiscountFacetsValue, veggie, - + {spiceProducts.length>0 && } diff --git a/src/components/common/ButtonCommon/ButtonCommon.module.scss b/src/components/common/ButtonCommon/ButtonCommon.module.scss index 318180ede..69c07c670 100644 --- a/src/components/common/ButtonCommon/ButtonCommon.module.scss +++ b/src/components/common/ButtonCommon/ButtonCommon.module.scss @@ -1,7 +1,7 @@ @import "../../../styles/utilities"; .buttonCommon { - @apply shape-common; + @apply shape-common h-full; &:hover { .inner { @apply shadow-md; diff --git a/src/components/common/CardItemCheckout/CardItemCheckout.tsx b/src/components/common/CardItemCheckout/CardItemCheckout.tsx index e67057aa6..40fbf6b37 100644 --- a/src/components/common/CardItemCheckout/CardItemCheckout.tsx +++ b/src/components/common/CardItemCheckout/CardItemCheckout.tsx @@ -1,24 +1,29 @@ +import { LineItem } from '@commerce/types/cart' import React from 'react' +import { ImgWithLink } from '..' import s from "./CardItemCheckout.module.scss" -import { ProductProps } from 'src/utils/types.utils' -export interface CardItemCheckoutProps extends ProductProps { - quantity:number +export interface CardItemCheckoutProps extends LineItem { + currency: { code: string } } -const CardItemCheckout = ({imageSrc,name,price,weight,quantity,category}: CardItemCheckoutProps) => { +const CardItemCheckout = ({ + quantity, + variant, + name, + currency }: CardItemCheckoutProps) => { return (
- image +
- {`${name} (${weight})`} + {name} {variant?.weight ? `(${variant.weight})` : ''}
- Quantity: + Quantity:
- {`${quantity} x ${price}`} + {`${quantity} x ${variant?.price} ${currency?.code}`}
diff --git a/src/components/common/CartDrawer/components/ProductCartItem/ProductCartItem.tsx b/src/components/common/CartDrawer/components/ProductCartItem/ProductCartItem.tsx index ef219d31d..57a6e2b20 100644 --- a/src/components/common/CartDrawer/components/ProductCartItem/ProductCartItem.tsx +++ b/src/components/common/CartDrawer/components/ProductCartItem/ProductCartItem.tsx @@ -74,7 +74,8 @@ const ProductCartItem = ({ {discounts.length > 0 && (
{/* {oldPrice} */} - {discounts[0]} + {/* TODO: edit the value */} + {discounts[0]?.value}
)}
{variant?.price} {currency?.code}
diff --git a/src/components/common/CheckoutCollapse/CheckoutCollapse.module.scss b/src/components/common/CheckoutCollapse/CheckoutCollapse.module.scss index 9e8e1f8e5..490797c75 100644 --- a/src/components/common/CheckoutCollapse/CheckoutCollapse.module.scss +++ b/src/components/common/CheckoutCollapse/CheckoutCollapse.module.scss @@ -1,51 +1,52 @@ -.warpper{ +.warpper { padding: 2.4rem 0; @apply border-b border-solid border-line; - .note{ + .note { + @apply cursor-pointer; font-size: 1.2rem; line-height: 2rem; letter-spacing: 0.01em; color: var(--text-label); padding: 0 5.6rem; } - .header{ - @apply flex justify-between; - .left{ + .header { + @apply flex justify-between cursor-pointer; + .left { @apply flex items-center; - .number{ + .number { width: 3.2rem; height: 3.2rem; border-radius: 100%; border: 1px solid var(--text-active); color: var(--text-active); @apply flex justify-center items-center font-bold; - &.visible{ + &.visible { background-color: var(--text-active); border: none; color: var(--white); } - &.done{ + &.done { @apply border-2 border-solid border-primary; } } - .title{ + .title { padding-left: 2.4rem; - @apply font-bold select-none cursor-pointer; + @apply font-bold select-none; color: var(--text-active); } } - .edit{ + .edit { @apply font-bold cursor-pointer; text-decoration-line: underline; margin-right: 5.6rem; } } - .body{ + .body { height: 0; @apply overflow-hidden; - &.show{ + &.show { margin-top: 3.2rem; height: initial; } } -} \ No newline at end of file +} diff --git a/src/components/common/CheckoutCollapse/CheckoutCollapse.tsx b/src/components/common/CheckoutCollapse/CheckoutCollapse.tsx index 077b94a79..38314c8cc 100644 --- a/src/components/common/CheckoutCollapse/CheckoutCollapse.tsx +++ b/src/components/common/CheckoutCollapse/CheckoutCollapse.tsx @@ -1,8 +1,6 @@ import classNames from 'classnames' -import { divide } from 'lodash' import React from 'react' import { IconDoneCheckout } from 'src/components/icons' -import { CheckOutForm } from 'src/utils/types.utils' import s from './CheckoutCollapse.module.scss' interface CheckoutCollapseProps { visible: boolean @@ -10,10 +8,11 @@ interface CheckoutCollapseProps { children: React.ReactNode title: string isEdit: boolean - onClose?: (id:number) => void - onOpen?: (id:number) => void - onEditClick?:(id:number) => void - note?:string + onClose: (id: number) => void + onOpen?: (id: number) => void + onEditClick?: (id: number) => void + note?: string + disableEdit?: boolean } const CheckoutCollapse = ({ @@ -24,33 +23,34 @@ const CheckoutCollapse = ({ visible, note, onOpen, - onClose, - onEditClick + onClose, + onEditClick, + disableEdit, }: CheckoutCollapseProps) => { - const handleTitleClick = () => { - if(visible){ - onClose && onClose(id) - }else{ - onOpen && onOpen(id) - } - } + const handleToggle = () => { + if (visible) { + isEdit && onClose(id) + } else if (!disableEdit) { + isEdit && onEditClick && onEditClick(id) + } + } const handleEdit = () => { - onEditClick && onEditClick(id) - } + onEditClick && onEditClick(id) + } return (
-
+
-
- {isEdit?:id} +
+ {isEdit ? : id}
-
+
{title}
- {isEdit &&
{'Edit'}
} + {!disableEdit && isEdit &&
{'Edit'}
}
- {(!visible && isEdit) && (
{note}
) } + {(!visible && isEdit) && (
{note}
)}
{children}
) diff --git a/src/components/common/Layout/LayoutContent/LayoutContent.tsx b/src/components/common/Layout/LayoutContent/LayoutContent.tsx index 7bfddafe7..87a224366 100644 --- a/src/components/common/Layout/LayoutContent/LayoutContent.tsx +++ b/src/components/common/Layout/LayoutContent/LayoutContent.tsx @@ -1,7 +1,6 @@ import { useRouter } from 'next/router' import { FC } from 'react' import { useMessage } from 'src/components/contexts' -import { useModalCommon } from 'src/components/hooks' import { FILTER_PAGE, ROUTE } from 'src/utils/constanst.utils' import { CartDrawer, Footer, MessageCommon, ScrollToTop } from '../..' import Header from '../../Header/Header' diff --git a/src/components/common/LayoutCheckout/LayoutCheckout.module.scss b/src/components/common/LayoutCheckout/LayoutCheckout.module.scss index 8a32d20a7..82e8d8a0e 100644 --- a/src/components/common/LayoutCheckout/LayoutCheckout.module.scss +++ b/src/components/common/LayoutCheckout/LayoutCheckout.module.scss @@ -4,8 +4,11 @@ flex-direction: column; min-height: 100vh; > main { + display: flex; flex: 1; position: relative; + max-width: min(100%, 1536px); + margin: auto; } .footer { @apply spacing-horizontal; diff --git a/src/components/common/LayoutCheckout/LayoutCheckout.tsx b/src/components/common/LayoutCheckout/LayoutCheckout.tsx index 06c632418..68490c87a 100644 --- a/src/components/common/LayoutCheckout/LayoutCheckout.tsx +++ b/src/components/common/LayoutCheckout/LayoutCheckout.tsx @@ -1,3 +1,4 @@ +import { MessageProvider } from 'src/components/contexts' import s from './LayoutCheckout.module.scss' interface Props { @@ -6,14 +7,18 @@ interface Props { const LayoutCheckout = ({ children }: Props) => { return ( -
-
{children}
-
-
- © 2021 Online Grocery + <> + +
+
{children}
+
+
+ © 2021 Online Grocery +
+
-
-
+ + ) } diff --git a/src/components/common/MessageCommon/MessageCommon.tsx b/src/components/common/MessageCommon/MessageCommon.tsx index f15f8f443..16040d963 100644 --- a/src/components/common/MessageCommon/MessageCommon.tsx +++ b/src/components/common/MessageCommon/MessageCommon.tsx @@ -1,4 +1,4 @@ -import React, { memo, useEffect } from 'react' +import React, { memo } from 'react' import s from './MessageCommon.module.scss' import MessageItem, { MessageItemProps } from './MessageItem/MessageItem' diff --git a/src/components/common/ModalAuthenticate/ModalAuthenticate.tsx b/src/components/common/ModalAuthenticate/ModalAuthenticate.tsx index 145155268..6b773cf98 100644 --- a/src/components/common/ModalAuthenticate/ModalAuthenticate.tsx +++ b/src/components/common/ModalAuthenticate/ModalAuthenticate.tsx @@ -12,9 +12,11 @@ interface Props { visible: boolean closeModal: () => void mode?: '' | 'register' + initialEmail?: string + disableRedirect ?: boolean } -const ModalAuthenticate = ({ visible, mode, closeModal }: Props) => { +const ModalAuthenticate = ({ visible, mode, closeModal, initialEmail, disableRedirect }: Props) => { const [isLogin, setIsLogin] = useState(true) const { customer } = useActiveCustomer() const router = useRouter() @@ -30,9 +32,11 @@ const ModalAuthenticate = ({ visible, mode, closeModal }: Props) => { useEffect(() => { if (visible && customer) { closeModal() - router.push(ROUTE.ACCOUNT) + if (!disableRedirect) { + router.push(ROUTE.ACCOUNT) + } } - }, [customer, visible, closeModal, router]) + }, [customer, visible, closeModal, router, disableRedirect]) const onSwitch = () => { setIsLogin(!isLogin) @@ -51,7 +55,7 @@ const ModalAuthenticate = ({ visible, mode, closeModal }: Props) => { [s.register]: !isLogin, })} > - +
diff --git a/src/components/common/ModalAuthenticate/components/FormLogin/FormLogin.tsx b/src/components/common/ModalAuthenticate/components/FormLogin/FormLogin.tsx index 279137a84..8264fc70a 100644 --- a/src/components/common/ModalAuthenticate/components/FormLogin/FormLogin.tsx +++ b/src/components/common/ModalAuthenticate/components/FormLogin/FormLogin.tsx @@ -15,16 +15,18 @@ import styles from './FormLogin.module.scss' interface Props { isHide: boolean onSwitch: () => void + initialEmail?: string + } -const DisplayingErrorMessagesSchema = Yup.object().shape({ - email: Yup.string().email('Your email was wrong').required('Required'), +const displayingErrorMessagesSchema = Yup.object().shape({ + email: Yup.string().email(LANGUAGE.MESSAGE.INVALID_EMAIL).required(LANGUAGE.MESSAGE.REQUIRED), password: Yup.string() .max(30, 'Password is too long') - .required('Required'), + .required(LANGUAGE.MESSAGE.REQUIRED), }) -const FormLogin = ({ onSwitch, isHide }: Props) => { +const FormLogin = ({ onSwitch, isHide, initialEmail = ''}: Props) => { const emailRef = useRef(null) const { loading, login } = useLogin() const { showMessageSuccess, showMessageError } = useMessage() @@ -54,9 +56,9 @@ const FormLogin = ({ onSwitch, isHide }: Props) => { diff --git a/src/components/common/ModalConfirm/ModalConfirm.tsx b/src/components/common/ModalConfirm/ModalConfirm.tsx index a41dd41d7..ac6ccc00c 100644 --- a/src/components/common/ModalConfirm/ModalConfirm.tsx +++ b/src/components/common/ModalConfirm/ModalConfirm.tsx @@ -5,7 +5,7 @@ import s from './ModalConfirm.module.scss' interface ModalConfirmProps extends ModalCommonProps { okText?: String cancelText?: String - loading?:boolean + loading?: boolean onOk?: () => void onCancel?: () => void } @@ -18,16 +18,17 @@ const ModalConfirm = ({ children, title = 'Confirm', loading, + onClose, ...props }: ModalConfirmProps) => { return ( - + {children}
- {cancelText} + {cancelText}
- {okText} + {okText}
) diff --git a/src/components/common/SelectFieldInForm/SelectFieldInForm.module.scss b/src/components/common/SelectFieldInForm/SelectFieldInForm.module.scss new file mode 100644 index 000000000..e50b5e0f5 --- /dev/null +++ b/src/components/common/SelectFieldInForm/SelectFieldInForm.module.scss @@ -0,0 +1,19 @@ +@import "../../../styles/form"; + +.inputWrap { + @extend .formInputWrap; + .inputInner { + select { + @apply w-full; + background-color: var(--white); + padding: 1.6rem 1.6rem; + border: 1px solid var(--border-line); + border-radius: 0.8rem; + &:focus { + outline: none; + border: 1px solid var(--primary); + @apply shadow-md; + } + } + } +} diff --git a/src/components/common/SelectFieldInForm/SelectFieldInForm.tsx b/src/components/common/SelectFieldInForm/SelectFieldInForm.tsx new file mode 100644 index 000000000..3a650fa1c --- /dev/null +++ b/src/components/common/SelectFieldInForm/SelectFieldInForm.tsx @@ -0,0 +1,106 @@ +import classNames from "classnames" +import { Field } from "formik" +import { useMemo } from "react" +import { IconCheck, IconError } from "src/components/icons" +import s from './SelectFieldInForm.module.scss' + + +interface Props { + placeholder?: string + styleType?: 'default' | 'custom' + backgroundTransparent?: boolean + icon?: React.ReactNode + isIconSuffix?: boolean + isShowIconSuccess?: boolean + name: string + error?: string + options: any[] + keyNameOption?: string[] + keyValueOption?: string + nameSeperator?: string + +} + +const SelectFieldInForm = ({ + name, + placeholder, + options, + styleType = 'default', + icon, + backgroundTransparent = false, + isIconSuffix = true, + isShowIconSuccess, + error, + keyNameOption = ['name'], + keyValueOption = 'value', + nameSeperator = " ", + +}: Props) => { + const iconElement = useMemo(() => { + if (error) { + return ( + + {' '} + + ) + } else if (isShowIconSuccess) { + return ( + + {' '} + + ) + } else if (icon) { + return {icon} + } + return <> + }, [icon, error, isShowIconSuccess]) + + return ( +
+
+ {iconElement} + + { + options.map((item) => { + let name = '' + keyNameOption.map((key) => { + if (name) { + name += nameSeperator + } + name += item[key] + }) + name = name.trim() + return + }) + } + + +
+ {error &&
{error}
} +
+ ) +} + +export default SelectFieldInForm diff --git a/src/components/common/index.ts b/src/components/common/index.ts index e2c28e186..754806669 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -50,6 +50,7 @@ export { default as RecommendedRecipes} from './RecommendedRecipes/RecommendedRe export { default as LayoutCheckout} from './LayoutCheckout/LayoutCheckout' export { default as InputPasswordFiledInForm} from './InputPasswordFiledInForm/InputPasswordFiledInForm' export { default as InputFiledInForm} from './InputFiledInForm/InputFiledInForm' +export { default as SelectFieldInForm} from './SelectFieldInForm/SelectFieldInForm' export { default as MessageCommon} from './MessageCommon/MessageCommon' export { default as FormForgot} from './ForgotPassword/FormForgot/FormForgot' export { default as FormResetPassword} from './ForgotPassword/FormResetPassword/FormResetPassword' diff --git a/src/components/contexts/Message/MessageContext.tsx b/src/components/contexts/Message/MessageContext.tsx index f9399498e..fef8497c0 100644 --- a/src/components/contexts/Message/MessageContext.tsx +++ b/src/components/contexts/Message/MessageContext.tsx @@ -6,7 +6,7 @@ export type MessageContextType = { removeMessage: (id: number) => void showMessageSuccess: (content: string, timeout?: number) => void showMessageInfo: (content: string, timeout?: number) => void - showMessageError: (content: string, timeout?: number) => void + showMessageError: (content?: string, timeout?: number) => void showMessageWarning: (content: string, timeout?: number) => void } export const DEFAULT_MESSAGE_CONTEXT: MessageContextType = { diff --git a/src/components/contexts/Message/MessageProvider.tsx b/src/components/contexts/Message/MessageProvider.tsx index 0ccde8609..bf2706f5e 100644 --- a/src/components/contexts/Message/MessageProvider.tsx +++ b/src/components/contexts/Message/MessageProvider.tsx @@ -1,5 +1,6 @@ -import { ReactNode, useCallback, useState } from 'react' +import { ReactNode, useState } from 'react' import { MessageItemProps } from 'src/components/common/MessageCommon/MessageItem/MessageItem' +import { LANGUAGE } from 'src/utils/language.utils' import { MessageContext } from './MessageContext' type Props = { @@ -33,8 +34,8 @@ export function MessageProvider({ children }: Props) { createNewMessage(content, timeout, 'info') } - const showMessageError = (content: string, timeout?: number) => { - createNewMessage(content, timeout, 'error') + const showMessageError = (content?: string, timeout?: number) => { + createNewMessage(content || LANGUAGE.MESSAGE.ERROR, timeout, 'error') } const showMessageWarning = (content: string, timeout?: number) => { diff --git a/src/components/hooks/auth/useLogin.tsx b/src/components/hooks/auth/useLogin.tsx index d38390004..c0125f73a 100644 --- a/src/components/hooks/auth/useLogin.tsx +++ b/src/components/hooks/auth/useLogin.tsx @@ -6,6 +6,7 @@ import { LoginMutation } from '@framework/schema' import { LOCAL_STORAGE_KEY } from 'src/utils/constanst.utils' import { errorMapping } from 'src/utils/errrorMapping' import { loginMutation } from '@framework/utils/mutations/log-in-mutation' +import { useGetActiveOrder } from '../cart' interface LoginInput { username: string @@ -16,6 +17,7 @@ const useLogin = () => { const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const { mutate } = useActiveCustomer() + const { mutate: mutateOrder } = useGetActiveOrder() const login = (options: LoginInput, fCallBack: (isSuccess: boolean, message?: string) => void @@ -34,6 +36,7 @@ const useLogin = () => { if (authToken != null) { localStorage.setItem(LOCAL_STORAGE_KEY.TOKEN, authToken) mutate() + mutateOrder() } fCallBack(true) }) diff --git a/src/components/hooks/cart/useGetActiveOrder.tsx b/src/components/hooks/cart/useGetActiveOrder.tsx index 4c26a1786..cb7af3f2e 100644 --- a/src/components/hooks/cart/useGetActiveOrder.tsx +++ b/src/components/hooks/cart/useGetActiveOrder.tsx @@ -1,4 +1,3 @@ -import { Cart } from '@commerce/types/cart' import { ActiveOrderQuery } from '@framework/schema' import { cartFragment } from '@framework/utils/fragments/cart-fragment' import { normalizeCart } from '@framework/utils/normalize' @@ -16,8 +15,7 @@ const query = gql` const useGetActiveOrder = () => { const { data, ...rest } = useSWR([query], gglFetcher) - return { order: data?.activeOrder ? normalizeCart(data!.activeOrder) : null, ...rest } - + return { order: data?.activeOrder ? normalizeCart(data!.activeOrder) : null, ...rest } } export default useGetActiveOrder diff --git a/src/components/hooks/cart/useRemoveProductInCart.tsx b/src/components/hooks/cart/useRemoveProductInCart.tsx index d66fd4306..27fcb8de2 100644 --- a/src/components/hooks/cart/useRemoveProductInCart.tsx +++ b/src/components/hooks/cart/useRemoveProductInCart.tsx @@ -1,11 +1,10 @@ +import { RemoveOrderLineMutation, RemoveOrderLineMutationVariables } from '@framework/schema' +import { removeOrderLineMutation } from '@framework/utils/mutations/remove-order-line-mutation' import { useState } from 'react' import { CommonError } from 'src/domains/interfaces/CommonError' -import rawFetcher from 'src/utils/rawFetcher' -import { AdjustOrderLineMutationVariables,AdjustOrderLineMutation, RemoveOrderLineMutation, RemoveOrderLineMutationVariables } from '@framework/schema' import { errorMapping } from 'src/utils/errrorMapping' +import rawFetcher from 'src/utils/rawFetcher' import { useGetActiveOrder } from '.' -import { adjustOrderLineMutation } from '@framework/utils/mutations/adjust-order-line-mutation' -import { removeOrderLineMutation } from '@framework/utils/mutations/remove-order-line-mutation' const useRemoveProductInCart = () => { const [loading, setLoading] = useState(false) diff --git a/src/components/hooks/order/index.ts b/src/components/hooks/order/index.ts new file mode 100644 index 000000000..9c20a24be --- /dev/null +++ b/src/components/hooks/order/index.ts @@ -0,0 +1,8 @@ +export { default as useSetCustomerForOrder } from './useSetCustomerForOrder' +export { default as useSetOrderShippingAddress } from './useSetOrderShippingAddress' +export { default as useApplyCouponCode } from './useApplyCouponCode' +export { default as useAvailableCountries } from './useAvailableCountries' +export { default as useSetOrderShippingMethod } from './useSetOrderShippingMethod' +export { default as useGetActiveOrderForCheckout } from './useGetActiveOrderForCheckout' +export { default as useEligibleShippingMethods } from './useEligibleShippingMethods' + diff --git a/src/components/hooks/order/useApplyCouponCode.tsx b/src/components/hooks/order/useApplyCouponCode.tsx new file mode 100644 index 000000000..329682ed8 --- /dev/null +++ b/src/components/hooks/order/useApplyCouponCode.tsx @@ -0,0 +1,41 @@ +import { ApplyCouponCodeMutation } from '@framework/schema' +import { applyCouponCodeMutation } from '@framework/utils/mutations/apply-coupon-code-mutation' +import { useState } from 'react' +import { CommonError } from 'src/domains/interfaces/CommonError' +import rawFetcher from 'src/utils/rawFetcher' +import { useGetActiveOrderForCheckout } from '.' + + +const useApplyCouponCode = () => { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const { mutate } = useGetActiveOrderForCheckout() + + const applyCouponCode = (couponCode: string, + fCallBack: (isSuccess: boolean, message?: string) => void + ) => { + setError(null) + setLoading(true) + rawFetcher({ + query: applyCouponCodeMutation, + variables: { couponCode }, + }) + .then(({ data }) => { + if (data.applyCouponCode.__typename === 'Order') { + fCallBack(true) + mutate() + } else { + fCallBack(false, data.applyCouponCode.message) + } + }) + .catch((error) => { + setError(error) + fCallBack(false, error.message) + }) + .finally(() => setLoading(false)) + } + + return { loading, applyCouponCode, error } +} + +export default useApplyCouponCode diff --git a/src/components/hooks/order/useAvailableCountries.tsx b/src/components/hooks/order/useAvailableCountries.tsx new file mode 100644 index 000000000..51099965a --- /dev/null +++ b/src/components/hooks/order/useAvailableCountries.tsx @@ -0,0 +1,14 @@ +import { GetAvailableCountriesQuery } from '@framework/schema' +import { availableCountriesQuery } from '@framework/utils/queries/available-countries-query' +import gglFetcher from 'src/utils/gglFetcher' +import useSWR from 'swr' + +const useAvailableCountries = () => { + const { data, isValidating } = useSWR([availableCountriesQuery], gglFetcher) + return { + countries: data?.availableCountries, + loading: isValidating, + } +} + +export default useAvailableCountries diff --git a/src/components/hooks/order/useEligibleShippingMethods.tsx b/src/components/hooks/order/useEligibleShippingMethods.tsx new file mode 100644 index 000000000..cd4b64019 --- /dev/null +++ b/src/components/hooks/order/useEligibleShippingMethods.tsx @@ -0,0 +1,14 @@ +import { GetEligibleMethodsQuery, ShippingMethodQuote } from '@framework/schema' +import { getEligibleShippingMethods } from '@framework/utils/queries/eligible-shipping-methods-query' +import gglFetcher from 'src/utils/gglFetcher' +import useSWR from 'swr' + +const useEligibleShippingMethods = () => { + const { data, isValidating } = useSWR([getEligibleShippingMethods], gglFetcher) + return { + eligibleShippingMethods: data?.eligibleShippingMethods as ShippingMethodQuote[], + loading: isValidating, + } +} + +export default useEligibleShippingMethods diff --git a/src/components/hooks/order/useGetActiveOrderForCheckout.tsx b/src/components/hooks/order/useGetActiveOrderForCheckout.tsx new file mode 100644 index 000000000..fb69f82be --- /dev/null +++ b/src/components/hooks/order/useGetActiveOrderForCheckout.tsx @@ -0,0 +1,13 @@ +import { ActiveOrderQuery } from '@framework/schema' +import { normalizeCartForCheckout } from '@framework/utils/normalize' +import { getActiveOrderForCheckoutQuery } from '@framework/utils/queries/active-order-for-checkout-query' +import gglFetcher from 'src/utils/gglFetcher' +import useSWR from 'swr' + + +const useGetActiveOrderForCheckout = () => { + const { data, ...rest } = useSWR([getActiveOrderForCheckoutQuery], gglFetcher) + return { order: data?.activeOrder ? normalizeCartForCheckout(data!.activeOrder) : null, ...rest } +} + +export default useGetActiveOrderForCheckout diff --git a/src/components/hooks/order/useSetCustomerForOrder.tsx b/src/components/hooks/order/useSetCustomerForOrder.tsx new file mode 100644 index 000000000..028f255cc --- /dev/null +++ b/src/components/hooks/order/useSetCustomerForOrder.tsx @@ -0,0 +1,42 @@ +import { CreateCustomerInput, SetCustomerForOrderMutation } from '@framework/schema' +import { setCustomerForOrderMutation } from '@framework/utils/mutations/set-customer-order-mutation' +import { useState } from 'react' +import { CommonError } from 'src/domains/interfaces/CommonError' +import rawFetcher from 'src/utils/rawFetcher' +import { useGetActiveOrderForCheckout } from '.' + + +const useSetCustomerForOrder = () => { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const { mutate } = useGetActiveOrderForCheckout() + + const setCustomerForOrder = (input: CreateCustomerInput, + fCallBack: (isSuccess: boolean, message?: CommonError) => void + ) => { + setError(null) + setLoading(true) + rawFetcher({ + query: setCustomerForOrderMutation, + variables: { input }, + }) + .then(({ data }) => { + if (data.setCustomerForOrder.__typename === 'Order') { + fCallBack(true) + mutate() + } else { + fCallBack(false, data.setCustomerForOrder) + } + + }) + .catch((error) => { + setError(error) + fCallBack(false, error) + }) + .finally(() => setLoading(false)) + } + + return { loading, setCustomerForOrder, error } +} + +export default useSetCustomerForOrder diff --git a/src/components/hooks/order/useSetOrderShippingAddress.tsx b/src/components/hooks/order/useSetOrderShippingAddress.tsx new file mode 100644 index 000000000..be020c48f --- /dev/null +++ b/src/components/hooks/order/useSetOrderShippingAddress.tsx @@ -0,0 +1,42 @@ +import { CreateAddressInput, SetOrderShippingAddressMutation } from '@framework/schema' +import { setOrderShippingAddressMutation } from '@framework/utils/mutations/set-order-shipping-address-mutation' +import { useState } from 'react' +import { CommonError } from 'src/domains/interfaces/CommonError' +import rawFetcher from 'src/utils/rawFetcher' +import { useGetActiveOrderForCheckout } from '.' + + +const useSetOrderShippingAddress = () => { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const { mutate } = useGetActiveOrderForCheckout() + + const setOrderShippingAddress = (input: CreateAddressInput, + fCallBack: (isSuccess: boolean, message?: string) => void + ) => { + setError(null) + setLoading(true) + rawFetcher({ + query: setOrderShippingAddressMutation, + variables: { input }, + }) + .then(({ data }) => { + if (data.setOrderShippingAddress.__typename === 'Order') { + fCallBack(true) + mutate() + } else { + fCallBack(false, data.setOrderShippingAddress.message) + } + + }) + .catch((error) => { + setError(error) + fCallBack(false, error.message) + }) + .finally(() => setLoading(false)) + } + + return { loading, setOrderShippingAddress, error } +} + +export default useSetOrderShippingAddress diff --git a/src/components/hooks/order/useSetOrderShippingMethod.tsx b/src/components/hooks/order/useSetOrderShippingMethod.tsx new file mode 100644 index 000000000..6c4f48f5a --- /dev/null +++ b/src/components/hooks/order/useSetOrderShippingMethod.tsx @@ -0,0 +1,41 @@ +import { SetShippingMethodMutation } from '@framework/schema' +import { setShippingMethodMutation } from '@framework/utils/mutations/set-order-shipping-method-mutation' +import { useState } from 'react' +import { CommonError } from 'src/domains/interfaces/CommonError' +import rawFetcher from 'src/utils/rawFetcher' +import { useGetActiveOrderForCheckout } from '.' + + +const useSetOrderShippingMethod = () => { + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const { mutate } = useGetActiveOrderForCheckout() + + const setOrderShippingMethod = (id: string, + fCallBack: (isSuccess: boolean, message?: string) => void + ) => { + setError(null) + setLoading(true) + rawFetcher({ + query: setShippingMethodMutation, + variables: { id }, + }) + .then(({ data }) => { + if (data.setOrderShippingMethod.__typename === 'Order') { + fCallBack(true) + mutate() + } else { + fCallBack(false, data.setOrderShippingMethod.message) + } + }) + .catch((error) => { + setError(error) + fCallBack(false, error.message) + }) + .finally(() => setLoading(false)) + } + + return { loading, setOrderShippingMethod, error } +} + +export default useSetOrderShippingMethod diff --git a/src/components/modules/checkout/CheckoutBill/CheckoutBill.module.scss b/src/components/modules/checkout/CheckoutBill/CheckoutBill.module.scss index 84dea0f06..713a984a5 100644 --- a/src/components/modules/checkout/CheckoutBill/CheckoutBill.module.scss +++ b/src/components/modules/checkout/CheckoutBill/CheckoutBill.module.scss @@ -1,6 +1,7 @@ .warpper { padding: 3.2rem; min-width: 100%; + min-height: 100%; @screen lg { max-width: 56.3rem; @apply flex justify-between flex-col; @@ -14,15 +15,14 @@ display: block; } } + .empty { + @apply flex flex-col justify-center items-center; + } .list { - min-height: 52.8rem; + // min-height: 52.8rem; } .bot { - .promo { - // padding: 3.2rem; - @apply bg-gray flex justify-between items-center; - min-height: 6.4rem; - } + margin-top: auto; .price { margin-top: 3.2rem; .line { diff --git a/src/components/modules/checkout/CheckoutBill/CheckoutBill.tsx b/src/components/modules/checkout/CheckoutBill/CheckoutBill.tsx index 259397980..3e3910c90 100644 --- a/src/components/modules/checkout/CheckoutBill/CheckoutBill.tsx +++ b/src/components/modules/checkout/CheckoutBill/CheckoutBill.tsx @@ -1,41 +1,59 @@ +import { CartCheckout } from '@commerce/types/cart' +import Link from 'next/link' import React from 'react' +import { ROUTE } from 'src/utils/constanst.utils' +import { LANGUAGE } from 'src/utils/language.utils' +import { ButtonCommon, CardItemCheckout, EmptyCommon } from '../../../common' import s from './CheckoutBill.module.scss' -import { CardItemCheckout } from '../../../common' -import { CardItemCheckoutProps } from '../../../common/CardItemCheckout/CardItemCheckout' -import { IconCirclePlus } from 'src/components/icons' +import FormPromotionCode from './FormPromotionCode/FormPromotionCode' interface CheckoutBillProps { - data: CardItemCheckoutProps[] + data: CartCheckout | null } const CheckoutBill = ({ data }: CheckoutBillProps) => { return (
-
- Your cart ({data.length}) -
+
+ Your cart ({data?.totalQuantity || 0}) +
+ { + !data?.totalQuantity && + }
- {data.map((item) => { - return + {data?.lineItems?.map((item) => { + return })}
-
- Apply Promotion Code - -
+
+
+ Discount {(data?.discounts?.length || 0) > 0 && `(${data?.discounts?.map(item => item.description).join(",")})`} +
+ {data?.totalDiscount || 0} {data?.currency?.code} +
+
Subtotal -
RP 120.500
+
+ {data?.subtotalPrice || 0} {data?.currency?.code} +
Shipping -
Free
+
{data?.shippingLine?.priceWithTax || 0} {data?.currency?.code}
Estimated Total -
RP 120.500
+
{data?.totalPrice || 0} {data?.currency?.code}
diff --git a/src/components/modules/checkout/CheckoutBill/FormPromotionCode/FormPromotionCode.module.scss b/src/components/modules/checkout/CheckoutBill/FormPromotionCode/FormPromotionCode.module.scss new file mode 100644 index 000000000..1391a5e00 --- /dev/null +++ b/src/components/modules/checkout/CheckoutBill/FormPromotionCode/FormPromotionCode.module.scss @@ -0,0 +1,19 @@ +.promo { + @apply bg-gray flex justify-between items-center; + min-height: 6.4rem; + .modalPromotion { + min-width: 40rem; + .bottom { + @apply flex justify-end items-center; + margin-top: 3.2rem; + button { + &:first-child { + margin-right: 0.8rem; + @screen md { + margin-right: 3.2rem; + } + } + } + } + } +} diff --git a/src/components/modules/checkout/CheckoutBill/FormPromotionCode/FormPromotionCode.tsx b/src/components/modules/checkout/CheckoutBill/FormPromotionCode/FormPromotionCode.tsx new file mode 100644 index 000000000..aaf8f6b32 --- /dev/null +++ b/src/components/modules/checkout/CheckoutBill/FormPromotionCode/FormPromotionCode.tsx @@ -0,0 +1,97 @@ +import { Form, Formik } from 'formik'; +import React, { useEffect, useRef } from 'react'; +import { ButtonCommon, InputFiledInForm, ModalCommon } from 'src/components/common'; +import { useMessage } from 'src/components/contexts'; +import { useModalCommon } from 'src/components/hooks'; +import { useApplyCouponCode } from 'src/components/hooks/order'; +import { IconCirclePlus } from 'src/components/icons'; +import { LANGUAGE } from 'src/utils/language.utils'; +import { CustomInputCommon } from 'src/utils/type.utils'; +import * as Yup from 'yup'; +import s from './FormPromotionCode.module.scss'; + +const displayingErrorMessagesSchema = Yup.object().shape({ + couponCode: Yup.string().required(LANGUAGE.MESSAGE.REQUIRED), +}) + +const FormPromotionCode = () => { + const { visible, openModal, closeModal } = useModalCommon({ initialValue: false }) + const { showMessageError, showMessageSuccess } = useMessage() + const { applyCouponCode, loading } = useApplyCouponCode() + const inputRef = useRef(null) + + useEffect(() => { + setTimeout(() => { + if (visible) { + inputRef.current?.focus() + } + }, 500); + }, [visible]) + + const handleSubmit = (values: { couponCode: string }) => { + applyCouponCode(values.couponCode, onSubmitCalBack) + } + + const onSubmitCalBack = (isSuccess: boolean, msg?: string) => { + // TODO: + if (isSuccess) { + showMessageSuccess("Applied coupon code successfully.", 5000) + closeModal() + } else { + showMessageError(msg) + } + } + + return ( +
+ Apply Promotion Code + + +
+ + {({ errors, touched, isValid, submitForm }) => ( +
+
+ + +
+
+ + {LANGUAGE.BUTTON_LABEL.CANCEL} + + + Apply promotion code + +
+
+ )} +
+
+
+
+ ); +}; + +export default FormPromotionCode; \ No newline at end of file diff --git a/src/components/modules/checkout/CheckoutInfo/CheckoutInfo.tsx b/src/components/modules/checkout/CheckoutInfo/CheckoutInfo.tsx index 423f92635..32cfec5fd 100644 --- a/src/components/modules/checkout/CheckoutInfo/CheckoutInfo.tsx +++ b/src/components/modules/checkout/CheckoutInfo/CheckoutInfo.tsx @@ -1,50 +1,100 @@ -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import { Logo } from 'src/components/common' import CheckoutCollapse from 'src/components/common/CheckoutCollapse/CheckoutCollapse' -import { removeItem } from 'src/utils/funtion.utils' -import { CheckOutForm } from 'src/utils/types.utils' +import { useActiveCustomer } from 'src/components/hooks/auth' +import { useGetActiveOrderForCheckout } from 'src/components/hooks/order' import s from './CheckoutInfo.module.scss' import CustomerInfoForm from './components/CustomerInfoForm/CustomerInfoForm' import PaymentInfoForm from './components/PaymentInfoForm/PaymentInfoForm' import ShippingInfoForm from './components/ShippingInfoForm/ShippingInfoForm' +import ShippingMethod from './components/ShippingMethod/ShippingMethod' interface CheckoutInfoProps { - onViewCart:()=>void + onViewCart: () => void + currency?: string } -const CheckoutInfo = ({onViewCart}: CheckoutInfoProps) => { - const [active, setActive] = useState(1) - const [done, setDone] = useState([]) - const [info, setInfo] = useState({}) +export enum CheckoutStep { + CustomerInfo = 1, + ShippingAddressInfo = 2, + ShippingMethodInfo = 3, + PaymentInfo = 4, +} - const onEdit = (id:number) => { - setActive(id) - setDone(removeItem(done,id)) - } +const CheckoutInfo = ({ onViewCart, currency = "" }: CheckoutInfoProps) => { + const [activeStep, setActiveStep] = useState(1) + const [doneSteps, setDoneSteps] = useState([]) + const { order } = useGetActiveOrderForCheckout() + const { customer } = useActiveCustomer() - const onConfirm = (id:number,formInfo:CheckOutForm) => { - if(id+1>formList.length){ - console.log({...info,...formInfo}) - }else{ - if(done.length>0){ - for (let i = id+1; i <= formList.length; i++) { - if(!done.includes(i)){ - setActive(i) + useEffect(() => { + if (customer) { + if (!doneSteps.includes(CheckoutStep.CustomerInfo)) { + + if (doneSteps.length > 0) { + for (let i = CheckoutStep.CustomerInfo + 1; i <= Object.keys(CheckoutStep).length; i++) { + if (!doneSteps.includes(i)) { + setActiveStep(i) + } } + } else { + setActiveStep(CheckoutStep.CustomerInfo + 1) } - }else{ - setActive(id+1) - } - setDone([...done,id]) - } - setInfo({...info,...formInfo}) - } - const getNote = (id:number) => { + setDoneSteps([...doneSteps, CheckoutStep.CustomerInfo]) + } + } + }, [customer, doneSteps]) + + + const onEdit = (id: CheckoutStep) => { + setActiveStep(id) + } + + const updateActiveStep = (step: CheckoutStep) => { + if (doneSteps.length > 0) { + for (let i = step + 1; i < Object.keys(CheckoutStep).length; i++) { + if (!doneSteps.includes(i)) { + setActiveStep(i) + return + } + } + } else { + setActiveStep(step + 1) + } + } + + const onConfirm = (step: CheckoutStep) => { + if (step + 1 > formList.length) { + // TODO: checkout + console.log("finish: ", order) + } else { + updateActiveStep(step) + setDoneSteps([...doneSteps, step]) + } + } + + + const getNote = (id: CheckoutStep) => { switch (id) { - case 1: - return `${info.name}, ${info.email}` - case 2: - return `${info.address}, ${info.state}, ${info.city}, ${info.code}, ${info.phone}, ` + case CheckoutStep.CustomerInfo: + if (order?.customer?.emailAddress) { + return `${order?.customer?.firstName} ${order?.customer?.lastName}, ${order?.customer?.emailAddress}` + } else if (customer) { + return `${customer.firstName} ${customer.lastName}, ${customer.emailAddress}` + } else { + return '' + } + case CheckoutStep.ShippingAddressInfo: + if (order?.shippingAddress) { + const { streetLine1, city, province, postalCode, countryCode, phoneNumber } = order.shippingAddress + return `${streetLine1}, ${city}, ${province}, ${postalCode}, ${countryCode}, ${phoneNumber}` + } + return '' + case CheckoutStep.ShippingMethodInfo: + if (order?.shippingLine) { + return `${order?.shippingLine.shippingMethod.name}, ${order?.shippingLine.priceWithTax ? `${order?.shippingLine.priceWithTax} ${currency}` : 'Free'}` || '' + } + return '' default: return "" } @@ -52,21 +102,27 @@ const CheckoutInfo = ({onViewCart}: CheckoutInfoProps) => { const formList = [ { - id: 1, + id: CheckoutStep.CustomerInfo, title: 'Customer Information', - form: , + form: , }, { - id: 2, - title: 'Shipping Information', - form: , + id: CheckoutStep.ShippingAddressInfo, + title: 'Shipping Address Information', + form: , }, { - id: 3, + id: CheckoutStep.ShippingMethodInfo, + title: 'Shipping Method Information', + form: , + }, + { + id: CheckoutStep.PaymentInfo, title: 'Payment Information', - form: , + form: , }, ] + return (
@@ -76,13 +132,15 @@ const CheckoutInfo = ({onViewCart}: CheckoutInfoProps) => { {formList.map((item) => { let note = getNote(item.id) return {item.form} diff --git a/src/components/modules/checkout/CheckoutInfo/components/ChekoutNotePolicy/ChekoutNotePolicy.module.scss b/src/components/modules/checkout/CheckoutInfo/components/ChekoutNotePolicy/ChekoutNotePolicy.module.scss new file mode 100644 index 000000000..83f678cf2 --- /dev/null +++ b/src/components/modules/checkout/CheckoutInfo/components/ChekoutNotePolicy/ChekoutNotePolicy.module.scss @@ -0,0 +1,6 @@ +@import "../../../../../../styles/utilities"; + +.chekoutNotePolicy { + @apply caption; + margin-bottom: 1.6rem; +} diff --git a/src/components/modules/checkout/CheckoutInfo/components/ChekoutNotePolicy/ChekoutNotePolicy.tsx b/src/components/modules/checkout/CheckoutInfo/components/ChekoutNotePolicy/ChekoutNotePolicy.tsx new file mode 100644 index 000000000..c5cf41955 --- /dev/null +++ b/src/components/modules/checkout/CheckoutInfo/components/ChekoutNotePolicy/ChekoutNotePolicy.tsx @@ -0,0 +1,31 @@ +import Link from 'next/link' +import React, { memo } from 'react' +import { ROUTE } from 'src/utils/constanst.utils' +import s from './ChekoutNotePolicy.module.scss' + +const ChekoutNotePolicy = memo(() => { + return ( +
+ By clicking continue you agree to Casper's{' '} + { + + + terms and conditions + + + }{' '} + and{' '} + { + + + privacy policy + + + } + . +
+ ) +}) + +ChekoutNotePolicy.displayName = 'ChekoutNotePolicy' +export default ChekoutNotePolicy diff --git a/src/components/modules/checkout/CheckoutInfo/components/CustomerInfoForm/CustomerInfoForm.module.scss b/src/components/modules/checkout/CheckoutInfo/components/CustomerInfoForm/CustomerInfoForm.module.scss index 863a716b8..cc415f373 100644 --- a/src/components/modules/checkout/CheckoutInfo/components/CustomerInfoForm/CustomerInfoForm.module.scss +++ b/src/components/modules/checkout/CheckoutInfo/components/CustomerInfoForm/CustomerInfoForm.module.scss @@ -8,18 +8,10 @@ } } .bottom{ + @apply flex flex-col items-start; margin-top: 2.4rem; - @apply flex justify-between items-center; - .note{ - font-size: 1.2rem; - line-height: 2rem; + button { + margin-left: auto; } - @screen sm-only { - @apply flex-col items-start; - .button { - padding-top: 2rem; - } - } - } } \ No newline at end of file diff --git a/src/components/modules/checkout/CheckoutInfo/components/CustomerInfoForm/CustomerInfoForm.tsx b/src/components/modules/checkout/CheckoutInfo/components/CustomerInfoForm/CustomerInfoForm.tsx index 5894683d7..254983dda 100644 --- a/src/components/modules/checkout/CheckoutInfo/components/CustomerInfoForm/CustomerInfoForm.tsx +++ b/src/components/modules/checkout/CheckoutInfo/components/CustomerInfoForm/CustomerInfoForm.tsx @@ -1,55 +1,142 @@ -import Link from 'next/link' -import React, { useRef } from 'react' -import { ButtonCommon, Inputcommon } from 'src/components/common' -import InputCommon from 'src/components/common/InputCommon/InputCommon' -import { CheckOutForm } from 'src/utils/types.utils' +import { Form, Formik } from 'formik' +import React, { useEffect, useRef, useState } from 'react' +import { ButtonCommon, InputFiledInForm } from 'src/components/common' +import ModalAuthenticate from 'src/components/common/ModalAuthenticate/ModalAuthenticate' +import { useMessage } from 'src/components/contexts' +import { useModalCommon } from 'src/components/hooks' +import { useSetCustomerForOrder } from 'src/components/hooks/order' +import { ErrorCode } from 'src/domains/enums/ErrorCode' +import { CommonError } from 'src/domains/interfaces/CommonError' +import { LANGUAGE } from 'src/utils/language.utils' +import { CustomInputCommon } from 'src/utils/type.utils' +import * as Yup from 'yup' +import ChekoutNotePolicy from '../ChekoutNotePolicy/ChekoutNotePolicy' import s from './CustomerInfoForm.module.scss' -interface CustomerInfoFormProps { - onConfirm?: (id: number, formInfo: CheckOutForm) => void +import ModalConfirmLogin from './ModalConfirmLogin/ModalConfirmLogin' +interface Props { id: number + onConfirm: (id: number) => void + activeStep: number + } -const CustomerInfoForm = ({ id, onConfirm }: CustomerInfoFormProps) => { - const nameRef = useRef>(null) - const emailRef = useRef>(null) +const displayingErrorMessagesSchema = Yup.object().shape({ + firstName: Yup.string().required(LANGUAGE.MESSAGE.REQUIRED), + lastName: Yup.string().required(LANGUAGE.MESSAGE.REQUIRED), + emailAddress: Yup.string().email(LANGUAGE.MESSAGE.INVALID_EMAIL).required(LANGUAGE.MESSAGE.REQUIRED), +}) - const handleConfirmClick = () => { - onConfirm && - onConfirm(id, { - name: nameRef?.current?.getValue().toString(), - email: emailRef.current?.getValue().toString(), - }) +const CustomerInfoForm = ({ id, onConfirm, activeStep }: Props) => { + const firstNameRef = useRef(null) + const emailRef = useRef(null) + const { setCustomerForOrder, loading } = useSetCustomerForOrder() + const { showMessageError } = useMessage() + const [emailAddress, setEmailAddress] = useState('') + const { visible: visibleModalConfirmLogin, closeModal: closeModalConfirmLogin, openModal: openModalConfirmLogin } = useModalCommon({ initialValue: false }) + const { visible: visibleModalAuthen, closeModal: closeModalAuthen, openModal: openModalAuthen } = useModalCommon({ initialValue: false }) + + useEffect(() => { + setTimeout(() => { + firstNameRef.current?.focus() + }, 500); + }, [activeStep]) + + const handleSubmit = (values: { firstName: string, lastName: string, emailAddress: string }) => { + const { firstName, lastName, emailAddress } = values + setEmailAddress(emailAddress) + setCustomerForOrder({ firstName, lastName, emailAddress }, onSubmitCalBack) + } + const onSubmitCalBack = (isSuccess: boolean, error?: CommonError) => { + // TODO: + if (isSuccess) { + onConfirm(id) + } else { + if (error?.errorCode === ErrorCode.EmailAddressConflictError) { + // show modal common + openModalConfirmLogin() + } else if (error?.errorCode === ErrorCode.NoActiveOrderError) { + showMessageError("Your cart is empty! Please add items to the cart!") + } else { + showMessageError(error?.message) + } + } + } + const handleOpenModalLogin = () => { + closeModalConfirmLogin() + openModalAuthen() + } + + const handleCloseModalConfirmLogin = () => { + closeModalConfirmLogin() + emailRef.current?.focus() } return ( -
+
- - + + {({ errors, touched, isValid, submitForm }) => ( +
+
+
+ + + +
+ +
+
+ + + Continue to Shipping + +
+
+ )} +
-
-
- By clicking continue you agree to Casper's{' '} - { - - terms and conditions - - }{' '} - and{' '} - { - - privacy policy - - } - . -
-
- - Continue to Shipping - -
-
-
+ + + ) } diff --git a/src/components/modules/checkout/CheckoutInfo/components/CustomerInfoForm/ModalConfirmLogin/ModalConfirmLogin.module.scss b/src/components/modules/checkout/CheckoutInfo/components/CustomerInfoForm/ModalConfirmLogin/ModalConfirmLogin.module.scss new file mode 100644 index 000000000..ca70f06a7 --- /dev/null +++ b/src/components/modules/checkout/CheckoutInfo/components/CustomerInfoForm/ModalConfirmLogin/ModalConfirmLogin.module.scss @@ -0,0 +1,5 @@ + +.modalConfirmLogin { + min-width: 40rem; + text-align: center; +} \ No newline at end of file diff --git a/src/components/modules/checkout/CheckoutInfo/components/CustomerInfoForm/ModalConfirmLogin/ModalConfirmLogin.tsx b/src/components/modules/checkout/CheckoutInfo/components/CustomerInfoForm/ModalConfirmLogin/ModalConfirmLogin.tsx new file mode 100644 index 000000000..bf2445eb9 --- /dev/null +++ b/src/components/modules/checkout/CheckoutInfo/components/CustomerInfoForm/ModalConfirmLogin/ModalConfirmLogin.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { ModalConfirm } from 'src/components/common'; +import { LANGUAGE } from 'src/utils/language.utils'; +import s from './ModalConfirmLogin.module.scss' + +interface Props { + visible: boolean + closeModal: () => void + handleOk: () => void + email: string +} + +const ModalConfirmLogin = ({ visible, closeModal, handleOk, email }: Props) => { + return ( +
+ +
+

Account already exists for email {email}

+

Please signin to continue or use another email

+
+
+
+ ); +}; + +export default ModalConfirmLogin; \ No newline at end of file diff --git a/src/components/modules/checkout/CheckoutInfo/components/PaymentInfoForm/PaymentInfoForm.tsx b/src/components/modules/checkout/CheckoutInfo/components/PaymentInfoForm/PaymentInfoForm.tsx index eac100fb0..de72812c3 100644 --- a/src/components/modules/checkout/CheckoutInfo/components/PaymentInfoForm/PaymentInfoForm.tsx +++ b/src/components/modules/checkout/CheckoutInfo/components/PaymentInfoForm/PaymentInfoForm.tsx @@ -1,19 +1,18 @@ import React from 'react' import { ButtonCommon, TabCommon, TabPane } from 'src/components/common' -import { CheckOutForm } from 'src/utils/types.utils' import BankTransfer from '../BankTransfer/BankTransfer' -import Link from 'next/link' - -import s from './PaymentInfoForm.module.scss' +import ChekoutNotePolicy from '../ChekoutNotePolicy/ChekoutNotePolicy' import CreditCardForm from '../CreditCardForm/CreditCardForm' +import s from './PaymentInfoForm.module.scss' + interface PaymentInfoFormProps { - onConfirm?: (id: number, formInfo: CheckOutForm) => void + onConfirm?: (id: number) => void id: number } const PaymentInfoForm = ({onConfirm,id}: PaymentInfoFormProps) => { const handleConfirmClick = () => { - onConfirm && onConfirm(id,{}) + onConfirm && onConfirm(id) } return (
@@ -29,21 +28,7 @@ const PaymentInfoForm = ({onConfirm,id}: PaymentInfoFormProps) => {
-
- By clicking continue you agree to Casper's{' '} - { - - terms and conditions - - }{' '} - and{' '} - { - - privacy policy - - } - . -
+
Submit Order diff --git a/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.module.scss b/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.module.scss index aa177fc88..74d70da48 100644 --- a/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.module.scss +++ b/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.module.scss @@ -1,45 +1,20 @@ @import "../../../../../../styles/utilities"; -.warpper{ +.warpper { @apply u-form; @screen md { padding: 0 5.6rem; } - .bottom{ + .bottom { + @apply flex flex-col items-start; margin-top: 2.4rem; - @apply flex justify-between items-center; - .note{ - font-size: 1.2rem; - line-height: 2rem; - } - @screen sm-only { - @apply flex-col items-start; - .button { - padding-top: 2rem; - } + button { + margin-left: auto; } } - .line{ - >div{ + .line { + > div { width: 50%; } } - .method{ - width: 100%; - height: 5.6rem; - padding: 1.6rem; - border-radius: 0.8rem; - @apply flex justify-between items-center border border-solid border-line bg-gray; - .left{ - @apply flex; - .name{ - margin-left: 1.6rem; - color: var(--text-active); - } - } - .price{ - font-weight: bold; - color: var(--text-active); - } - } -} \ No newline at end of file +} diff --git a/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.tsx b/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.tsx index 04217d706..f9ef80ce6 100644 --- a/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.tsx +++ b/src/components/modules/checkout/CheckoutInfo/components/ShippingInfoForm/ShippingInfoForm.tsx @@ -1,96 +1,189 @@ -import React, { useRef } from 'react' -import { ButtonCommon, Inputcommon, SelectCommon } from 'src/components/common' -import s from './ShippingInfoForm.module.scss' -import Link from 'next/link' +import { Form, Formik } from 'formik' +import React, { useEffect, useRef } from 'react' +import { ButtonCommon, InputFiledInForm, SelectFieldInForm } from 'src/components/common' +import { useMessage } from 'src/components/contexts' +import { useAvailableCountries, useSetOrderShippingAddress } from 'src/components/hooks/order' +import { LANGUAGE } from 'src/utils/language.utils' import { CustomInputCommon } from 'src/utils/type.utils' -import { Shipping } from 'src/components/icons' -import { CheckOutForm } from 'src/utils/types.utils' +import * as Yup from 'yup' +import ChekoutNotePolicy from '../ChekoutNotePolicy/ChekoutNotePolicy' +import s from './ShippingInfoForm.module.scss' interface ShippingInfoFormProps { - onConfirm?: (id:number,formInfo:CheckOutForm)=>void - id:number + id: number + activeStep: number + onConfirm: (id: number) => void + } -const option = [ + +const displayingErrorMessagesSchema = Yup.object().shape({ + streetLine1: Yup.string().required(LANGUAGE.MESSAGE.REQUIRED), + city: Yup.string().required(LANGUAGE.MESSAGE.REQUIRED), + province: Yup.string().required(LANGUAGE.MESSAGE.REQUIRED), + postalCode: Yup.number().required(LANGUAGE.MESSAGE.REQUIRED), + countryCode: Yup.string().required(LANGUAGE.MESSAGE.REQUIRED), + phoneNumber: Yup.string().required(LANGUAGE.MESSAGE.REQUIRED), + +}) + +const DEFAULT_COUNTRY_CODE = 'MY' +const DEFAULT_PROVINCE = 'Sabah' + +// TODO: update data +const provinceOptions = [ { name: 'Hồ Chí Minh', + value: 'Hồ Chí Minh', }, { name: 'Hà Nội', + value: 'Hà Nội', + }, + { + name: 'Sabah', + value: 'Sabah', }, ] -const ShippingInfoForm = ({onConfirm,id}: ShippingInfoFormProps) => { +const ShippingInfoForm = ({ onConfirm, id, activeStep }: ShippingInfoFormProps) => { const addressRef = useRef(null) - const cityRef = useRef(null) - const stateRef = useRef(null) - const codeRef = useRef(null) - const phoneRef = useRef(null) - const handleConfirmClick = () => { - onConfirm && onConfirm(id,{ - address: addressRef?.current?.getValue().toString(), - city: cityRef.current?.getValue().toString(), - state: stateRef?.current?.getValue().toString(), - code: Number(codeRef.current?.getValue()), - phone: Number(phoneRef?.current?.getValue()), - }) + const { setOrderShippingAddress, loading } = useSetOrderShippingAddress() + const { showMessageError } = useMessage() + const { countries } = useAvailableCountries() + + useEffect(() => { + setTimeout(() => { + addressRef.current?.focus() + }, 500); + }, [activeStep]) + + const handleSubmit = (values: any) => { + setOrderShippingAddress(values, onSubmitCalBack) } + const onSubmitCalBack = (isSuccess: boolean, msg?: string) => { + if (isSuccess) { + onConfirm(id) + } else { + showMessageError(msg) + } + } + + return (
- - -
- State - -
- -
-
-
- -
-
- Standard Delivery Method -
-
-
-
- Free -
-
-
-
-
-
- By clicking continue you agree to Casper's{' '} - { - - terms and conditions - - }{' '} - and{' '} - { - - privacy policy - - } - . -
-
- - Continue to Payment - -
+ + {({ errors, touched, isValid, submitForm }) => ( +
+
+
+ +
+
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+ + + Continue to Shipping method + +
+
+
+ )} +
+
) diff --git a/src/components/modules/checkout/CheckoutInfo/components/ShippingMethod/ShippingMethod.module.scss b/src/components/modules/checkout/CheckoutInfo/components/ShippingMethod/ShippingMethod.module.scss new file mode 100644 index 000000000..225f3282a --- /dev/null +++ b/src/components/modules/checkout/CheckoutInfo/components/ShippingMethod/ShippingMethod.module.scss @@ -0,0 +1,28 @@ +.shippingMethod { + @apply relative; + .method { + @apply w-full flex justify-between items-center border border-solid border-line bg-gray cursor-pointer; + height: 5.6rem; + padding: 1.6rem; + border-radius: 0.8rem; + .left { + @apply flex; + .name { + margin-left: 1.6rem; + color: var(--text-active); + } + } + .price { + font-weight: bold; + color: var(--text-active); + } + } + + .options { + margin-top: 0.8rem; + width: 100%; + background: var(--white); + border: 1px solid var(--border-line); + border-radius: 0.8rem; + } +} diff --git a/src/components/modules/checkout/CheckoutInfo/components/ShippingMethod/ShippingMethod.tsx b/src/components/modules/checkout/CheckoutInfo/components/ShippingMethod/ShippingMethod.tsx new file mode 100644 index 000000000..10b3022e2 --- /dev/null +++ b/src/components/modules/checkout/CheckoutInfo/components/ShippingMethod/ShippingMethod.tsx @@ -0,0 +1,75 @@ +import { ShippingMethodQuote } from '@framework/schema' +import React, { memo, useState } from 'react' +import { useMessage } from 'src/components/contexts' +import { useEligibleShippingMethods, useSetOrderShippingMethod } from 'src/components/hooks/order' +import { Shipping } from 'src/components/icons' +import { CheckoutStep } from '../../CheckoutInfo' +import s from './ShippingMethod.module.scss' +import ShippingMethodItem from './ShippingMethodItem/ShippingMethodItem' + +interface Props { + currency: string + onConfirm: (id: number) => void + +} + +const ShippingMethod = memo(({ currency, onConfirm }: Props) => { + const { eligibleShippingMethods } = useEligibleShippingMethods() + const { setOrderShippingMethod } = useSetOrderShippingMethod() + const [selectedValue, setSelectedValue] = useState(eligibleShippingMethods ? eligibleShippingMethods[0] : undefined) + const { showMessageError } = useMessage() + + const onChange = (id: string) => { + const newValue = eligibleShippingMethods?.find(item => item.id === id) + if (newValue) { + setSelectedValue(newValue) + if (newValue?.id) { + setOrderShippingMethod(newValue?.id, onSubmitCalBack) + } + } + } + + const onSubmitCalBack = (isSuccess: boolean, msg?: string) => { + if (isSuccess) { + onConfirm(CheckoutStep.ShippingMethodInfo) + } else { + showMessageError(msg) + } + } + + return ( +
+
+
+
+ +
+
+ {selectedValue?.name} +
+
+
+
+ {selectedValue?.price ? `${selectedValue?.price / 100} ${currency}` : "Free"} +
+
+
+
+
    + {eligibleShippingMethods?.map(item => )} +
+
+
+ ) +}) + +ShippingMethod.displayName = 'ShippingMethod' +export default ShippingMethod diff --git a/src/components/modules/checkout/CheckoutInfo/components/ShippingMethod/ShippingMethodItem/ShippingMethodItem.module.scss b/src/components/modules/checkout/CheckoutInfo/components/ShippingMethod/ShippingMethodItem/ShippingMethodItem.module.scss new file mode 100644 index 000000000..9d8526629 --- /dev/null +++ b/src/components/modules/checkout/CheckoutInfo/components/ShippingMethod/ShippingMethodItem/ShippingMethodItem.module.scss @@ -0,0 +1,25 @@ +.shippingMethodItem { + @apply w-full flex justify-between items-center cursor-pointer transition-all duration-200; + padding: 1.6rem; + &:hover { + @apply bg-gray; + } + .left { + @apply flex; + .icon { + @apply transition-all duration-200; + opacity: 0; + &.show { + opacity: 1; + } + } + .name { + margin-left: 1.6rem; + color: var(--text-active); + } + } + .price { + font-weight: bold; + color: var(--text-active); + } +} diff --git a/src/components/modules/checkout/CheckoutInfo/components/ShippingMethod/ShippingMethodItem/ShippingMethodItem.tsx b/src/components/modules/checkout/CheckoutInfo/components/ShippingMethod/ShippingMethodItem/ShippingMethodItem.tsx new file mode 100644 index 000000000..30651168d --- /dev/null +++ b/src/components/modules/checkout/CheckoutInfo/components/ShippingMethod/ShippingMethodItem/ShippingMethodItem.tsx @@ -0,0 +1,38 @@ +import classNames from 'classnames' +import React, { memo } from 'react' +import { IconCheck } from 'src/components/icons' +import s from './ShippingMethodItem.module.scss' + +interface Props { + id: string + name: string + price: number + currency: string + onSelect: (id: string) => void + isActive: boolean + +} + +const ShippingMethodItem = memo(({ id, name, price, currency, isActive, onSelect }: Props) => { + const handleSelect = () => { + onSelect(id) + } + return ( +
  • +
    +
    + +
    +
    + {name} +
    +
    +
    + {price ? `${price / 100} ${currency}` : "Free"} +
    +
  • + ) +}) + +ShippingMethodItem.displayName = 'ShippingMethodItem' +export default ShippingMethodItem diff --git a/src/components/modules/checkout/CheckoutPage/CheckoutPage.module.scss b/src/components/modules/checkout/CheckoutPage/CheckoutPage.module.scss index ec7b19dc2..bd519be01 100644 --- a/src/components/modules/checkout/CheckoutPage/CheckoutPage.module.scss +++ b/src/components/modules/checkout/CheckoutPage/CheckoutPage.module.scss @@ -1,6 +1,7 @@ @import "../../../../styles/utilities"; .warrper{ - @apply flex w-full h-full absolute; + @apply flex w-full; + min-height: 100%; .right { display: none; @screen lg { @@ -46,10 +47,7 @@ color:var(--text-base); } } - button{ - margin-top: 2rem; - width: 100%; - } + } } } diff --git a/src/components/modules/checkout/CheckoutPage/CheckoutPage.tsx b/src/components/modules/checkout/CheckoutPage/CheckoutPage.tsx index 8cfcc31f3..12e110a7c 100644 --- a/src/components/modules/checkout/CheckoutPage/CheckoutPage.tsx +++ b/src/components/modules/checkout/CheckoutPage/CheckoutPage.tsx @@ -1,5 +1,8 @@ import classNames from 'classnames' import React, { useState } from 'react' +import { MessageCommon } from 'src/components/common' +import { useMessage } from 'src/components/contexts' +import { useGetActiveOrderForCheckout } from 'src/components/hooks/order' import IconHide from 'src/components/icons/IconHide' import { CHECKOUT_BILL_DATA } from 'src/utils/demo-data' import { CheckoutBill, CheckoutInfo } from '..' @@ -7,26 +10,30 @@ import s from "./CheckoutPage.module.scss" interface CheckoutPageProps { } -const CheckoutPage = ({}: CheckoutPageProps) => { +const CheckoutPage = ({ }: CheckoutPageProps) => { + const { messages, removeMessage } = useMessage() const [isShow, setIsShow] = useState(false) + const { order } = useGetActiveOrderForCheckout() + const onClose = () => { setIsShow(false) } - const onViewCart =() => { + const onViewCart = () => { setIsShow(true) } return (
    -
    -
    -
    + +
    +
    +

    Your Cart({CHECKOUT_BILL_DATA.length})

    -
    +
    - +
    diff --git a/src/styles/_utilities.scss b/src/styles/_utilities.scss index af566560a..570c1069d 100644 --- a/src/styles/_utilities.scss +++ b/src/styles/_utilities.scss @@ -201,7 +201,7 @@ } } .line { - @apply flex justify-between items-center; + @apply flex justify-between items-start; > div { flex: 1; &:not(:last-child) { diff --git a/src/utils/fetcher.ts b/src/utils/fetcher.ts index a86367398..a0c5222cc 100644 --- a/src/utils/fetcher.ts +++ b/src/utils/fetcher.ts @@ -1,4 +1,4 @@ -import { request } from 'graphql-request' +import { GraphQLClient } from 'graphql-request' import { RequestDocument, Variables } from 'graphql-request/dist/types' import { LOCAL_STORAGE_KEY } from './constanst.utils' @@ -12,11 +12,15 @@ interface QueryOptions { const fetcher = async (options: QueryOptions): Promise => { const { query, variables } = options const token = localStorage.getItem(LOCAL_STORAGE_KEY.TOKEN) - const res = await request( - process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL as string, + const graphQLClient = new GraphQLClient(process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL as string, { + credentials: 'include', + mode: 'cors', + headers: token ? { Authorization: 'Bearer ' + token } : {}, + }) + + const res = await graphQLClient.request( query, variables, - token ? { Authorization: 'Bearer ' + token } : {} ) return res diff --git a/src/utils/funtion.utils.ts b/src/utils/funtion.utils.ts index 25c394487..f5f1cb146 100644 --- a/src/utils/funtion.utils.ts +++ b/src/utils/funtion.utils.ts @@ -1,6 +1,6 @@ import { Collection } from '@commerce/types/collection'; import { Facet } from "@commerce/types/facet"; -import { Product, ProductCard, ProductOption, ProductOptionValues } from "@commerce/types/product"; +import { Product, ProductCard, ProductOptionValues } from "@commerce/types/product"; import { FacetValue, SearchResultSortParameter } from './../../framework/vendure/schema.d'; import { CODE_FACET_DISCOUNT, CODE_FACET_FEATURED, CODE_FACET_FEATURED_VARIANT, FACET, PRODUCT_SORT_OPTION_VALUE } from "./constanst.utils"; import { PromiseWithKey, SelectedOptions, SortOrder } from "./types.utils"; diff --git a/src/utils/language.utils.ts b/src/utils/language.utils.ts index 921c29435..7a8abd6b4 100644 --- a/src/utils/language.utils.ts +++ b/src/utils/language.utils.ts @@ -5,12 +5,16 @@ export const LANGUAGE = { CONFIRM:'Confirm', ADD_TO_CARD: 'Add to Cart', PREORDER: 'Pre-Order Now', - SIGNIN :'Sign In' + SIGNIN :'Sign In', + CANCEL: 'Cancel', }, PLACE_HOLDER: { SEARCH: 'Search', }, MESSAGE: { - ERROR: 'Something went wrong! Please try again!' + ERROR: 'Something went wrong! Please try again!', + INVALID_EMAIL: 'Your email was wrong', + REQUIRED: 'Required', + } } \ No newline at end of file diff --git a/src/utils/rawFetcher.ts b/src/utils/rawFetcher.ts index c26b2ab20..6c87dc4a9 100644 --- a/src/utils/rawFetcher.ts +++ b/src/utils/rawFetcher.ts @@ -1,7 +1,6 @@ -import { rawRequest } from 'graphql-request' +import { GraphQLClient } from 'graphql-request' import { RequestDocument, Variables } from 'graphql-request/dist/types' import { LOCAL_STORAGE_KEY } from './constanst.utils' - interface QueryOptions { query: RequestDocument variables?: Variables @@ -16,15 +15,20 @@ const rawFetcher = ({ }: QueryOptions): Promise<{ data: T; headers: any }> => { onLoad(true) const token = localStorage.getItem(LOCAL_STORAGE_KEY.TOKEN) - return rawRequest( - process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL as string, + + const graphQLClient = new GraphQLClient(process.env.NEXT_PUBLIC_VENDURE_SHOP_API_URL as string, { + credentials: 'include', + mode: 'cors', + headers: token ? { Authorization: 'Bearer ' + token } : {}, + }) + + return graphQLClient.rawRequest( query as string, variables, - token ? { Authorization: 'Bearer ' + token } : {} - ) - .then(({ data, headers }) => { - return { data, headers } - }) + ) + .then(({ data, headers }) => { + return { data, headers } + }) .finally(() => onLoad(false)) } diff --git a/src/utils/types.utils.ts b/src/utils/types.utils.ts index 52b890ee8..e14a7eb59 100644 --- a/src/utils/types.utils.ts +++ b/src/utils/types.utils.ts @@ -33,18 +33,6 @@ export interface BlogProps { imageSrc: string | null, } -export interface CheckOutForm { - name?: string - email?: string - address?: string - city?: string - state?: string - code?: number - phone?: number - method?: string - shipping_fee?: number -} - export type MouseAndTouchEvent = MouseEvent | TouchEvent export enum SortOrder { @@ -72,4 +60,18 @@ export type PromiseWithKey = { keyResult?: string, } -export type SelectedOptions = Record \ No newline at end of file +// ref https://www.vendure.io/docs/typescript-api/orders/order-state/ +export type OrderState = | 'Created' + | 'AddingItems' + | 'ArrangingPayment' + | 'PaymentAuthorized' + | 'PaymentSettled' + | 'PartiallyShipped' + | 'Shipped' + | 'PartiallyDelivered' + | 'Delivered' + | 'Modifying' + | 'ArrangingAdditionalPayment' + | 'Cancelled' + +export type SelectedOptions = Record