Browse Source

feat(admin-ui): Create product, generate default variant

Michael Bromley 7 years ago
parent
commit
3389459ae4

+ 88 - 61
admin-ui/src/app/catalog/components/product-detail/product-detail.component.html

@@ -18,13 +18,17 @@
 
     <vdr-ab-right>
         <button class="btn btn-primary"
-                (click)="save()"
-                [disabled]="productForm.invalid || productForm.pristine">{{ 'common.update' | translate }}</button>
+                *ngIf="isNew$ | async; else updateButton"
+                (click)="create()"
+                [disabled]="productForm.invalid || productForm.pristine">{{ 'common.create' | translate }}</button>
+        <ng-template #updateButton>
+            <button class="btn btn-primary"
+                    (click)="save()"
+                    [disabled]="productForm.invalid || productForm.pristine">{{ 'common.update' | translate }}</button>
+        </ng-template>
     </vdr-ab-right>
 </vdr-action-bar>
 
-
-
 <form class="form" [formGroup]="productForm" >
     <section class="form-block" formGroupName="product">
         <label>{{ 'catalog.product' | translate }}</label>
@@ -37,67 +41,90 @@
         <vdr-form-field [label]="'catalog.description' | translate" for="description">
             <textarea id="description" formControlName="description"></textarea>
         </vdr-form-field>
-        <vdr-form-item [label]="'catalog.product-option-groups' | translate">
-            <div class="option-groups-list">
-                <div *ngFor="let optionGroup of (product$ | async)?.optionGroups"
-                     class="option-group">
-                    <div class="group-name">{{ optionGroup.name }}</div>
-                    <button type="button"
-                            class="btn remove-option-group"
-                            (click)="removeGroup(optionGroup.id)">
-                        <clr-icon shape="trash"></clr-icon>
-                    </button>
-                </div>
-                <clr-dropdown>
-                    <button type="button" class="btn btn-outline-primary btn-sm" clrDropdownTrigger>
-                        <clr-icon shape="add"></clr-icon>
-                        {{ 'catalog.add-option-group' | translate }}
-                        <clr-icon shape="caret down"></clr-icon>
-                    </button>
-                    <clr-dropdown-menu clrPosition="bottom-right" *clrIfOpen>
-                        <button type="button"
-                                clrDropdownItem
-                                (click)="addExistingOptionGroup()">{{ 'catalog.add-existing-option-group' | translate }}</button>
-                        <button type="button"
-                                clrDropdownItem
-                                (click)="createNewOptionGroup()">{{ 'catalog.create-new-option-group' | translate }}</button>
-                    </clr-dropdown-menu>
-                </clr-dropdown>
-            </div>
-        </vdr-form-item>
     </section>
 
-    <section class="form-block">
+    <section class="form-block" *ngIf="!(isNew$ | async)">
+
+
         <label>{{ 'catalog.product-variants' | translate }}</label>
 
