Browse Source

Merge branch 'master' into next

Michael Bromley 5 years ago
parent
commit
00f68e8867

+ 7 - 3
docs/content/docs/plugins/extending-the-admin-ui/_index.md

@@ -59,11 +59,15 @@ Although the examples so far all use the `compileUiExtensions` function in conju
 ```TypeScript
 // compile-admin-ui.ts
 import { compileUiExtensions } from '@vendure/ui-devkit/compiler';
+import * as path from 'path';
 
 compileUiExtensions({
-  outputPath: path.join(__dirname, 'admin-ui'),
-  extensions: [/* ... */],
-}).compile();
+    outputPath: path.join(__dirname, 'admin-ui'),
+    extensions: [/* ... */],
+}).compile?.().then(() => {
+    process.exit(0);
+});
+
 ```
 
 This can then be run from the command line:

+ 1 - 1
packages/admin-ui/src/lib/catalog/src/components/facet-detail/facet-detail.component.ts

@@ -296,7 +296,7 @@ export class FacetDetailComponent extends BaseDetailComponent<FacetWithValues.Fr
 
         const currentValuesFormArray = this.detailForm.get('values') as FormArray;
         currentValuesFormArray.clear();
-        this.values = facet.values;
+        this.values = [...facet.values];
         facet.values.forEach((value, i) => {
             const valueTranslation =
                 value.translations && value.translations.find((t) => t.languageCode === languageCode);

+ 19 - 0
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -4381,6 +4381,19 @@ export type UpdateCustomerMutation = (
   ) }
 );
 
