Back cart with GraphQL storage

This commit is contained in:
Ruslan Bakiev
2026-04-04 09:08:51 +07:00
parent f1ee0850c9
commit 264b88bcee
10 changed files with 644 additions and 68 deletions

View File

@@ -62,6 +62,30 @@ export type BonusTransaction = {
userId: Scalars['ID']['output']; userId: Scalars['ID']['output'];
}; };
export type Cart = {
__typename?: 'Cart';
createdAt: Scalars['DateTime']['output'];
deliveryAddress?: Maybe<DeliveryAddress>;
deliveryAddressId?: Maybe<Scalars['ID']['output']>;
id: Scalars['ID']['output'];
items: Array<CartItem>;
updatedAt: Scalars['DateTime']['output'];
userId: Scalars['ID']['output'];
};
export type CartItem = {
__typename?: 'CartItem';
createdAt: Scalars['DateTime']['output'];
id: Scalars['ID']['output'];
isCustomizable: Scalars['Boolean']['output'];
parameters: Scalars['JSON']['output'];
productId: Scalars['ID']['output'];
productName: Scalars['String']['output'];
quantity: Scalars['Float']['output'];
sku: Scalars['String']['output'];
updatedAt: Scalars['DateTime']['output'];
};
export type Company = { export type Company = {
__typename?: 'Company'; __typename?: 'Company';
id: Scalars['ID']['output']; id: Scalars['ID']['output'];
@@ -180,7 +204,9 @@ export type Mutation = {
__typename?: 'Mutation'; __typename?: 'Mutation';
acceptInvitation: User; acceptInvitation: User;
addBonusTransaction: BonusTransaction; addBonusTransaction: BonusTransaction;
addProductToCart: Cart;
blockOrder: Order; blockOrder: Order;
clearCart: Cart;
clientReviewOrder: Order; clientReviewOrder: Order;
completeOrder: Order; completeOrder: Order;
connectMessenger: MessengerConnection; connectMessenger: MessengerConnection;
@@ -192,15 +218,18 @@ export type Mutation = {
managerFinalizeOrder: Order; managerFinalizeOrder: Order;
managerSetOrderOffer: Order; managerSetOrderOffer: Order;
registerSelf: RegistrationRequest; registerSelf: RegistrationRequest;
removeCartItem: Cart;
requestLoginCode: AuthCodeRequestResult; requestLoginCode: AuthCodeRequestResult;
requestRewardWithdrawal: RewardWithdrawalRequest; requestRewardWithdrawal: RewardWithdrawalRequest;
reviewRegistrationRequest: RegistrationRequest; reviewRegistrationRequest: RegistrationRequest;
reviewRewardWithdrawal: RewardWithdrawalRequest; reviewRewardWithdrawal: RewardWithdrawalRequest;
sendTestMessengerMessage: MessengerDispatchResult; sendTestMessengerMessage: MessengerDispatchResult;
setCartDeliveryAddress: Cart;
setMyDefaultDeliveryAddress: DeliveryAddress; setMyDefaultDeliveryAddress: DeliveryAddress;
startOrderWork: Order; startOrderWork: Order;
submitCalculationOrder: Order; submitCalculationOrder: Order;
submitReadyOrder: Order; submitReadyOrder: Order;
updateCartItemQuantity: Cart;
upsertMyCounterpartyProfile: CounterpartyProfile; upsertMyCounterpartyProfile: CounterpartyProfile;
verifyLoginCode: AuthSession; verifyLoginCode: AuthSession;
}; };
@@ -216,6 +245,11 @@ export type MutationAddBonusTransactionArgs = {
}; };
export type MutationAddProductToCartArgs = {
productId: Scalars['ID']['input'];
};
export type MutationBlockOrderArgs = { export type MutationBlockOrderArgs = {
input: BlockOrderInput; input: BlockOrderInput;
}; };
@@ -278,6 +312,11 @@ export type MutationRegisterSelfArgs = {
}; };
export type MutationRemoveCartItemArgs = {
productId: Scalars['ID']['input'];
};
export type MutationRequestLoginCodeArgs = { export type MutationRequestLoginCodeArgs = {
input: RequestLoginCodeInput; input: RequestLoginCodeInput;
}; };
@@ -305,6 +344,11 @@ export type MutationSendTestMessengerMessageArgs = {
}; };
export type MutationSetCartDeliveryAddressArgs = {
addressId?: InputMaybe<Scalars['ID']['input']>;
};
export type MutationSetMyDefaultDeliveryAddressArgs = { export type MutationSetMyDefaultDeliveryAddressArgs = {
addressId: Scalars['ID']['input']; addressId: Scalars['ID']['input'];
}; };
@@ -325,6 +369,11 @@ export type MutationSubmitReadyOrderArgs = {
}; };
export type MutationUpdateCartItemQuantityArgs = {
input: UpdateCartItemQuantityInput;
};
export type MutationUpsertMyCounterpartyProfileArgs = { export type MutationUpsertMyCounterpartyProfileArgs = {
input: UpsertMyCounterpartyProfileInput; input: UpsertMyCounterpartyProfileInput;
}; };
@@ -430,6 +479,7 @@ export type Query = {
managerNotificationHistory: Array<NotificationHistoryItem>; managerNotificationHistory: Array<NotificationHistoryItem>;
managerOrders: Array<Order>; managerOrders: Array<Order>;
me?: Maybe<User>; me?: Maybe<User>;
myCart: Cart;
myCounterpartyProfile?: Maybe<CounterpartyProfile>; myCounterpartyProfile?: Maybe<CounterpartyProfile>;
myCurrentOrders: Array<Order>; myCurrentOrders: Array<Order>;
myDeliveryAddresses: Array<DeliveryAddress>; myDeliveryAddresses: Array<DeliveryAddress>;
@@ -564,6 +614,11 @@ export type SubmitReadyOrderInput = {
items: Array<ReadyOrderItemInput>; items: Array<ReadyOrderItemInput>;
}; };
export type UpdateCartItemQuantityInput = {
productId: Scalars['ID']['input'];
quantity: Scalars['Float']['input'];
};
export type UpsertMyCounterpartyProfileInput = { export type UpsertMyCounterpartyProfileInput = {
bankName: Scalars['String']['input']; bankName: Scalars['String']['input'];
bik: Scalars['String']['input']; bik: Scalars['String']['input'];
@@ -645,6 +700,44 @@ export type VerifyLoginCodeMutationVariables = Exact<{
export type VerifyLoginCodeMutation = { __typename?: 'Mutation', verifyLoginCode: { __typename?: 'AuthSession', accessToken: string, expiresAt: any, user: { __typename?: 'User', id: string, email: string, fullName: string, role: UserRole, company?: { __typename?: 'Company', id: string } | null } } }; export type VerifyLoginCodeMutation = { __typename?: 'Mutation', verifyLoginCode: { __typename?: 'AuthSession', accessToken: string, expiresAt: any, user: { __typename?: 'User', id: string, email: string, fullName: string, role: UserRole, company?: { __typename?: 'Company', id: string } | null } } };
export type AddProductToCartMutationVariables = Exact<{
productId: Scalars['ID']['input'];
}>;
export type AddProductToCartMutation = { __typename?: 'Mutation', addProductToCart: { __typename?: 'Cart', id: string, userId: string, deliveryAddressId?: string | null, updatedAt: any, items: Array<{ __typename?: 'CartItem', id: string, productId: string, productName: string, sku: string, isCustomizable: boolean, quantity: number, parameters: any, updatedAt: any }> } };
export type ClearCartMutationVariables = Exact<{ [key: string]: never; }>;
export type ClearCartMutation = { __typename?: 'Mutation', clearCart: { __typename?: 'Cart', id: string, userId: string, deliveryAddressId?: string | null, updatedAt: any, items: Array<{ __typename?: 'CartItem', id: string, productId: string, productName: string, sku: string, isCustomizable: boolean, quantity: number, parameters: any, updatedAt: any }> } };
export type MyCartQueryVariables = Exact<{ [key: string]: never; }>;
export type MyCartQuery = { __typename?: 'Query', myCart: { __typename?: 'Cart', id: string, userId: string, deliveryAddressId?: string | null, updatedAt: any, items: Array<{ __typename?: 'CartItem', id: string, productId: string, productName: string, sku: string, isCustomizable: boolean, quantity: number, parameters: any, updatedAt: any }> } };
export type RemoveCartItemMutationVariables = Exact<{
productId: Scalars['ID']['input'];
}>;
export type RemoveCartItemMutation = { __typename?: 'Mutation', removeCartItem: { __typename?: 'Cart', id: string, userId: string, deliveryAddressId?: string | null, updatedAt: any, items: Array<{ __typename?: 'CartItem', id: string, productId: string, productName: string, sku: string, isCustomizable: boolean, quantity: number, parameters: any, updatedAt: any }> } };
export type SetCartDeliveryAddressMutationVariables = Exact<{
addressId?: InputMaybe<Scalars['ID']['input']>;
}>;
export type SetCartDeliveryAddressMutation = { __typename?: 'Mutation', setCartDeliveryAddress: { __typename?: 'Cart', id: string, userId: string, deliveryAddressId?: string | null, updatedAt: any, items: Array<{ __typename?: 'CartItem', id: string, productId: string, productName: string, sku: string, isCustomizable: boolean, quantity: number, parameters: any, updatedAt: any }> } };
export type UpdateCartItemQuantityMutationVariables = Exact<{
input: UpdateCartItemQuantityInput;
}>;
export type UpdateCartItemQuantityMutation = { __typename?: 'Mutation', updateCartItemQuantity: { __typename?: 'Cart', id: string, userId: string, deliveryAddressId?: string | null, updatedAt: any, items: Array<{ __typename?: 'CartItem', id: string, productId: string, productName: string, sku: string, isCustomizable: boolean, quantity: number, parameters: any, updatedAt: any }> } };
export type ClientProductsQueryVariables = Exact<{ [key: string]: never; }>; export type ClientProductsQueryVariables = Exact<{ [key: string]: never; }>;
@@ -1015,6 +1108,252 @@ export function useVerifyLoginCodeMutation(options: VueApolloComposable.UseMutat
return VueApolloComposable.useMutation<VerifyLoginCodeMutation, VerifyLoginCodeMutationVariables>(VerifyLoginCodeDocument, options); return VueApolloComposable.useMutation<VerifyLoginCodeMutation, VerifyLoginCodeMutationVariables>(VerifyLoginCodeDocument, options);
} }
export type VerifyLoginCodeMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<VerifyLoginCodeMutation, VerifyLoginCodeMutationVariables>; export type VerifyLoginCodeMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<VerifyLoginCodeMutation, VerifyLoginCodeMutationVariables>;
export const AddProductToCartDocument = gql`
mutation AddProductToCart($productId: ID!) {
addProductToCart(productId: $productId) {
id
userId
deliveryAddressId
items {
id
productId
productName
sku
isCustomizable
quantity
parameters
updatedAt
}
updatedAt
}
}
`;
/**
* __useAddProductToCartMutation__
*
* To run a mutation, you first call `useAddProductToCartMutation` within a Vue component and pass it any options that fit your needs.
* When your component renders, `useAddProductToCartMutation` returns an object that includes:
* - A mutate function that you can call at any time to execute the mutation
* - Several other properties: https://v4.apollo.vuejs.org/api/use-mutation.html#return
*
* @param options that will be passed into the mutation, supported options are listed on: https://v4.apollo.vuejs.org/guide-composable/mutation.html#options;
*
* @example
* const { mutate, loading, error, onDone } = useAddProductToCartMutation({
* variables: {
* productId: // value for 'productId'
* },
* });
*/
export function useAddProductToCartMutation(options: VueApolloComposable.UseMutationOptions<AddProductToCartMutation, AddProductToCartMutationVariables> | ReactiveFunction<VueApolloComposable.UseMutationOptions<AddProductToCartMutation, AddProductToCartMutationVariables>> = {}) {
return VueApolloComposable.useMutation<AddProductToCartMutation, AddProductToCartMutationVariables>(AddProductToCartDocument, options);
}
export type AddProductToCartMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<AddProductToCartMutation, AddProductToCartMutationVariables>;
export const ClearCartDocument = gql`
mutation ClearCart {
clearCart {
id
userId
deliveryAddressId
items {
id
productId
productName
sku
isCustomizable
quantity
parameters
updatedAt
}
updatedAt
}
}
`;
/**
* __useClearCartMutation__
*
* To run a mutation, you first call `useClearCartMutation` within a Vue component and pass it any options that fit your needs.
* When your component renders, `useClearCartMutation` returns an object that includes:
* - A mutate function that you can call at any time to execute the mutation
* - Several other properties: https://v4.apollo.vuejs.org/api/use-mutation.html#return
*
* @param options that will be passed into the mutation, supported options are listed on: https://v4.apollo.vuejs.org/guide-composable/mutation.html#options;
*
* @example
* const { mutate, loading, error, onDone } = useClearCartMutation();
*/
export function useClearCartMutation(options: VueApolloComposable.UseMutationOptions<ClearCartMutation, ClearCartMutationVariables> | ReactiveFunction<VueApolloComposable.UseMutationOptions<ClearCartMutation, ClearCartMutationVariables>> = {}) {
return VueApolloComposable.useMutation<ClearCartMutation, ClearCartMutationVariables>(ClearCartDocument, options);
}
export type ClearCartMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<ClearCartMutation, ClearCartMutationVariables>;
export const MyCartDocument = gql`
query MyCart {
myCart {
id
userId
deliveryAddressId
items {
id
productId
productName
sku
isCustomizable
quantity
parameters
updatedAt
}
updatedAt
}
}
`;
/**
* __useMyCartQuery__
*
* To run a query within a Vue component, call `useMyCartQuery` and pass it any options that fit your needs.
* When your component renders, `useMyCartQuery` returns an object from Apollo Client that contains result, loading and error properties
* you can use to render your UI.
*
* @param options that will be passed into the query, supported options are listed on: https://v4.apollo.vuejs.org/guide-composable/query.html#options;
*
* @example
* const { result, loading, error } = useMyCartQuery();
*/
export function useMyCartQuery(options: VueApolloComposable.UseQueryOptions<MyCartQuery, MyCartQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<MyCartQuery, MyCartQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<MyCartQuery, MyCartQueryVariables>> = {}) {
return VueApolloComposable.useQuery<MyCartQuery, MyCartQueryVariables>(MyCartDocument, {}, options);
}
export function useMyCartLazyQuery(options: VueApolloComposable.UseQueryOptions<MyCartQuery, MyCartQueryVariables> | VueCompositionApi.Ref<VueApolloComposable.UseQueryOptions<MyCartQuery, MyCartQueryVariables>> | ReactiveFunction<VueApolloComposable.UseQueryOptions<MyCartQuery, MyCartQueryVariables>> = {}) {
return VueApolloComposable.useLazyQuery<MyCartQuery, MyCartQueryVariables>(MyCartDocument, {}, options);
}
export type MyCartQueryCompositionFunctionResult = VueApolloComposable.UseQueryReturn<MyCartQuery, MyCartQueryVariables>;
export const RemoveCartItemDocument = gql`
mutation RemoveCartItem($productId: ID!) {
removeCartItem(productId: $productId) {
id
userId
deliveryAddressId
items {
id
productId
productName
sku
isCustomizable
quantity
parameters
updatedAt
}
updatedAt
}
}
`;
/**
* __useRemoveCartItemMutation__
*
* To run a mutation, you first call `useRemoveCartItemMutation` within a Vue component and pass it any options that fit your needs.
* When your component renders, `useRemoveCartItemMutation` returns an object that includes:
* - A mutate function that you can call at any time to execute the mutation
* - Several other properties: https://v4.apollo.vuejs.org/api/use-mutation.html#return
*
* @param options that will be passed into the mutation, supported options are listed on: https://v4.apollo.vuejs.org/guide-composable/mutation.html#options;
*
* @example
* const { mutate, loading, error, onDone } = useRemoveCartItemMutation({
* variables: {
* productId: // value for 'productId'
* },
* });
*/
export function useRemoveCartItemMutation(options: VueApolloComposable.UseMutationOptions<RemoveCartItemMutation, RemoveCartItemMutationVariables> | ReactiveFunction<VueApolloComposable.UseMutationOptions<RemoveCartItemMutation, RemoveCartItemMutationVariables>> = {}) {
return VueApolloComposable.useMutation<RemoveCartItemMutation, RemoveCartItemMutationVariables>(RemoveCartItemDocument, options);
}
export type RemoveCartItemMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<RemoveCartItemMutation, RemoveCartItemMutationVariables>;
export const SetCartDeliveryAddressDocument = gql`
mutation SetCartDeliveryAddress($addressId: ID) {
setCartDeliveryAddress(addressId: $addressId) {
id
userId
deliveryAddressId
items {
id
productId
productName
sku
isCustomizable
quantity
parameters
updatedAt
}
updatedAt
}
}
`;
/**
* __useSetCartDeliveryAddressMutation__
*
* To run a mutation, you first call `useSetCartDeliveryAddressMutation` within a Vue component and pass it any options that fit your needs.
* When your component renders, `useSetCartDeliveryAddressMutation` returns an object that includes:
* - A mutate function that you can call at any time to execute the mutation
* - Several other properties: https://v4.apollo.vuejs.org/api/use-mutation.html#return
*
* @param options that will be passed into the mutation, supported options are listed on: https://v4.apollo.vuejs.org/guide-composable/mutation.html#options;
*
* @example
* const { mutate, loading, error, onDone } = useSetCartDeliveryAddressMutation({
* variables: {
* addressId: // value for 'addressId'
* },
* });
*/
export function useSetCartDeliveryAddressMutation(options: VueApolloComposable.UseMutationOptions<SetCartDeliveryAddressMutation, SetCartDeliveryAddressMutationVariables> | ReactiveFunction<VueApolloComposable.UseMutationOptions<SetCartDeliveryAddressMutation, SetCartDeliveryAddressMutationVariables>> = {}) {
return VueApolloComposable.useMutation<SetCartDeliveryAddressMutation, SetCartDeliveryAddressMutationVariables>(SetCartDeliveryAddressDocument, options);
}
export type SetCartDeliveryAddressMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<SetCartDeliveryAddressMutation, SetCartDeliveryAddressMutationVariables>;
export const UpdateCartItemQuantityDocument = gql`
mutation UpdateCartItemQuantity($input: UpdateCartItemQuantityInput!) {
updateCartItemQuantity(input: $input) {
id
userId
deliveryAddressId
items {
id
productId
productName
sku
isCustomizable
quantity
parameters
updatedAt
}
updatedAt
}
}
`;
/**
* __useUpdateCartItemQuantityMutation__
*
* To run a mutation, you first call `useUpdateCartItemQuantityMutation` within a Vue component and pass it any options that fit your needs.
* When your component renders, `useUpdateCartItemQuantityMutation` returns an object that includes:
* - A mutate function that you can call at any time to execute the mutation
* - Several other properties: https://v4.apollo.vuejs.org/api/use-mutation.html#return
*
* @param options that will be passed into the mutation, supported options are listed on: https://v4.apollo.vuejs.org/guide-composable/mutation.html#options;
*
* @example
* const { mutate, loading, error, onDone } = useUpdateCartItemQuantityMutation({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useUpdateCartItemQuantityMutation(options: VueApolloComposable.UseMutationOptions<UpdateCartItemQuantityMutation, UpdateCartItemQuantityMutationVariables> | ReactiveFunction<VueApolloComposable.UseMutationOptions<UpdateCartItemQuantityMutation, UpdateCartItemQuantityMutationVariables>> = {}) {
return VueApolloComposable.useMutation<UpdateCartItemQuantityMutation, UpdateCartItemQuantityMutationVariables>(UpdateCartItemQuantityDocument, options);
}
export type UpdateCartItemQuantityMutationCompositionFunctionResult = VueApolloComposable.UseMutationReturn<UpdateCartItemQuantityMutation, UpdateCartItemQuantityMutationVariables>;
export const ClientProductsDocument = gql` export const ClientProductsDocument = gql`
query ClientProducts { query ClientProducts {
clientProducts { clientProducts {

View File

@@ -1,14 +1,27 @@
import {
AddProductToCartDocument,
ClearCartDocument,
MyCartDocument,
RemoveCartItemDocument,
SetCartDeliveryAddressDocument,
UpdateCartItemQuantityDocument,
type MyCartQuery,
} from '~/composables/graphql/generated';
import { useGqlClient } from '~/composables/useGqlClient';
type CartParameters = {
width: number;
thickness: number;
color: string;
};
export type ClientCartItem = { export type ClientCartItem = {
productId: string; productId: string;
productName: string; productName: string;
sku: string; sku: string;
isCustomizable: boolean; isCustomizable: boolean;
quantity: number; quantity: number;
parameters: { parameters: CartParameters;
width: number;
thickness: number;
color: string;
};
}; };
function normalizeQuantity(value: number) { function normalizeQuantity(value: number) {
@@ -19,8 +32,33 @@ function normalizeQuantity(value: number) {
return Math.floor(value); return Math.floor(value);
} }
function normalizeParameters(value: unknown): CartParameters {
const source = (value && typeof value === 'object') ? value as Partial<CartParameters> : {};
return {
width: Number(source.width ?? 100),
thickness: Number(source.thickness ?? 50),
color: typeof source.color === 'string' ? source.color : 'прозрачный',
};
}
function mapCartItem(item: MyCartQuery['myCart']['items'][number]): ClientCartItem {
return {
productId: item.productId,
productName: item.productName,
sku: item.sku,
isCustomizable: item.isCustomizable,
quantity: Number(item.quantity),
parameters: normalizeParameters(item.parameters),
};
}
export function useClientCart() { export function useClientCart() {
const client = useGqlClient();
const items = useState<ClientCartItem[]>('client-cart-items', () => []); const items = useState<ClientCartItem[]>('client-cart-items', () => []);
const selectedDeliveryAddressId = useState<string>('client-cart-delivery-address-id', () => '');
const initialized = useState<boolean>('client-cart-initialized', () => false);
const loading = useState<boolean>('client-cart-loading', () => false);
const totalPositions = computed(() => items.value.length); const totalPositions = computed(() => items.value.length);
const totalItems = computed(() => items.value.reduce((sum, item) => sum + item.quantity, 0)); const totalItems = computed(() => items.value.reduce((sum, item) => sum + item.quantity, 0));
@@ -28,94 +66,138 @@ export function useClientCart() {
items.value.reduce((sum, item) => sum + item.quantity * item.parameters.width * item.parameters.thickness, 0), items.value.reduce((sum, item) => sum + item.quantity * item.parameters.width * item.parameters.thickness, 0),
); );
function applyCart(cart: MyCartQuery['myCart']) {
items.value = cart.items.map(mapCartItem);
selectedDeliveryAddressId.value = cart.deliveryAddressId ?? '';
initialized.value = true;
}
async function fetchCart(force = false) {
if (loading.value) {
return;
}
if (initialized.value && !force) {
return;
}
loading.value = true;
try {
const { data } = await client.query({
query: MyCartDocument,
fetchPolicy: 'network-only',
});
if (data?.myCart) {
applyCart(data.myCart);
}
} finally {
loading.value = false;
}
}
function getQuantity(productId: string) { function getQuantity(productId: string) {
return items.value.find((item) => item.productId === productId)?.quantity ?? 0; return items.value.find((item) => item.productId === productId)?.quantity ?? 0;
} }
function addProduct(product: { async function addProduct(product: {
id: string; id: string;
name: string; name: string;
sku: string; sku: string;
isCustomizable: boolean; isCustomizable: boolean;
}) { }) {
const existing = items.value.find((item) => item.productId === product.id); const { data } = await client.mutate({
if (existing) { mutation: AddProductToCartDocument,
existing.quantity += 1; variables: { productId: product.id },
return; fetchPolicy: 'no-cache',
}
items.value.push({
productId: product.id,
productName: product.name,
sku: product.sku,
isCustomizable: product.isCustomizable,
quantity: 1,
parameters: {
width: 100,
thickness: 50,
color: 'прозрачный',
},
}); });
if (data?.addProductToCart) {
applyCart(data.addProductToCart);
}
} }
function setQuantity(productId: string, quantity: number) { async function setQuantity(productId: string, quantity: number) {
const existing = items.value.find((item) => item.productId === productId);
if (!existing) {
return;
}
const normalizedQuantity = normalizeQuantity(quantity); const normalizedQuantity = normalizeQuantity(quantity);
if (normalizedQuantity === 0) { const { data } = await client.mutate({
removeProduct(productId); mutation: UpdateCartItemQuantityDocument,
return; variables: {
} input: {
productId,
quantity: normalizedQuantity,
},
},
fetchPolicy: 'no-cache',
});
existing.quantity = normalizedQuantity; if (data?.updateCartItemQuantity) {
applyCart(data.updateCartItemQuantity);
}
} }
function incrementQuantity(productId: string) { async function incrementQuantity(productId: string) {
const existing = items.value.find((item) => item.productId === productId); await setQuantity(productId, getQuantity(productId) + 1);
if (!existing) {
return;
}
existing.quantity += 1;
} }
function decrementQuantity(productId: string) { async function decrementQuantity(productId: string) {
const existing = items.value.find((item) => item.productId === productId); await setQuantity(productId, getQuantity(productId) - 1);
if (!existing) {
return;
}
const normalizedQuantity = normalizeQuantity(existing.quantity - 1);
if (normalizedQuantity === 0) {
removeProduct(productId);
return;
}
existing.quantity = normalizedQuantity;
} }
function removeProduct(productId: string) { async function removeProduct(productId: string) {
items.value = items.value.filter((item) => item.productId !== productId); const { data } = await client.mutate({
mutation: RemoveCartItemDocument,
variables: { productId },
fetchPolicy: 'no-cache',
});
if (data?.removeCartItem) {
applyCart(data.removeCartItem);
}
} }
function clearCart() { async function setDeliveryAddress(addressId: string | null) {
items.value = []; const { data } = await client.mutate({
mutation: SetCartDeliveryAddressDocument,
variables: { addressId: addressId || null },
fetchPolicy: 'no-cache',
});
if (data?.setCartDeliveryAddress) {
applyCart(data.setCartDeliveryAddress);
}
}
async function clearCart() {
const { data } = await client.mutate({
mutation: ClearCartDocument,
fetchPolicy: 'no-cache',
});
if (data?.clearCart) {
applyCart(data.clearCart);
}
}
if (!initialized.value && !loading.value) {
void fetchCart();
} }
return { return {
items, items,
loading,
selectedDeliveryAddressId,
totalPositions, totalPositions,
totalItems, totalItems,
totalVolume, totalVolume,
fetchCart,
addProduct, addProduct,
setQuantity, setQuantity,
incrementQuantity, incrementQuantity,
decrementQuantity, decrementQuantity,
removeProduct, removeProduct,
clearCart, clearCart,
setDeliveryAddress,
getQuantity, getQuantity,
}; };
} }

View File

@@ -16,6 +16,8 @@ const deliveryAddressesQuery = useQuery(MyDeliveryAddressesDocument);
const { const {
items: cartItems, items: cartItems,
fetchCart,
selectedDeliveryAddressId,
totalPositions, totalPositions,
totalItems, totalItems,
totalVolume, totalVolume,
@@ -23,9 +25,9 @@ const {
decrementQuantity, decrementQuantity,
removeProduct, removeProduct,
clearCart, clearCart,
setDeliveryAddress,
} = useClientCart(); } = useClientCart();
const selectedDeliveryAddressId = ref('');
const sending = ref(false); const sending = ref(false);
const success = ref(''); const success = ref('');
const errorMessage = ref(''); const errorMessage = ref('');
@@ -35,9 +37,11 @@ const hasDeliveryAddresses = computed(() => deliveryAddresses.value.length > 0);
watch( watch(
deliveryAddresses, deliveryAddresses,
(addresses) => { async (addresses) => {
if (addresses.length < 1) { if (addresses.length < 1) {
selectedDeliveryAddressId.value = ''; if (selectedDeliveryAddressId.value) {
await setDeliveryAddress(null);
}
return; return;
} }
@@ -47,11 +51,15 @@ watch(
} }
const defaultAddress = addresses.find((address) => address.isDefault); const defaultAddress = addresses.find((address) => address.isDefault);
selectedDeliveryAddressId.value = defaultAddress?.id || addresses[0]?.id || ''; await setDeliveryAddress(defaultAddress?.id || addresses[0]?.id || null);
}, },
{ immediate: true }, { immediate: true },
); );
onMounted(() => {
void fetchCart(true);
});
function lineVolume(productId: string) { function lineVolume(productId: string) {
const item = cartItems.value.find((entry) => entry.productId === productId); const item = cartItems.value.find((entry) => entry.productId === productId);
if (!item) { if (!item) {
@@ -64,19 +72,25 @@ function lineVolume(productId: string) {
function increment(productId: string) { function increment(productId: string) {
success.value = ''; success.value = '';
errorMessage.value = ''; errorMessage.value = '';
incrementQuantity(productId); void incrementQuantity(productId);
} }
function decrement(productId: string) { function decrement(productId: string) {
success.value = ''; success.value = '';
errorMessage.value = ''; errorMessage.value = '';
decrementQuantity(productId); void decrementQuantity(productId);
} }
function removeFromCart(productId: string) { function removeFromCart(productId: string) {
success.value = ''; success.value = '';
errorMessage.value = ''; errorMessage.value = '';
removeProduct(productId); void removeProduct(productId);
}
function selectDeliveryAddress(addressId: string) {
success.value = '';
errorMessage.value = '';
void setDeliveryAddress(addressId);
} }
async function submitCart() { async function submitCart() {
@@ -126,7 +140,7 @@ async function submitCart() {
} }
sending.value = false; sending.value = false;
clearCart(); await clearCart();
success.value = `Отправлено заявок: ${createdCodes.length}. Статус: уточнение цены.`; success.value = `Отправлено заявок: ${createdCodes.length}. Статус: уточнение цены.`;
} }
</script> </script>
@@ -160,11 +174,11 @@ async function submitCart() {
class="flex cursor-pointer items-start gap-3 rounded-2xl bg-white p-3 transition hover:shadow-md" class="flex cursor-pointer items-start gap-3 rounded-2xl bg-white p-3 transition hover:shadow-md"
> >
<input <input
v-model="selectedDeliveryAddressId"
type="radio" type="radio"
name="delivery-address" name="delivery-address"
class="radio radio-success mt-1" class="radio radio-success mt-1"
:value="address.id" :checked="selectedDeliveryAddressId === address.id"
@change="selectDeliveryAddress(address.id)"
> >
<span> <span>
<span class="block font-semibold text-[#123824]"> <span class="block font-semibold text-[#123824]">

View File

@@ -0,0 +1,18 @@
mutation AddProductToCart($productId: ID!) {
addProductToCart(productId: $productId) {
id
userId
deliveryAddressId
items {
id
productId
productName
sku
isCustomizable
quantity
parameters
updatedAt
}
updatedAt
}
}

View File

@@ -0,0 +1,18 @@
mutation ClearCart {
clearCart {
id
userId
deliveryAddressId
items {
id
productId
productName
sku
isCustomizable
quantity
parameters
updatedAt
}
updatedAt
}
}

View File

@@ -0,0 +1,18 @@
query MyCart {
myCart {
id
userId
deliveryAddressId
items {
id
productId
productName
sku
isCustomizable
quantity
parameters
updatedAt
}
updatedAt
}
}

View File

@@ -0,0 +1,18 @@
mutation RemoveCartItem($productId: ID!) {
removeCartItem(productId: $productId) {
id
userId
deliveryAddressId
items {
id
productId
productName
sku
isCustomizable
quantity
parameters
updatedAt
}
updatedAt
}
}

View File

@@ -0,0 +1,18 @@
mutation SetCartDeliveryAddress($addressId: ID) {
setCartDeliveryAddress(addressId: $addressId) {
id
userId
deliveryAddressId
items {
id
productId
productName
sku
isCustomizable
quantity
parameters
updatedAt
}
updatedAt
}
}

View File

@@ -0,0 +1,18 @@
mutation UpdateCartItemQuantity($input: UpdateCartItemQuantityInput!) {
updateCartItemQuantity(input: $input) {
id
userId
deliveryAddressId
items {
id
productId
productName
sku
isCustomizable
quantity
parameters
updatedAt
}
updatedAt
}
}

View File

@@ -191,6 +191,28 @@ type Product {
availableInWarehouses: [ProductWarehouseBalance!]! availableInWarehouses: [ProductWarehouseBalance!]!
} }
type CartItem {
id: ID!
productId: ID!
productName: String!
sku: String!
isCustomizable: Boolean!
quantity: Float!
parameters: JSON!
createdAt: DateTime!
updatedAt: DateTime!
}
type Cart {
id: ID!
userId: ID!
deliveryAddressId: ID
deliveryAddress: DeliveryAddress
items: [CartItem!]!
createdAt: DateTime!
updatedAt: DateTime!
}
type OrderItem { type OrderItem {
id: ID! id: ID!
productId: ID productId: ID
@@ -266,6 +288,7 @@ type Query {
healthcheck: String! healthcheck: String!
me: User me: User
myCounterpartyProfile: CounterpartyProfile myCounterpartyProfile: CounterpartyProfile
myCart: Cart!
myDeliveryAddresses: [DeliveryAddress!]! myDeliveryAddresses: [DeliveryAddress!]!
myMessengerConnections: [MessengerConnection!]! myMessengerConnections: [MessengerConnection!]!
myNotificationHistory(channel: MessengerType!, limit: Int = 50): [NotificationHistoryItem!]! myNotificationHistory(channel: MessengerType!, limit: Int = 50): [NotificationHistoryItem!]!
@@ -340,6 +363,11 @@ input CreateMyDeliveryAddressInput {
fiasId: String fiasId: String
} }
input UpdateCartItemQuantityInput {
productId: ID!
quantity: Float!
}
input ReadyOrderItemInput { input ReadyOrderItemInput {
productId: ID! productId: ID!
quantity: Float! quantity: Float!
@@ -400,6 +428,11 @@ type Mutation {
acceptInvitation(input: AcceptInvitationInput!): User! acceptInvitation(input: AcceptInvitationInput!): User!
connectMessenger(input: ConnectMessengerInput!): MessengerConnection! connectMessenger(input: ConnectMessengerInput!): MessengerConnection!
upsertMyCounterpartyProfile(input: UpsertMyCounterpartyProfileInput!): CounterpartyProfile! upsertMyCounterpartyProfile(input: UpsertMyCounterpartyProfileInput!): CounterpartyProfile!
addProductToCart(productId: ID!): Cart!
updateCartItemQuantity(input: UpdateCartItemQuantityInput!): Cart!
removeCartItem(productId: ID!): Cart!
setCartDeliveryAddress(addressId: ID): Cart!
clearCart: Cart!
createMyDeliveryAddress(input: CreateMyDeliveryAddressInput!): DeliveryAddress! createMyDeliveryAddress(input: CreateMyDeliveryAddressInput!): DeliveryAddress!
setMyDefaultDeliveryAddress(addressId: ID!): DeliveryAddress! setMyDefaultDeliveryAddress(addressId: ID!): DeliveryAddress!
deleteMyDeliveryAddress(addressId: ID!): Boolean! deleteMyDeliveryAddress(addressId: ID!): Boolean!