-        <table class="variants-list table" formArrayName="variants">
-            <thead>
-            <tr>
-                <th>{{ 'catalog.product-variant-table-sku' | translate }}</th>
-                <th>{{ 'catalog.product-variant-table-name' | translate }}</th>
-                <th>{{ 'catalog.product-variant-table-options' | translate }}</th>
-                <th>{{ 'catalog.product-variant-table-price' | translate }}</th>
-            </tr>
-            </thead>
-            <tbody>
-            <tr class="variant"
-                *ngFor="let variant of variants$ | async; let i = index"
-                [formGroupName]="i">
-                <td>
-                    <input type="text" formControlName="sku">
-                </td>
-                <td>
-                    <input type="text" formControlName="name">
-                </td>
-                <td>
-                    <div *ngFor="let option of variant.options">{{ option.name }}</div>
-                </td>
-                <td>
-                    <input type="number" min="0" formControlName="price">
-                </td>
-            </tr>
-            </tbody>
-        </table>
+        <div *ngIf="(variants$ | async)?.length === 0; else variants">
+            <clr-dropdown>
+                <button type="button" class="btn btn-primary" clrDropdownTrigger>
+                    <clr-icon shape="add"></clr-icon>
+                    {{ 'catalog.generate-product-variants' | translate }}
+                    <clr-icon shape="caret down"></clr-icon>
+                </button>
+                <clr-dropdown-menu clrPosition="bottom-right" *clrIfOpen>
+                    <button type="button"
+                            clrDropdownItem
+                            (click)="generateProductVariants()">{{ 'catalog.generate-variants-default-only' | translate }}</button>
+                   <!-- <button type="button"
+                            clrDropdownItem
+                            (click)="createNewOptionGroup()">{{ 'catalog.generate-variants-with-options' | translate }}</button>-->
+                </clr-dropdown-menu>
+            </clr-dropdown>
+        </div>
+
+        <ng-template #variants>
+            <vdr-form-item [label]="'catalog.product-option-groups' | translate">
+                <div class="option-groups-list">
+                    <div *ngFor="let optionGroup of (product$ | async)?.optionGroups"
+                         class="option-group">
+                        <div class="group-name">{{ optionGroup.name }}</div>
+                        <button type="button"
+                                class="btn remove-option-group"
+                                (click)="removeGroup(optionGroup.id)">
+                            <clr-icon shape="trash"></clr-icon>
+                        </button>
+                    </div>
+                    <clr-dropdown>
+                        <button type="button" class="btn btn-outline-primary btn-sm" clrDropdownTrigger>
+                            <clr-icon shape="add"></clr-icon>
+                            {{ 'catalog.add-option-group' | translate }}
+                            <clr-icon shape="caret down"></clr-icon>
+                        </button>
+                        <clr-dropdown-menu clrPosition="bottom-right" *clrIfOpen>
+                            <button type="button"
+                                    clrDropdownItem
+                                    (click)="addExistingOptionGroup()">{{ 'catalog.add-existing-option-group' | translate }}</button>
+                            <button type="button"
+                                    clrDropdownItem
+                                    (click)="createNewOptionGroup()">{{ 'catalog.create-new-option-group' | translate }}</button>
+                        </clr-dropdown-menu>
+                    </clr-dropdown>
+                </div>
+            </vdr-form-item>
+
+            <table class="variants-list table" formArrayName="variants">
+                <thead>
+                <tr>
+                    <th>{{ 'catalog.product-variant-table-sku' | translate }}</th>
+                    <th>{{ 'catalog.product-variant-table-name' | translate }}</th>
+                    <th>{{ 'catalog.product-variant-table-options' | translate }}</th>
+                    <th>{{ 'catalog.product-variant-table-price' | translate }}</th>
+                </tr>
+                </thead>
+                <tbody>
+                <tr class="variant"
+                    *ngFor="let variant of variants$ | async; let i = index"
+                    [formGroupName]="i">
+                    <td>
+                        <input type="text" formControlName="sku">
+                    </td>
+                    <td>
+                        <input type="text" formControlName="name">
+                    </td>
+                    <td>
+                        <div *ngFor="let option of variant.options">{{ option.name }}</div>
+                    </td>
+                    <td>
+                        <input type="number" min="0" formControlName="price">
+                    </td>
+                </tr>
+                </tbody>
+            </table>
+        </ng-template>
 
     </section>
 

+ 43 - 5
admin-ui/src/app/catalog/components/product-detail/product-detail.component.ts

@@ -2,7 +2,7 @@ import { Component, OnDestroy } from '@angular/core';
 import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 import { combineLatest, forkJoin, Observable, Subject } from 'rxjs';
-import { filter, map, mergeMap, take, takeUntil, tap } from 'rxjs/operators';
+import { filter, map, mergeMap, switchMap, take, takeUntil } from 'rxjs/operators';
 
 import { notNullOrUndefined } from '../../../../../../shared/shared-utils';
 import { getDefaultLanguage } from '../../../common/utilities/get-default-language';
