Jelajahi Sumber

feat(admin-ui): Enable deletion of Channels

Relates to #12
Michael Bromley 6 tahun lalu
induk
melakukan
b295e52e6b

+ 32 - 0
packages/admin-ui/src/app/common/generated-types.ts

@@ -1815,6 +1815,7 @@ export type Mutation = {
   setAsLoggedOut: UserStatus,
   setUiLanguage?: Maybe<LanguageCode>,
   setActiveChannel: UserStatus,
+  updateUserChannels: UserStatus,
 };
 
 
@@ -2200,6 +2201,11 @@ export type MutationSetActiveChannelArgs = {
   channelId: Scalars['ID']
 };
 
+
+export type MutationUpdateUserChannelsArgs = {
+  channels: Array<CurrentUserChannelInput>
+};
+
 export type NetworkStatus = {
   __typename?: 'NetworkStatus',
   inFlightRequests: Scalars['Int'],
@@ -3702,6 +3708,13 @@ export type SetActiveChannelMutationVariables = {
 
 export type SetActiveChannelMutation = ({ __typename?: 'Mutation' } & { setActiveChannel: ({ __typename?: 'UserStatus' } & UserStatusFragment) });
 
+export type UpdateUserChannelsMutationVariables = {
+  channels: Array<CurrentUserChannelInput>
+};
+
+
+export type UpdateUserChannelsMutation = ({ __typename?: 'Mutation' } & { updateUserChannels: ({ __typename?: 'UserStatus' } & UserStatusFragment) });
+
 export type GetCollectionFiltersQueryVariables = {};
 
 
@@ -4315,6 +4328,13 @@ export type UpdateChannelMutationVariables = {
 
 export type UpdateChannelMutation = ({ __typename?: 'Mutation' } & { updateChannel: ({ __typename?: 'Channel' } & ChannelFragment) });
 
+export type DeleteChannelMutationVariables = {
+  id: Scalars['ID']
+};
+
+
+export type DeleteChannelMutation = ({ __typename?: 'Mutation' } & { deleteChannel: ({ __typename?: 'DeletionResponse' } & Pick<DeletionResponse, 'result' | 'message'>) });
+
 export type PaymentMethodFragment = ({ __typename?: 'PaymentMethod' } & Pick<PaymentMethod, 'id' | 'createdAt' | 'updatedAt' | 'code' | 'enabled'> & { configArgs: Array<({ __typename?: 'ConfigArg' } & Pick<ConfigArg, 'name' | 'type' | 'value'>)> });
 
 export type GetPaymentMethodListQueryVariables = {
@@ -4611,6 +4631,12 @@ export namespace SetActiveChannel {
   export type SetActiveChannel = UserStatusFragment;
 }
 
+export namespace UpdateUserChannels {
+  export type Variables = UpdateUserChannelsMutationVariables;
+  export type Mutation = UpdateUserChannelsMutation;
+  export type UpdateUserChannels = UserStatusFragment;
+}
+
 export namespace GetCollectionFilters {
   export type Variables = GetCollectionFiltersQueryVariables;
   export type Query = GetCollectionFiltersQuery;
@@ -5282,6 +5308,12 @@ export namespace UpdateChannel {
   export type UpdateChannel = ChannelFragment;
 }
 
+export namespace DeleteChannel {
+  export type Variables = DeleteChannelMutationVariables;
+  export type Mutation = DeleteChannelMutation;
+  export type DeleteChannel = DeleteChannelMutation['deleteChannel'];
+}
+
 export namespace PaymentMethod {
   export type Fragment = PaymentMethodFragment;
   export type ConfigArgs = (NonNullable<PaymentMethodFragment['configArgs'][0]>);

+ 13 - 0
packages/admin-ui/src/app/data/client-state/client-resolvers.ts

@@ -9,6 +9,7 @@ import {
     SetActiveChannel,
     SetAsLoggedIn,
     SetUiLanguage,
+    UpdateUserChannels,
     UserStatus,
 } from '../../common/generated-types';
 import { GET_NEWTORK_STATUS, GET_USER_STATUS } from '../definitions/client-definitions';
@@ -96,6 +97,18 @@ export const clientResolvers: ResolverDefinition = {
             cache.writeData({ data });
             return { ...previous.userStatus, ...data.userStatus };
         },
+        updateUserChannels: (_, args: UpdateUserChannels.Variables, { cache }): UserStatus => {
+            // tslint:disable-next-line:no-non-null-assertion
+            const previous = cache.readQuery<GetUserStatus.Query>({ query: GET_USER_STATUS })!;
+            const data = {
+                userStatus: {
+                    __typename: 'UserStatus' as const,
+                    channels: args.channels,
+                },
+            };
+            cache.writeData({ data });
+            return { ...previous.userStatus, ...data.userStatus };
+        },
     },
 };
 

+ 1 - 0
packages/admin-ui/src/app/data/client-state/client-types.graphql

@@ -11,6 +11,7 @@ type Mutation {
     setAsLoggedOut: UserStatus!
     setUiLanguage(languageCode: LanguageCode): LanguageCode
     setActiveChannel(channelId: ID!): UserStatus!
+    updateUserChannels(channels: [CurrentUserChannelInput!]!): UserStatus!
 }
 
 type NetworkStatus {

+ 9 - 0
packages/admin-ui/src/app/data/definitions/client-definitions.ts

@@ -85,3 +85,12 @@ export const SET_ACTIVE_CHANNEL = gql`
     }
     ${USER_STATUS_FRAGMENT}
 `;
+
+export const UPDATE_USER_CHANNELS = gql`
+    mutation UpdateUserChannels($channels: [CurrentUserChannelInput!]!) {
+        updateUserChannels(channels: $channels) @client {
+            ...UserStatus
+        }
+    }
+    ${USER_STATUS_FRAGMENT}
+`;

+ 9 - 0
packages/admin-ui/src/app/data/definitions/settings-definitions.ts

@@ -322,6 +322,15 @@ export const UPDATE_CHANNEL = gql`
     ${CHANNEL_FRAGMENT}
 `;
 
+export const DELETE_CHANNEL = gql`
+    mutation DeleteChannel($id: ID!) {
+        deleteChannel(id: $id) {
+            result
+            message
+        }
+    }
+`;
+
 export const PAYMENT_METHOD_FRAGMENT = gql`
     fragment PaymentMethod on PaymentMethod {
         id

+ 12 - 0
packages/admin-ui/src/app/data/providers/client-data.service.ts

@@ -1,5 +1,6 @@
 import {
     CurrentUserChannel,
+    CurrentUserChannelInput,
     GetNetworkStatus,
     GetUiState,
     GetUserStatus,
@@ -9,6 +10,7 @@ import {
     SetActiveChannel,
     SetAsLoggedIn,
     SetUiLanguage,
+    UpdateUserChannels,
 } from '../../common/generated-types';
 import {
     GET_NEWTORK_STATUS,
@@ -20,6 +22,7 @@ import {
     SET_AS_LOGGED_IN,
     SET_AS_LOGGED_OUT,
     SET_UI_LANGUAGE,
+    UPDATE_USER_CHANNELS,
 } from '../definitions/client-definitions';
 
 import { BaseDataService } from './base-data.service';
@@ -83,4 +86,13 @@ export class ClientDataService {
             },
         );
     }
+
+    updateUserChannels(channels: CurrentUserChannelInput[]) {
+        return this.baseDataService.mutate<UpdateUserChannels.Mutation, UpdateUserChannels.Variables>(
+            UPDATE_USER_CHANNELS,
+            {
+                channels,
+            },
+        );
+    }
 }

+ 8 - 0
packages/admin-ui/src/app/data/providers/settings-data.service.ts

@@ -13,6 +13,7 @@ import {
     CreateTaxRateInput,
     CreateZone,
     CreateZoneInput,
+    DeleteChannel,
     DeleteCountry,
     GetActiveChannel,
     GetAllJobs,
@@ -56,6 +57,7 @@ import {
     CREATE_TAX_CATEGORY,
     CREATE_TAX_RATE,
     CREATE_ZONE,
+    DELETE_CHANNEL,
     DELETE_COUNTRY,
     GET_ACTIVE_CHANNEL,
     GET_ALL_JOBS,
@@ -256,6 +258,12 @@ export class SettingsDataService {
         });
     }
 
+    deleteChannel(id: string) {
+        return this.baseDataService.mutate<DeleteChannel.Mutation, DeleteChannel.Variables>(DELETE_CHANNEL, {
+            id,
+        });
+    }
+
     getPaymentMethods(take: number = 10, skip: number = 0) {
         return this.baseDataService.query<GetPaymentMethodList.Query, GetPaymentMethodList.Variables>(
             GET_PAYMENT_METHOD_LIST,

+ 42 - 18
packages/admin-ui/src/app/settings/components/channel-detail/channel-detail.component.ts

@@ -3,7 +3,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 import { getDefaultLanguage } from '@vendure/admin-ui/src/app/common/utilities/get-default-language';
 import { Observable } from 'rxjs';
-import { mergeMap, take } from 'rxjs/operators';
+import { map, mergeMap, take } from 'rxjs/operators';
 import { DEFAULT_CHANNEL_CODE } from 'shared/shared-constants';
 
 import { BaseDetailComponent } from '../../../common/base-detail.component';
@@ -79,21 +79,37 @@ export class ChannelDetailComponent extends BaseDetailComponent<Channel.Fragment
             defaultShippingZoneId: formValue.defaultShippingZoneId,
             defaultTaxZoneId: formValue.defaultTaxZoneId,
         };
-        this.dataService.settings.createChannel(input).subscribe(
-            data => {
-                this.notificationService.success(_('common.notify-create-success'), {
-                    entity: 'Channel',
-                });
-                this.detailForm.markAsPristine();
-                this.changeDetector.markForCheck();
-                this.router.navigate(['../', data.createChannel.id], { relativeTo: this.route });
-            },
-            err => {
-                this.notificationService.error(_('common.notify-create-error'), {
-                    entity: 'Channel',
-                });
-            },
-        );
+        this.dataService.settings
+            .createChannel(input)
+            .pipe(
+                mergeMap(({ createChannel }) =>
+                    this.dataService.auth.currentUser().single$.pipe(
+                        map(({ me }) => ({
+                            me,
+                            createChannel,
+                        })),
+                    ),
+                ),
+                mergeMap(({ me, createChannel }) =>
+                    // tslint:disable-next-line:no-non-null-assertion
+                    this.dataService.client.updateUserChannels(me!.channels).pipe(map(() => createChannel)),
+                ),
+            )
+            .subscribe(
+                data => {
+                    this.notificationService.success(_('common.notify-create-success'), {
+                        entity: 'Channel',
+                    });
+                    this.detailForm.markAsPristine();
+                    this.changeDetector.markForCheck();
+                    this.router.navigate(['../', data.id], { relativeTo: this.route });
+                },
+                err => {
+                    this.notificationService.error(_('common.notify-create-error'), {
+                        entity: 'Channel',
+                    });
+                },
+            );
     }
 
     save() {
@@ -117,7 +133,7 @@ export class ChannelDetailComponent extends BaseDetailComponent<Channel.Fragment
                 }),
             )
             .subscribe(
-                data => {
+                () => {
                     this.notificationService.success(_('common.notify-update-success'), {
                         entity: 'Channel',
                     });
@@ -138,7 +154,7 @@ export class ChannelDetailComponent extends BaseDetailComponent<Channel.Fragment
     protected setFormValues(entity: Channel.Fragment, languageCode: LanguageCode): void {
         this.detailForm.patchValue({
             code: entity.code,
-            token: entity.token,
+            token: entity.token || this.generateToken(),
             pricesIncludeTax: entity.pricesIncludeTax,
             currencyCode: entity.currencyCode,
             defaultShippingZoneId: entity.defaultShippingZone ? entity.defaultShippingZone.id : '',
@@ -151,4 +167,12 @@ export class ChannelDetailComponent extends BaseDetailComponent<Channel.Fragment
             }
         }
     }
+
+    private generateToken(): string {
+        const randomString = () =>
+            Math.random()
+                .toString(36)
+                .substr(3, 10);
+        return `${randomString()}${randomString()}`;
+    }
 }

+ 21 - 0
packages/admin-ui/src/app/settings/components/channel-list/channel-list.component.html

@@ -11,6 +11,7 @@
 <vdr-data-table [items]="channels$ | async">
     <vdr-dt-column>{{ 'common.code' | translate }}</vdr-dt-column>
     <vdr-dt-column></vdr-dt-column>
+    <vdr-dt-column></vdr-dt-column>
     <ng-template let-channel="item">
         <td class="left align-middle">
             <vdr-channel-badge [channelCode]="channel.code"></vdr-channel-badge>
@@ -23,5 +24,25 @@
                 [linkTo]="['./', channel.id]"
             ></vdr-table-row-action>
         </td>
+        <td class="right align-middle">
+            <vdr-dropdown>
+                <button type="button" class="btn btn-link btn-sm" vdrDropdownTrigger [disabled]="isDefaultChannel(channel.code)">
+                    {{ 'common.actions' | translate }}
+                    <clr-icon shape="caret down"></clr-icon>
+                </button>
+                <vdr-dropdown-menu vdrPosition="bottom-right">
+                    <button
+                        type="button"
+                        class="delete-button"
+                        (click)="deleteChannel(channel.id)"
+                        [disabled]="!('SuperAdmin' | 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>

+ 50 - 3
packages/admin-ui/src/app/settings/components/channel-list/channel-list.component.ts

@@ -1,5 +1,10 @@
 import { ChangeDetectionStrategy, Component } from '@angular/core';
-import { Observable } from 'rxjs';
+import { _ } from '@vendure/admin-ui/src/app/core/providers/i18n/mark-for-extraction';
+import { NotificationService } from '@vendure/admin-ui/src/app/core/providers/notification/notification.service';
+import { ModalService } from '@vendure/admin-ui/src/app/shared/providers/modal/modal.service';
+import { EMPTY, Observable, Subject } from 'rxjs';
+import { mergeMap, startWith, switchMap } from 'rxjs/operators';
+import { DEFAULT_CHANNEL_CODE } from 'shared/shared-constants';
 
 import { Channel } from '../../../common/generated-types';
 import { DataService } from '../../../data/providers/data.service';
@@ -12,8 +17,50 @@ import { DataService } from '../../../data/providers/data.service';
 })
 export class ChannelListComponent {
     channels$: Observable<Channel.Fragment[]>;
+    private refresh$ = new Subject();
 
-    constructor(private dataService: DataService) {
-        this.channels$ = this.dataService.settings.getChannels().mapStream(data => data.channels);
+    constructor(
+        private dataService: DataService,
+        private modalService: ModalService,
+        private notificationService: NotificationService,
+    ) {
+        this.channels$ = this.refresh$.pipe(
+            startWith(1),
+            switchMap(() => this.dataService.settings.getChannels().mapStream(data => data.channels)),
+        );
+    }
+
+    isDefaultChannel(channelCode: string): boolean {
+        return channelCode === DEFAULT_CHANNEL_CODE;
+    }
+
+    deleteChannel(id: string) {
+        this.modalService
+            .dialog({
+                title: _('catalog.confirm-delete-channel'),
+                buttons: [
+                    { type: 'seconday', label: _('common.cancel') },
+                    { type: 'danger', label: _('common.delete'), returnValue: true },
+                ],
+            })
+            .pipe(
+                switchMap(response => (response ? this.dataService.settings.deleteChannel(id) : EMPTY)),
+                mergeMap(() => this.dataService.auth.currentUser().single$),
+                // tslint:disable-next-line:no-non-null-assertion
+                mergeMap(data => this.dataService.client.updateUserChannels(data.me!.channels)),
+            )
+            .subscribe(
+                () => {
+                    this.notificationService.success(_('common.notify-delete-success'), {
+                        entity: 'Channel',
+                    });
+                    this.refresh$.next(1);
+                },
+                err => {
+                    this.notificationService.error(_('common.notify-delete-error'), {
+                        entity: 'Channel',
+                    });
+                },
+            );
     }
 }