+export type DeleteCustomerMutationVariables = {
+  id: Scalars['ID'];
+};
+
+
+export type DeleteCustomerMutation = (
+  { __typename?: 'Mutation' }
+  & { deleteCustomer: (
+    { __typename?: 'DeletionResponse' }
+    & Pick<DeletionResponse, 'result' | 'message'>
+  ) }
+);
+
 export type CreateCustomerAddressMutationVariables = {
   customerId: Scalars['ID'];
   input: CreateAddressInput;
@@ -6965,6 +6978,12 @@ export namespace UpdateCustomer {
   export type UpdateCustomer = CustomerFragment;
 }
 
+export namespace DeleteCustomer {
+  export type Variables = DeleteCustomerMutationVariables;
+  export type Mutation = DeleteCustomerMutation;
+  export type DeleteCustomer = DeleteCustomerMutation['deleteCustomer'];
+}
+
 export namespace CreateCustomerAddress {
   export type Variables = CreateCustomerAddressMutationVariables;
   export type Mutation = CreateCustomerAddressMutation;

+ 9 - 0
packages/admin-ui/src/lib/core/src/data/definitions/customer-definitions.ts

@@ -109,6 +109,15 @@ export const UPDATE_CUSTOMER = gql`
     ${CUSTOMER_FRAGMENT}
 `;
 
+export const DELETE_CUSTOMER = gql`
+    mutation DeleteCustomer($id: ID!) {
+        deleteCustomer(id: $id) {
+            result
+            message
+        }
+    }
+`;
+
 export const CREATE_CUSTOMER_ADDRESS = gql`
     mutation CreateCustomerAddress($customerId: ID!, $input: CreateAddressInput!) {
         createCustomerAddress(customerId: $customerId, input: $input) {

+ 9 - 0
packages/admin-ui/src/lib/core/src/data/providers/customer-data.service.ts

@@ -9,6 +9,7 @@ import {
     CreateCustomerInput,
     CustomerGroupListOptions,
     CustomerListOptions,
+    DeleteCustomer,
     DeleteCustomerGroup,
     DeleteCustomerNote,
     GetCustomer,
@@ -34,6 +35,7 @@ import {
     CREATE_CUSTOMER,
     CREATE_CUSTOMER_ADDRESS,
     CREATE_CUSTOMER_GROUP,
+    DELETE_CUSTOMER,
     DELETE_CUSTOMER_GROUP,
     DELETE_CUSTOMER_NOTE,
     GET_CUSTOMER,
@@ -101,6 +103,13 @@ export class CustomerDataService {
         );
     }
 
+    deleteCustomer(id: string) {
+        return this.baseDataService.mutate<DeleteCustomer.Mutation, DeleteCustomer.Variables>(
+            DELETE_CUSTOMER,
+            { id },
+        );
+    }
+
     createCustomerAddress(customerId: string, input: CreateAddressInput) {
         return this.baseDataService.mutate<CreateCustomerAddress.Mutation, CreateCustomerAddress.Variables>(
             CREATE_CUSTOMER_ADDRESS,

+ 7 - 0
packages/admin-ui/src/lib/customer/src/components/customer-detail/customer-detail.component.html

@@ -46,6 +46,13 @@
     >
         <input id="lastName" type="text" formControlName="lastName" />
     </vdr-form-field>
+    <vdr-form-field
+        [label]="'customer.phone-number' | translate"
+        for="phoneNumber"
+        [readOnlyToggle]="!(isNew$ | async)"
+    >
+        <input id="phoneNumber" type="text" formControlName="phoneNumber" />
+    </vdr-form-field>
     <vdr-form-field
         [label]="'customer.email-address' | translate"
         for="emailAddress"

+ 2 - 0
packages/admin-ui/src/lib/customer/src/components/customer-detail/customer-detail.component.ts

@@ -177,6 +177,7 @@ export class CustomerDetailComponent extends BaseDetailComponent<CustomerWithOrd
             emailAddress: formValue.emailAddress,
             firstName: formValue.firstName,
             lastName: formValue.lastName,
+            phoneNumber: formValue.phoneNumber,
         };
         this.dataService.customer.createCustomer(customer, formValue.password).subscribe(
             (data) => {
@@ -219,6 +220,7 @@ export class CustomerDetailComponent extends BaseDetailComponent<CustomerWithOrd
                             emailAddress: formValue.emailAddress,
                             firstName: formValue.firstName,
                             lastName: formValue.lastName,
+                            phoneNumber: formValue.phoneNumber,
                         };
                         saveOperations.push(this.dataService.customer.updateCustomer(customer));
                     }

+ 21 - 0
packages/admin-ui/src/lib/customer/src/components/customer-list/customer-list.component.html

@@ -29,6 +29,7 @@
     <vdr-dt-column [expand]="true">{{ 'customer.email-address' | translate }}</vdr-dt-column>
     <vdr-dt-column>{{ 'customer.customer-type' | translate }}</vdr-dt-column>
     <vdr-dt-column></vdr-dt-column>
+    <vdr-dt-column></vdr-dt-column>
     <ng-template let-customer="item">
         <td class="left align-middle">
             {{ customer.title }} {{ customer.firstName }} {{ customer.lastName }}
@@ -44,5 +45,25 @@
                 [linkTo]="['./', customer.id]"
             ></vdr-table-row-action>
         </td>
+        <td>
+            <vdr-dropdown>
+                <button type="button" class="btn btn-link btn-sm" vdrDropdownTrigger>
+                    {{ 'common.actions' | translate }}
+                    <clr-icon shape="caret down"></clr-icon>
+                </button>
+                <vdr-dropdown-menu vdrPosition="bottom-right">
+                    <button
+                        type="button"
+                        class="delete-button"
+                        (click)="deleteCustomer(customer)"
+                        [disabled]="!('DeleteCustomer' | hasPermission)"
+                        vdrDropdownItem
+                    >
+                        <clr-icon shape="trash" class="is-danger"></clr-icon>
+                        {{ 'common.delete' | translate }}
+                    </button>
+                </vdr-dropdown-menu>
+            </vdr-dropdown>
+        </td>
     </ng-template>
 </vdr-data-table>

+ 43 - 5
packages/admin-ui/src/lib/customer/src/components/customer-list/customer-list.component.ts

@@ -1,11 +1,17 @@
 import { Component, OnInit } from '@angular/core';
 import { FormControl } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
-import { BaseListComponent } from '@vendure/admin-ui/core';
-import { GetCustomerList } from '@vendure/admin-ui/core';
-import { DataService } from '@vendure/admin-ui/core';
+import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
+import {
+    BaseListComponent,
+    DataService,
+    GetCustomerList,
+    ModalService,
+    NotificationService,
+} from '@vendure/admin-ui/core';
 import { SortOrder } from '@vendure/common/lib/generated-shop-types';
-import { debounceTime, takeUntil } from 'rxjs/operators';
+import { EMPTY } from 'rxjs';
+import { debounceTime, switchMap, takeUntil } from 'rxjs/operators';
 
 @Component({
     selector: 'vdr-customer-list',
@@ -15,7 +21,13 @@ import { debounceTime, takeUntil } from 'rxjs/operators';
 export class CustomerListComponent extends BaseListComponent<GetCustomerList.Query, GetCustomerList.Items>
     implements OnInit {
     searchTerm = new FormControl('');
-    constructor(private dataService: DataService, router: Router, route: ActivatedRoute) {
+    constructor(
+        private dataService: DataService,
+        router: Router,
+        route: ActivatedRoute,
+        private modalService: ModalService,
+        private notificationService: NotificationService,
+    ) {
         super(router, route);
         super.setQueryFn(
             (...args: any[]) => this.dataService.customer.getCustomerList(...args),
@@ -43,4 +55,30 @@ export class CustomerListComponent extends BaseListComponent<GetCustomerList.Que
             .pipe(debounceTime(250), takeUntil(this.destroy$))
             .subscribe(() => this.refresh());
     }
+
+    deleteCustomer(customer: GetCustomerList.Items) {
+        return this.modalService
+            .dialog({
+                title: _('catalog.confirm-delete-customer'),
+                body: `${customer.firstName} ${customer.lastName}`,
+                buttons: [
+                    { type: 'secondary', label: _('common.cancel') },
+                    { type: 'danger', label: _('common.delete'), returnValue: true },
+                ],
+            })
+            .pipe(switchMap((res) => (res ? this.dataService.customer.deleteCustomer(customer.id) : EMPTY)))
+            .subscribe(
+                () => {
+                    this.notificationService.success(_('common.notify-delete-success'), {
+                        entity: 'Customer',
+                    });
+                    this.refresh();
+                },
+                (err) => {
+                    this.notificationService.error(_('common.notify-delete-error'), {
+                        entity: 'Customer',
+                    });
+                },
+            );
+    }
 }

+ 0 - 1
packages/admin-ui/src/lib/order/src/components/order-list/order-list.component.html

@@ -4,7 +4,6 @@
             <select clrSelect name="state" [formControl]="stateFilter">
                 <option value="all">{{ 'order.state-all-orders' | translate }}</option>
                 <option value="AddingItems">{{ 'order.state-adding-items' | translate }}</option>
-                <option value="AddingItems">{{ 'order.state-adding-items' | translate }}</option>
                 <option value="ArrangingPayment">{{ 'order.state-arranging-payment' | translate }}</option>
                 <option value="PaymentAuthorized">{{ 'order.state-payment-authorized' | translate }}</option>
                 <option value="PaymentSettled">{{ 'order.state-payment-settled' | translate }}</option>

+ 1 - 0
packages/admin-ui/src/lib/static/i18n-messages/en.json

@@ -65,6 +65,7 @@
     "confirm-delete-collection": "Delete collection?",
     "confirm-delete-collection-and-children-body": "Deleting this collection will also delete all child collections",
     "confirm-delete-country": "Delete country?",
+    "confirm-delete-customer": "Delete customer?",
     "confirm-delete-facet": "Delete facet?",
     "confirm-delete-facet-value": "Delete facet value?",
     "confirm-delete-product": "Delete product?",

+ 44 - 41
packages/email-plugin/dev-mailbox.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html lang="en">
 <head>
-    <meta charset="UTF-8">
+    <meta charset="UTF-8" />
     <title>Vendure Development Inbox</title>
     <style>
         body {
@@ -37,7 +37,9 @@
             display: flex;
             justify-content: flex-end;
         }
-        input, select, button {
+        input,
+        select,
+        button {
             padding: 6px;
             border-radius: 3px;
             border: 1px solid #0b384b;
@@ -108,27 +110,28 @@
     <h1 class="heading">Vendure Dev Mailbox</h1>
     <div class="generate-controls">
         <select id="type-selector"></select>
-        <input id="language-code" value="en" type="text">
+        <input id="language-code" value="en" type="text" />
         <button id="generate-test">Generate test</button>
     </div>
 </div>
 <div class="content">
-    <div class="list">
-    </div>
-    <div class="detail">
-
-    </div>
+    <div class="list"></div>
+    <div class="detail"></div>
 </div>
 <script>
     let selectedId = '';
+    const normalizePath = (endpoint) => {
+        const pathname = location.pathname;
+        return pathname.endsWith('/') ? `${pathname}${endpoint}` : `${pathname}/${endpoint}`;
+    };
     refreshInbox();
     setInterval(refreshInbox, 5000);
 
     const typeSelect = document.querySelector('#type-selector');
-    fetch('./types')
-        .then(res => res.json())
-        .then(res => {
-            res.forEach(type => {
+    fetch(normalizePath('types'))
+        .then((res) => res.json())
+        .then((res) => {
+            res.forEach((type) => {
                 const option = document.createElement('option');
                 option.value = type;
                 option.text = type;
@@ -138,24 +141,24 @@
 
     const languageCodeInput = document.querySelector('#language-code');
     const generateTestButton = document.querySelector('#generate-test');
-    generateTestButton.addEventListener('click', e => {
-        fetch(`./generate/${typeSelect.value}/${languageCodeInput.value}`)
-            .then(() => new Promise(resolve => setTimeout(resolve, 500)))
+    generateTestButton.addEventListener('click', (e) => {
+        fetch(normalizePath(`generate/${typeSelect.value}/${languageCodeInput.value}`))
+            .then(() => new Promise((resolve) => setTimeout(resolve, 500)))
             .then(() => refreshInbox());
     });
 
     const list = document.querySelector('.list');
 
     function refreshInbox() {
-        fetch('./list')
-            .then(res => res.json())
-            .then(res => renderList(res));
+        fetch(normalizePath('list'))
+            .then((res) => res.json())
+            .then((res) => renderList(res));
     }
 
     function renderList(items) {
         const list = document.querySelector('.list');
         list.innerHTML = '';
-        const rows = items.forEach(item => {
+        const rows = items.forEach((item) => {
             const row = document.createElement('div');
             row.classList.add('row');
             row.dataset.id = item.fileName;
@@ -168,9 +171,9 @@
 
             row.addEventListener('click', (e) => {
                 selectedId = item.fileName;
-                fetch('./item/' + item.fileName)
-                    .then(res => res.json())
-                    .then(res => renderEmail(res))
+                fetch(normalizePath('item/' + item.fileName))
+                    .then((res) => res.json())
+                    .then((res) => renderEmail(res))
                     .then(() => highlightSelectedRow());
             });
             list.appendChild(row);
@@ -179,7 +182,7 @@
     }
 
     function highlightSelectedRow() {
-        document.querySelectorAll('.list .row').forEach(row => {
+        document.querySelectorAll('.list .row').forEach((row) => {
             row.classList.remove('selected');
             if (row.dataset.id === selectedId) {
                 row.classList.add('selected');
@@ -189,24 +192,24 @@
 
     function renderEmail(email) {
         const content = `
-            <div class="metadata">
-                <table>
-                    <tr>
-                        <td>Recipient:</td>
-                        <td>${email.recipient}</td>
-                    </tr>
-                    <tr>
-                        <td>Subject:</td>
-                        <td>${email.subject}</td>
-                    </tr>
-                    <tr>
-                        <td>Date:</td>
-                        <td>${new Date().toLocaleString()}</td>
-                    </tr>
-                </table>
-            </div>
-            <iframe srcdoc="${email.body.replace(/"/g, '&quot;')}"></iframe>
-        `;
+    <div class="metadata">
+        <table>
+            <tr>
+                <td>Recipient:</td>
+                <td>${email.recipient}</td>
+            </tr>
+            <tr>
+                <td>Subject:</td>
+                <td>${email.subject}</td>
+            </tr>
+            <tr>
+                <td>Date:</td>
+                <td>${new Date().toLocaleString()}</td>
+            </tr>
+        </table>
+    </div>
+    <iframe srcdoc="${email.body.replace(/"/g, '&quot;')}"></iframe>
+`;
 
         document.querySelector('.detail').innerHTML = content;
     }