@@ -29,6 +29,7 @@ export class ProductDetailComponent implements OnDestroy {
     variants$: Observable<GetProductWithVariants_product_variants[]>;
     availableLanguages$: Observable<LanguageCode[]>;
     languageCode$: Observable<LanguageCode>;
+    isNew$: Observable<boolean>;
     productForm: FormGroup;
     private destroy$ = new Subject<void>();
 
@@ -41,7 +42,7 @@ export class ProductDetailComponent implements OnDestroy {
         private notificationService: NotificationService,
         private productUpdaterService: ProductUpdaterService,
     ) {
-        this.product$ = this.route.snapshot.data.product;
+        this.product$ = this.route.data.pipe(switchMap(data => data.product));
         this.variants$ = this.product$.pipe(map(product => product.variants));
         this.productForm = this.formBuilder.group({
             product: this.formBuilder.group({
@@ -51,7 +52,7 @@ export class ProductDetailComponent implements OnDestroy {
             }),
             variants: this.formBuilder.array([]),
         });
-
+        this.isNew$ = this.product$.pipe(map(product => product.id === ''));
         this.languageCode$ = this.route.queryParamMap.pipe(
             map(qpm => qpm.get('lang')),
             map(lang => (!lang ? getDefaultLanguage() : (lang as LanguageCode))),
@@ -175,6 +176,35 @@ export class ProductDetailComponent implements OnDestroy {
             .subscribe();
     }
 
+    create() {
+        const productGroup = this.productForm.get('product');
+        if (!productGroup || !productGroup.dirty) {
+            return;
+        }
+        combineLatest(this.product$, this.languageCode$)
+            .pipe(
+                take(1),
+                mergeMap(([product, languageCode]) => {
+                    const newProduct = this.productUpdaterService.getUpdatedProduct(
+                        product,
+                        productGroup.value,
+                        languageCode,
+                    );
+                    return this.dataService.product.createProduct(newProduct);
+                }),
+            )
+            .subscribe(
+                data => {
+                    this.notificationService.success(_('catalog.notify-create-product-success'));
+                    this.productForm.markAsPristine();
+                    this.router.navigate(['../', data.createProduct.id], { relativeTo: this.route });
+                },
+                err => {
+                    this.notificationService.error(_('catalog.notify-create-product-error'));
+                },
+            );
+    }
+
     save() {
         combineLatest(this.product$, this.languageCode$)
             .pipe(
@@ -208,16 +238,24 @@ export class ProductDetailComponent implements OnDestroy {
             )
             .subscribe(
                 () => {
-                    this.productForm.markAsPristine();
                     this.productForm.markAsPristine();
                     this.notificationService.success(_('catalog.notify-update-product-success'));
                 },
                 err => {
-                    this.notificationService.success(_('catalog.notify-update-product-error'));
+                    this.notificationService.error(_('catalog.notify-update-product-error'));
                 },
             );
     }
 
+    generateProductVariants() {
+        this.product$
+            .pipe(
+                take(1),
+                mergeMap(product => this.dataService.product.generateProductVariants(product.id)),
+            )
+            .subscribe();
+    }
+
     private setQueryParam(key: string, value: any) {
         this.router.navigate(['./'], {
             queryParams: { [key]: value },

+ 9 - 0
admin-ui/src/app/catalog/components/product-list/product-list.component.html

@@ -1,3 +1,12 @@
+<vdr-action-bar>
+    <vdr-ab-right>
+        <a class="btn btn-primary" [routerLink]="['./create']">
+            <clr-icon shape="plus"></clr-icon>
+            {{ 'catalog.create-new-product' | translate }}
+        </a>
+    </vdr-ab-right>
+</vdr-action-bar>
+
 <vdr-data-table [items]="products$ | async"
                 [itemsPerPage]="itemsPerPage$ | async"
                 [totalItems]="totalItems$ | async"

+ 8 - 3
admin-ui/src/app/catalog/providers/product-updater/product-updater.service.spec.ts

@@ -59,13 +59,18 @@ describe('ProductUpdaterService', () => {
             expect(result).not.toBe(product);
         });
 
-        it('returns undefined if the specified translation does not exist', () => {
+        it('creates new translation if the specified translation does not exist', () => {
             const formValue = {
-                name: 'New Name EN',
+                name: 'New Name AA',
             };
             const result = productUpdaterService.getUpdatedProduct(product, formValue, LanguageCode.aa);
 
-            expect(result).toBe(undefined);
+            expect(result.translations[2]).toEqual({
+                languageCode: LanguageCode.aa,
+                name: 'New Name AA',
+                description: '',
+                slug: '',
+            });
         });
 
         it('updates the non-translatable properties', () => {

+ 16 - 8
admin-ui/src/app/catalog/providers/product-updater/product-updater.service.ts

@@ -24,8 +24,13 @@ export class ProductUpdaterService {
         product: GetProductWithVariants_product,
         formValue: { [key: string]: any },
         languageCode: LanguageCode,
-    ): UpdateProductInput | undefined {
-        return this.createUpdatedTranslatable(product, formValue, languageCode);
+    ): UpdateProductInput {
+        return this.createUpdatedTranslatable(product, formValue, languageCode, {
+            languageCode,
+            name: product.name || '',
+            slug: product.slug || '',
+            description: product.description || '',
+        });
     }
 
     /**
@@ -51,18 +56,21 @@ export class ProductUpdaterService {
         translatable: T,
         updatedFields: { [key: string]: any },
         languageCode: LanguageCode,
-    ): T | undefined {
-        const currentTranslation = translatable.translations.find(t => t.languageCode === languageCode);
-        if (!currentTranslation) {
-            return;
-        }
+        defaultTranslation?: Partial<T['translations'][number]>,
+    ): T {
+        const currentTranslation =
+            translatable.translations.find(t => t.languageCode === languageCode) || defaultTranslation;
         const index = translatable.translations.indexOf(currentTranslation);
         const newTranslation = this.patchObject(currentTranslation, updatedFields);
         const newTranslatable = {
             ...(this.patchObject(translatable, updatedFields) as any),
             ...{ translations: translatable.translations.slice() },
         };
-        newTranslatable.translations.splice(index, 1, newTranslation);
+        if (index !== -1) {
+            newTranslatable.translations.splice(index, 1, newTranslation);
+        } else {
+            newTranslatable.translations.push(newTranslation);
+        }
         return newTranslatable;
     }
 

+ 19 - 2
admin-ui/src/app/catalog/providers/routing/product-resolver.ts

@@ -1,11 +1,13 @@
 import { Injectable } from '@angular/core';
 import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
 import { Observable, of } from 'rxjs';
-import { filter, finalize, map, take, tap } from 'rxjs/operators';
+import { filter, map, take } from 'rxjs/operators';
 
 import { notNullOrUndefined } from '../../../../../../shared/shared-utils';
+import { getDefaultLanguage } from '../../../common/utilities/get-default-language';
 import { DataService } from '../../../data/providers/data.service';
 import { GetProductWithVariants_product } from '../../../data/types/gql-generated-types';
+
 /**
  * Resolves the id from the path into a Customer entity.
  */
@@ -20,7 +22,22 @@ export class ProductResolver implements Resolve<Observable<GetProductWithVariant
     ): Observable<Observable<GetProductWithVariants_product>> {
         const id = route.paramMap.get('id');
 
-        if (id) {
+        if (id === 'create') {
+            return of(
+                of({
+                    __typename: 'Product' as 'Product',
+                    id: '',
+                    languageCode: getDefaultLanguage(),
+                    name: '',
+                    slug: '',
+                    image: '',
+                    description: '',
+                    translations: [],
+                    optionGroups: [],
+                    variants: [],
+                }),
+            );
+        } else if (id) {
             const stream = this.dataService.product
                 .getProduct(id)
                 .mapStream(data => data.product)

+ 24 - 17
admin-ui/src/app/data/fragments/product-fragments.ts

@@ -1,5 +1,27 @@
 import gql from 'graphql-tag';
 
+export const PRODUCT_VARIANT_FRAGMENT = gql`
+    fragment ProductVariant on ProductVariant {
+        id
+        languageCode
+        name
+        price
+        sku
+        image
+        options {
+            id
+            code
+            languageCode
+            name
+        }
+        translations {
+            id
+            languageCode
+            name
+        }
+    }
+`;
+
 export const PRODUCT_WITH_VARIANTS_FRAGMENT = gql`
     fragment ProductWithVariants on Product {
         id
@@ -21,25 +43,10 @@ export const PRODUCT_WITH_VARIANTS_FRAGMENT = gql`
             name
         }
         variants {
-            id
-            languageCode
-            name
-            price
-            sku
-            image
-            options {
-                id
-                code
-                languageCode
-                name
-            }
-            translations {
-                id
-                languageCode
-                name
-            }
+            ...ProductVariant
         }
     }
+    ${PRODUCT_VARIANT_FRAGMENT}
 `;
 
 export const PRODUCT_OPTION_GROUP_FRAGMENT = gql`

+ 21 - 8
admin-ui/src/app/data/mutations/product-mutations.ts

@@ -2,6 +2,7 @@ import gql from 'graphql-tag';
 
 import {
     PRODUCT_OPTION_GROUP_FRAGMENT,
+    PRODUCT_VARIANT_FRAGMENT,
     PRODUCT_WITH_VARIANTS_FRAGMENT,
 } from '../fragments/product-fragments';
 
@@ -14,19 +15,31 @@ export const UPDATE_PRODUCT = gql`
     ${PRODUCT_WITH_VARIANTS_FRAGMENT}
 `;
 
+export const CREATE_PRODUCT = gql`
+    mutation CreateProduct($input: CreateProductInput!) {
+        createProduct(input: $input) {
+            ...ProductWithVariants
+        }
+    }
+    ${PRODUCT_WITH_VARIANTS_FRAGMENT}
+`;
+
+export const GENERATE_PRODUCT_VARIANTS = gql`
+    mutation GenerateProductVariants($productId: ID!) {
+        generateVariantsForProduct(productId: $productId) {
+            ...ProductWithVariants
+        }
+    }
+    ${PRODUCT_WITH_VARIANTS_FRAGMENT}
+`;
+
 export const UPDATE_PRODUCT_VARIANTS = gql`
     mutation UpdateProductVariants($input: [UpdateProductVariantInput!]!) {
         updateProductVariants(input: $input) {
-            id
-            name
-            price
-            sku
-            translations {
-                id
-                name
-            }
+            ...ProductVariant
         }
     }
+    ${PRODUCT_VARIANT_FRAGMENT}
 `;
 
 export const CREATE_PRODUCT_OPTION_GROUP = gql`

+ 25 - 0
admin-ui/src/app/data/providers/product-data.service.ts

@@ -3,7 +3,9 @@ import { Observable } from 'rxjs';
 import { getDefaultLanguage } from '../../common/utilities/get-default-language';
 import {
     ADD_OPTION_GROUP_TO_PRODUCT,
+    CREATE_PRODUCT,
     CREATE_PRODUCT_OPTION_GROUP,
+    GENERATE_PRODUCT_VARIANTS,
     REMOVE_OPTION_GROUP_FROM_PRODUCT,
     UPDATE_PRODUCT,
     UPDATE_PRODUCT_VARIANTS,
@@ -16,9 +18,14 @@ import {
 import {
     AddOptionGroupToProduct,
     AddOptionGroupToProductVariables,
+    CreateProduct,
+    CreateProductInput,
     CreateProductOptionGroup,
     CreateProductOptionGroupInput,
     CreateProductOptionGroupVariables,
+    CreateProductVariables,
+    GenerateProductVariants,
+    GenerateProductVariantsVariables,
     GetProductList,
     GetProductListVariables,
     GetProductOptionGroups,
@@ -59,6 +66,17 @@ export class ProductDataService {
         );
     }
 
+    createProduct(product: CreateProductInput): Observable<CreateProduct> {
+        const input: CreateProductVariables = {
+            input: {
+                image: product.image,
+                translations: product.translations,
+                optionGroupCodes: product.optionGroupCodes,
+            },
+        };
+        return this.baseDataService.mutate<CreateProduct, CreateProductVariables>(CREATE_PRODUCT, input);
+    }
+
     updateProduct(product: UpdateProductInput): Observable<UpdateProduct> {
         const input: UpdateProductVariables = {
             input: {
@@ -70,6 +88,13 @@ export class ProductDataService {
         return this.baseDataService.mutate<UpdateProduct, UpdateProductVariables>(UPDATE_PRODUCT, input);
     }
 
+    generateProductVariants(productId: string): Observable<GenerateProductVariants> {
+        return this.baseDataService.mutate<GenerateProductVariants, GenerateProductVariantsVariables>(
+            GENERATE_PRODUCT_VARIANTS,
+            { productId },
+        );
+    }
+
     updateProductVariants(variants: UpdateProductVariantInput[]): Observable<UpdateProductVariants> {
         const input: UpdateProductVariantsVariables = {
             input: variants.map(v => ({

+ 233 - 38
admin-ui/src/app/data/types/gql-generated-types.ts

@@ -150,22 +150,176 @@ export interface UpdateProductVariables {
 /* tslint:disable */
 // This file was automatically generated and should not be edited.
 
+// ====================================================
+// GraphQL mutation operation: CreateProduct
+// ====================================================
+
+export interface CreateProduct_createProduct_translations {
+    __typename: 'ProductTranslation';
+    languageCode: LanguageCode;
+    name: string;
+    slug: string;
+    description: string | null;
+}
+
+export interface CreateProduct_createProduct_optionGroups {
+    __typename: 'ProductOptionGroup';
+    id: string;
+    languageCode: LanguageCode;
+    code: string;
+    name: string;
+}
+
+export interface CreateProduct_createProduct_variants_options {
+    __typename: 'ProductOption';
+    id: string;
+    code: string | null;
+    languageCode: LanguageCode | null;
+    name: string | null;
+}
+
+export interface CreateProduct_createProduct_variants_translations {
+    __typename: 'ProductVariantTranslation';
+    id: string;
+    languageCode: LanguageCode;
+    name: string;
+}
+
+export interface CreateProduct_createProduct_variants {
+    __typename: 'ProductVariant';
+    id: string;
+    languageCode: LanguageCode;
+    name: string;
+    price: number;
+    sku: string;
+    image: string | null;
+    options: CreateProduct_createProduct_variants_options[];
+    translations: CreateProduct_createProduct_variants_translations[];
+}
+
+export interface CreateProduct_createProduct {
+    __typename: 'Product';
+    id: string;
+    languageCode: LanguageCode;
+    name: string;
+    slug: string;
+    image: string;
+    description: string;
+    translations: CreateProduct_createProduct_translations[];
+    optionGroups: CreateProduct_createProduct_optionGroups[];
+    variants: CreateProduct_createProduct_variants[];
+}
+
+export interface CreateProduct {
+    createProduct: CreateProduct_createProduct; // Create a new Product
+}
+
+export interface CreateProductVariables {
+    input: CreateProductInput;
+}
+
+/* tslint:disable */
+// This file was automatically generated and should not be edited.
+
+// ====================================================
+// GraphQL mutation operation: GenerateProductVariants
+// ====================================================
+
+export interface GenerateProductVariants_generateVariantsForProduct_translations {
+    __typename: 'ProductTranslation';
+    languageCode: LanguageCode;
+    name: string;
+    slug: string;
+    description: string | null;
+}
+
+export interface GenerateProductVariants_generateVariantsForProduct_optionGroups {
+    __typename: 'ProductOptionGroup';
+    id: string;
+    languageCode: LanguageCode;
+    code: string;
+    name: string;
+}
+
+export interface GenerateProductVariants_generateVariantsForProduct_variants_options {
+    __typename: 'ProductOption';
+    id: string;
+    code: string | null;
+    languageCode: LanguageCode | null;
+    name: string | null;
+}
+
+export interface GenerateProductVariants_generateVariantsForProduct_variants_translations {
+    __typename: 'ProductVariantTranslation';
+    id: string;
+    languageCode: LanguageCode;
+    name: string;
+}
+
+export interface GenerateProductVariants_generateVariantsForProduct_variants {
+    __typename: 'ProductVariant';
+    id: string;
+    languageCode: LanguageCode;
+    name: string;
+    price: number;
+    sku: string;
+    image: string | null;
+    options: GenerateProductVariants_generateVariantsForProduct_variants_options[];
+    translations: GenerateProductVariants_generateVariantsForProduct_variants_translations[];
+}
+
+export interface GenerateProductVariants_generateVariantsForProduct {
+    __typename: 'Product';
+    id: string;
+    languageCode: LanguageCode;
+    name: string;
+    slug: string;
+    image: string;
+    description: string;
+    translations: GenerateProductVariants_generateVariantsForProduct_translations[];
+    optionGroups: GenerateProductVariants_generateVariantsForProduct_optionGroups[];
+    variants: GenerateProductVariants_generateVariantsForProduct_variants[];
+}
+
+export interface GenerateProductVariants {
+    generateVariantsForProduct: GenerateProductVariants_generateVariantsForProduct; // Create a set of ProductVariants based on the OptionGroups assigned to the given Product
+}
+
+export interface GenerateProductVariantsVariables {
+    productId: string;
+}
+
+/* tslint:disable */
+// This file was automatically generated and should not be edited.
+
 // ====================================================
 // GraphQL mutation operation: UpdateProductVariants
 // ====================================================
 
+export interface UpdateProductVariants_updateProductVariants_options {
+    __typename: 'ProductOption';
+    id: string;
+    code: string | null;
+    languageCode: LanguageCode | null;
+    name: string | null;
+}
+
 export interface UpdateProductVariants_updateProductVariants_translations {
     __typename: 'ProductVariantTranslation';
     id: string;
+    languageCode: LanguageCode;
     name: string;
 }
 
 export interface UpdateProductVariants_updateProductVariants {
     __typename: 'ProductVariant';
     id: string;
+    languageCode: LanguageCode;
     name: string;
     price: number;
     sku: string;
+    image: string | null;
+    options: UpdateProductVariants_updateProductVariants_options[];
     translations: UpdateProductVariants_updateProductVariants_translations[];
 }
 
@@ -293,6 +447,56 @@ export interface RemoveOptionGroupFromProductVariables {
 /* tslint:disable */
 // This file was automatically generated and should not be edited.
 
+// ====================================================
+// GraphQL query operation: GetNetworkStatus
+// ====================================================
+
+export interface GetNetworkStatus_networkStatus {
+    __typename: 'NetworkStatus';
+    inFlightRequests: number;
+}
+
+export interface GetNetworkStatus {
+    networkStatus: GetNetworkStatus_networkStatus;
+}
+
+/* tslint:disable */
+// This file was automatically generated and should not be edited.
+
+// ====================================================
+// GraphQL query operation: GetUserStatus
+// ====================================================
+
+export interface GetUserStatus_userStatus {
+    __typename: 'UserStatus';
+    username: string;
+    isLoggedIn: boolean;
+    loginTime: string;
+}
+
+export interface GetUserStatus {
+    userStatus: GetUserStatus_userStatus;
+}
+
+/* tslint:disable */
+// This file was automatically generated and should not be edited.
+
+// ====================================================
+// GraphQL query operation: GetUiState
+// ====================================================
+
+export interface GetUiState_uiState {
+    __typename: 'UiState';
+    language: LanguageCode;
+}
+
+export interface GetUiState {
+    uiState: GetUiState_uiState;
+}
+
+/* tslint:disable */
+// This file was automatically generated and should not be edited.
+
 // ====================================================
 // GraphQL query operation: GetProductWithVariants
 // ====================================================
@@ -431,50 +635,34 @@ export interface GetProductOptionGroupsVariables {
 // This file was automatically generated and should not be edited.
 
 // ====================================================
-// GraphQL query operation: GetNetworkStatus
+// GraphQL fragment: ProductVariant
 // ====================================================
 
-export interface GetNetworkStatus_networkStatus {
-    __typename: 'NetworkStatus';
-    inFlightRequests: number;
-}
-
-export interface GetNetworkStatus {
-    networkStatus: GetNetworkStatus_networkStatus;
-}
-
-/* tslint:disable */
-// This file was automatically generated and should not be edited.
-
-// ====================================================
-// GraphQL query operation: GetUserStatus
-// ====================================================
-
-export interface GetUserStatus_userStatus {
-    __typename: 'UserStatus';
-    username: string;
-    isLoggedIn: boolean;
-    loginTime: string;
-}
-
-export interface GetUserStatus {
-    userStatus: GetUserStatus_userStatus;
+export interface ProductVariant_options {
+    __typename: 'ProductOption';
+    id: string;
+    code: string | null;
+    languageCode: LanguageCode | null;
+    name: string | null;
 }
 
-/* tslint:disable */
-// This file was automatically generated and should not be edited.
-
-// ====================================================
-// GraphQL query operation: GetUiState
-// ====================================================
-
-export interface GetUiState_uiState {
-    __typename: 'UiState';
-    language: LanguageCode;
+export interface ProductVariant_translations {
+    __typename: 'ProductVariantTranslation';
+    id: string;
+    languageCode: LanguageCode;
+    name: string;
 }
 
-export interface GetUiState {
-    uiState: GetUiState_uiState;
+export interface ProductVariant {
+    __typename: 'ProductVariant';
+    id: string;
+    languageCode: LanguageCode;
+    name: string;
+    price: number;
+    sku: string;
+    image: string | null;
+    options: ProductVariant_options[];
+    translations: ProductVariant_translations[];
 }
 
 /* tslint:disable */
@@ -787,6 +975,13 @@ export interface ProductTranslationInput {
     description?: string | null;
 }
 
+//
+export interface CreateProductInput {
+    image?: string | null;
+    translations: (ProductTranslationInput | null)[];
+    optionGroupCodes?: (string | null)[] | null;
+}
+
 //
 export interface UpdateProductVariantInput {
     id: string;