diff --git a/apps/proxy/src/app/features/create-feature/create-feature.component.html b/apps/proxy/src/app/features/create-feature/create-feature.component.html index d4199616..1d818523 100644 --- a/apps/proxy/src/app/features/create-feature/create-feature.component.html +++ b/apps/proxy/src/app/features/create-feature/create-feature.component.html @@ -49,7 +49,7 @@

Add New Blocks

(selectionChange)="stepChange($event)" *ngIf="!isEditMode" > - + Block Name -
- -
- + - -
-
-
- - Authorization Setup -
+
-
-
- - - - Branding -
- -
- - - + +
+
+
Design & code
@@ -320,7 +296,7 @@

Add New Blocks

- +
@@ -340,16 +316,6 @@

Add New Blocks

- -
- -
- -
-
-
@@ -455,7 +421,7 @@

Add New Blocks

- +

Give a name of your Block

@@ -467,33 +433,15 @@

Add New Blocks

fieldConfig: { label: 'Name', is_required: true, - patternErrorText: 'Name must contain only alphanumeric and underscore', - info: 'Internal name to identify this authentication block.' + patternErrorText: 'Name must contain only alphanumeric and underscore' } } " > - - -
- +
@@ -618,247 +566,144 @@

Custom Mapping

- -
- -
- -
- - -
-
- -
- -

Credentials

- -
-
- +
+
+ + + +

Credentials

+ +
+
+ +
-
+ - - -

Configurations

-
-
- +

Configurations

+ +
+
+ +
-
+ - - - Callback URL - - - - - Enable Service - - - - + + Callback URL + + + + + Enable Service + + + + +
+
+
+
- -
-

{{ getSelectedServiceName() }}

- -
- - - - - - - -
- - -
- - - - - - - - - - - - - - - - - - - - -
Method{{ row.name }}Toggle - - - (default) - Configure - -
-
No methods available
-
-

- Note: By default, 36Blocks credentials will be used, and the consent screen or Sender ID - will display the 36Blocks name. You can update these with your own credentials and branding anytime after - the block is created. -

-
-
- - +
- -
- -

- {{ featureForm.get('brandingDetails.title')?.value }} -

-
-
+
+

Sign in to your account

+
- - - + + + - + - + - + - - + +

- Are you a new user? - {{ featureForm.get('brandingDetails.sign_up_button_text')?.value }} + Are you a new user? Create an account

- +
@@ -874,9 +719,8 @@

Sign in @@ -884,11 +728,11 @@

+
Or continue with
@@ -932,7 +774,7 @@

@@ -948,19 +790,19 @@

phone_android Login With OTP
@@ -969,19 +811,19 @@

apple Continue with Apple
@@ -990,13 +832,13 @@

Google Continue with Google
@@ -1004,23 +846,23 @@

password Continue with Password
@@ -1033,6 +875,23 @@

+ + + + + + + + + + + + +
+ Show Create Account For New User: + + +
+
+ + +
+ Show the social login as a icon: + + +
+
+
Block new user sign-ups: @@ -1111,6 +1020,32 @@

+
+
+ + +
+

@@ -1282,24 +1217,6 @@

- - - info - - {{ fieldConfig?.hint }} - - - info - - - + {{ fieldConfig?.hint }}
Taxes

- - -
- -
-

Customize your login UI

- -
- - - Enter URL - - Upload file - info - - -
- - Logo URL - - -
- - - - {{ logoFileInput.files?.[0]?.name || 'Logo selected' }} - - -
- - - - - - - - - - - - - - - - -
-
- Show the social login as a icon: - - -
- -
- Show Create Account For New User: - - -
- -
- - - {{ - featureForm.get('brandingDetails.light_theme_primary_color')?.value || '#000000' - }} - (Light theme) -
- -
- - - {{ - featureForm.get('brandingDetails.dark_theme_primary_color')?.value || '#ffffff' - }} - (Dark theme) -
- -
- - - {{ - featureForm.get('brandingDetails.button_color')?.value || '#19E6CE' - }} -
- -
- - - {{ - featureForm.get('brandingDetails.button_hover_color')?.value || '#19E6CE' - }} -
- -
- - - {{ - featureForm.get('brandingDetails.button_text_color')?.value || '#000000' - }} -
-
-
- -
-
-
- - -
-
- -
-
-
diff --git a/apps/proxy/src/app/features/create-feature/create-feature.component.scss b/apps/proxy/src/app/features/create-feature/create-feature.component.scss index 1afd4dda..80f39a9c 100644 --- a/apps/proxy/src/app/features/create-feature/create-feature.component.scss +++ b/apps/proxy/src/app/features/create-feature/create-feature.component.scss @@ -21,28 +21,6 @@ width: 250px; } -.configure-method-dialog-content { - max-height: 65vh; - overflow-y: auto; -} - -// Show Configure column edit button only on row hover -.configure-methods-table { - tr.mat-row, - tr.mat-mdc-row { - .mat-column-configure button { - opacity: 0; - transition: opacity 0.15s ease; - } - &:hover .mat-column-configure button { - opacity: 1; - } - } - th { - font-size: var(--font-size-common-14) !important; - } -} - // Fix table borders .default-table { .mat-mdc-row { @@ -431,6 +409,7 @@ .info-icon { color: var(--color-common-primary, #1976d2); font-size: 17px; + cursor: help; opacity: 0.7; transition: opacity 0.2s ease-in-out; position: absolute; @@ -596,6 +575,7 @@ color: #ffffff !important; } .mat-form-field-outline-thick { + background-color: #fffcfc !important; color: #ffffff !important; } .mat-form-field-outline-start, @@ -913,69 +893,3 @@ } } } - -// Branding step: apply border radius to all preview elements (variable --branding-border-radius set on .auth-credentials) -.auth-credentials { - border-radius: var(--branding-border-radius, 8px) !important; - // mat-form-field and other elements use --border-common-radius-4; override with branding radius - --border-common-radius-4: var(--branding-border-radius, 8px); -} -.auth-credentials .branding-preview-btn { - background-color: var(--branding-button-color, #19e6ce) !important; - color: var(--branding-button-text-color, #000000) !important; - border-radius: var(--branding-border-radius, 8px) !important; -} -.auth-credentials .branding-preview-btn:hover { - background-color: var(--branding-button-hover-color, #19e6ce) !important; -} -.auth-credentials .auth-option-btn { - border-radius: var(--branding-border-radius, 8px) !important; -} -.auth-credentials .social-login-icon-box { - border-radius: var(--branding-border-radius, 8px) !important; -} - -// Email or Phone & Password fields in preview: apply user's border radius to outline -::ng-deep .auth-credentials .branding-preview-input .mdc-text-field--outlined { - --mdc-outlined-text-field-container-shape: var(--branding-border-radius, 8px) !important; -} -::ng-deep .auth-credentials .branding-preview-input .mdc-notched-outline .mdc-notched-outline__leading { - border-top-left-radius: var(--branding-border-radius, 8px) !important; - border-bottom-left-radius: var(--branding-border-radius, 8px) !important; -} -::ng-deep .auth-credentials .branding-preview-input .mdc-notched-outline .mdc-notched-outline__trailing { - border-top-right-radius: var(--branding-border-radius, 8px) !important; - border-bottom-right-radius: var(--branding-border-radius, 8px) !important; -} -::ng-deep .auth-credentials .branding-preview-input .mdc-notched-outline .mdc-notched-outline__notch { - border-top-left-radius: var(--branding-border-radius, 8px) !important; - border-top-right-radius: var(--branding-border-radius, 8px) !important; -} - -.branding-preview-logo { - max-height: 48px; - max-width: 160px; - object-fit: contain; -} - -.branding-step-content { - min-height: 400px; -} - -// Use app green theme for logo radio buttons (instead of default blue) -::ng-deep .branding-logo-radio { - .mat-mdc-radio-button.mat-mdc-radio-checked .mdc-radio__outer-circle, - .mat-mdc-radio-button .mdc-radio__outer-circle { - border-color: var(--color-dark-accent) !important; - } - .mat-mdc-radio-button.mat-mdc-radio-checked .mdc-radio__inner-circle { - border-color: var(--color-dark-accent) !important; - background-color: var(--color-dark-accent) !important; - } - .mat-mdc-radio-button .mdc-radio__background::before { - background-color: var(--color-dark-accent) !important; - } -} -.mat-tab-body-wrapper { - height: 100%; -} diff --git a/apps/proxy/src/app/features/create-feature/create-feature.component.ts b/apps/proxy/src/app/features/create-feature/create-feature.component.ts index 21480d06..95bb7b8f 100644 --- a/apps/proxy/src/app/features/create-feature/create-feature.component.ts +++ b/apps/proxy/src/app/features/create-feature/create-feature.component.ts @@ -1,24 +1,11 @@ import { ActivatedRoute } from '@angular/router'; import { cloneDeep, isEqual } from 'lodash-es'; -import { - Component, - OnDestroy, - OnInit, - NgZone, - ViewChildren, - QueryList, - ChangeDetectorRef, - ViewChild, - ElementRef, - AfterViewInit, - TemplateRef, -} from '@angular/core'; +import { Component, OnDestroy, OnInit, NgZone, ViewChildren, QueryList, ChangeDetectorRef } from '@angular/core'; import { BaseComponent } from '@proxy/ui/base-component'; import { BehaviorSubject, Observable, distinctUntilChanged, filter, of, take, takeUntil } from 'rxjs'; import { CreateFeatureComponentStore } from './create-feature.store'; import { FeatureFieldType, - FeatureServiceIds, IFeature, IFeatureDetails, IFeatureType, @@ -28,7 +15,7 @@ import { ProxyAuthScript, ProxyAuthScriptUrl, } from '@proxy/models/features-model'; -import { AbstractControl, FormArray, FormControl, FormGroup, Validators, ValidatorFn } from '@angular/forms'; +import { FormArray, FormControl, FormGroup, Validators, ValidatorFn } from '@angular/forms'; import { CAMPAIGN_NAME_REGEX, ONLY_INTEGER_REGEX, URL_REGEX } from '@proxy/regex'; import { CustomValidators } from '@proxy/custom-validator'; import { COMMA, ENTER } from '@angular/cdk/keycodes'; @@ -87,12 +74,8 @@ export interface PeriodicElement { styleUrls: ['./create-feature.component.scss'], providers: [CreateFeatureComponentStore], }) -export class CreateFeatureComponent extends BaseComponent implements OnDestroy, OnInit, AfterViewInit { +export class CreateFeatureComponent extends BaseComponent implements OnDestroy, OnInit { @ViewChildren('stepper') stepper: QueryList; - @ViewChild('blockNameStepContent', { read: ElementRef }) blockNameStepContent: ElementRef; - @ViewChild('authorizationStepContent', { read: ElementRef }) authorizationStepContent: ElementRef; - @ViewChild('configureMethodDialogTemplate', { read: TemplateRef }) - configureMethodDialogTemplateRef: TemplateRef; public taxes: any[] = []; public createPlanForm: any; public taxConfigData: any; @@ -149,15 +132,11 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, public paymentDetailsById$: Observable = this.componentStore.paymentDetailsById$; public updatePaymentDetails$: Observable = this.componentStore.updatePaymentDetails$; public webhookEvents$: Observable = this.componentStore.webhookEvents$; - public uploadLogo$: Observable = this.componentStore.uploadLogo$; public isEditMode = false; public previewInputPosition: 'top' | 'bottom' = 'top'; public selectedServiceIndex = 0; public selectedSubscriptionServiceIndex = -2; - /** Duplicate of service form used inside configure method dialog; patched back to serviceForm on close */ - public configureMethodDialogForm: ServiceFormGroup | null = null; - public selectedMethod = new BehaviorSubject(null); public featureId: number = null; public nameFieldEditMode = false; @@ -171,12 +150,6 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, public featureFieldType = FeatureFieldType; public proxyAuthScript = ProxyAuthScript(environment.proxyServer); - public configureMethodsTableColumns: string[] = ['method', 'toggle', 'configure']; - - /** Table data for Configure Method step (create block). Set when method loads so dataSource is stable and toggle binding works. */ - public configureMethodsTableData: { name: string; index: number }[] = []; - - public configureMethodDialog: MatDialogRef; // Chip list public chipListSeparatorKeysCodes: number[] = [ENTER, COMMA]; public chipListValues: { [key: string]: Set } = {}; @@ -185,14 +158,8 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, // File public fileValues: { [key: string]: FileList } = {}; - /** Logo input: 'url' = enter URL, 'file' = upload file */ - public logoInputMode: 'url' | 'file' = 'url'; - - public isLogoUploading = false; - // Options cache for select fields private optionsCache: { [key: string]: any[] } = {}; - public logoUrl: string = null; public featureForm = new FormGroup({ primaryDetails: new FormGroup({ @@ -203,9 +170,8 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, CustomValidators.noStartEndSpaces, Validators.maxLength(60), ]), - feature_id: new FormControl(1, [Validators.required]), + feature_id: new FormControl(null, [Validators.required]), method_id: new FormControl(null, [Validators.required]), - redirect_url: new FormControl(null, [Validators.required, Validators.pattern(URL_REGEX)]), }), serviceDetails: new FormArray([]), planDetails: new FormArray([]), @@ -215,17 +181,22 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, planSelected: new FormControl(null, [Validators.required]), }), authorizationDetails: new FormGroup({ - session_time: new FormControl(86400, [ + session_time: new FormControl(null, [ Validators.required, Validators.pattern(ONLY_INTEGER_REGEX), Validators.min(60), Validators.max(999999999), ]), - authorizationKey: new FormControl('Authorization', [ + authorizationKey: new FormControl(null, [ Validators.required, CustomValidators.minLengthThreeWithoutSpace, ]), + theme: new FormControl('system', []), + version: new FormControl('v1', []), + allowNewUserRegistration: new FormControl(false, []), encryptionKey: new FormControl(null, []), + redirect_url: new FormControl(null, [Validators.required, Validators.pattern(URL_REGEX)]), + showSocialLoginIcons: new FormControl(false, []), blockNewUserSignUps: new FormControl(false, []), }), webhookDetails: new FormGroup({ @@ -233,21 +204,6 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, method: new FormControl('POST', [Validators.required]), triggerEvents: new FormControl([], []), }), - brandingDetails: new FormGroup({ - icons: new FormControl(false, []), - logo_url: new FormControl(null, []), - light_theme_primary_color: new FormControl('#000000', []), - dark_theme_primary_color: new FormControl('#ffffff', []), - button_color: new FormControl('#19E6CE', []), - button_hover_color: new FormControl('#19E6CE', []), - button_text_color: new FormControl('#000000', []), - border_radius: new FormControl('small', []), - title: new FormControl('Sign in to your account', []), - sign_up_button_text: new FormControl('Create an account', [Validators.required]), - create_account_link: new FormControl(true, []), - theme: new FormControl('system', []), - version: new FormControl('v2', []), - }), // New form controls for conditional steps }); public demoDiv$: Observable = of(null); @@ -273,8 +229,6 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, this.getFeatureDetalis(); } else { this.getFeatureType(); - this.featureForm.get('brandingDetails.light_theme_primary_color')?.setValue('#000000'); - this.featureForm.get('brandingDetails.dark_theme_primary_color')?.setValue('#ffffff'); } }); @@ -329,6 +283,10 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, authorizationDetails: { session_time: feature.session_time, authorizationKey: feature.authorization_format.key, + theme: feature.ui_preferences?.theme || 'system', + version: feature.ui_preferences?.version || 'v1', + showSocialLoginIcons: feature.ui_preferences?.icons || false, + allowNewUserRegistration: feature.ui_preferences?.create_account_link || false, encryptionKey: feature.encryption_key, blockNewUserSignUps: feature.block_registration || false, }, @@ -337,26 +295,10 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, method: feature.webhook?.method, triggerEvents: feature.trigger_events || feature.webhook_events || [], }, - brandingDetails: { - logo_url: feature.ui_preferences?.logo_url, - light_theme_primary_color: feature.ui_preferences?.light_theme_primary_color, - dark_theme_primary_color: feature.ui_preferences?.dark_theme_primary_color, - button_color: feature.ui_preferences?.button_color, - button_hover_color: feature.ui_preferences?.button_hover_color, - button_text_color: - feature.ui_preferences?.button_text_color ?? feature.ui_preferences?.text_color, - border_radius: feature.ui_preferences?.border_radius, - title: feature.ui_preferences?.title, - sign_up_button_text: feature.ui_preferences?.sign_up_button_text ?? 'Create an account', - icons: feature.ui_preferences?.icons ?? false, - create_account_link: feature.ui_preferences?.create_account_link ?? true, - theme: feature.ui_preferences?.theme || 'system', - version: feature.ui_preferences?.version || 'v1', - }, }); // Clear redirect_url validators in edit mode since the field is hidden - this.featureForm.get('primaryDetails.redirect_url')?.clearValidators(); - this.featureForm.get('primaryDetails.redirect_url')?.updateValueAndValidity(); + this.featureForm.get('authorizationDetails.redirect_url')?.clearValidators(); + this.featureForm.get('authorizationDetails.redirect_url')?.updateValueAndValidity(); this.previewInputPosition = feature.ui_preferences?.input_fields || 'top'; }); }); @@ -384,12 +326,7 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, configurations: new FormGroup({}), createPlanForm: new FormGroup({}), chargesForm: new FormGroup({}), - is_enable: new FormControl( - this.isEditMode - ? serviceValues?.is_enable - : FeatureServiceIds.GoogleAuthentication === service.service_id || - FeatureServiceIds.Msg91OtpService === service.service_id - ), + is_enable: new FormControl(this.isEditMode ? serviceValues?.is_enable : true), }); if (service.requirements) { Object.entries(service.requirements).forEach(([key, config]) => { @@ -415,15 +352,6 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, } this.featureForm.controls.serviceDetails.push(serviceFormGroup); }); - this.configureMethodsTableData = method.method_services.map((s, i) => ({ name: s.name, index: i })); - // After building service details for authorization block, sync redirect_url into all service redirect_uri fields - if ( - !this.isEditMode && - this.featureForm.get('primaryDetails.feature_id')?.value === 1 && - this.featureForm.get('primaryDetails.redirect_url')?.value - ) { - this.setRedirectUrlInServiceDetails(); - } }); this.createUpdateObject$.pipe(filter(Boolean), takeUntil(this.destroy$)).subscribe((obj) => { this.proxyAuthScript = ProxyAuthScript( @@ -448,8 +376,6 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, this.stepper?.first?.next(); }, 10); } - this.featureId = obj.id; - this.demoDiv$ = of(`
`); }); this.createBillableMetric$.pipe(filter(Boolean), takeUntil(this.destroy$)).subscribe((metric) => { @@ -576,110 +502,14 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, } } }); - this.uploadLogo$.pipe(filter(Boolean), takeUntil(this.destroy$)).subscribe((data) => { - const url = data?.logo_url ?? data?.url; - if (url) { - // this.featureForm.get('brandingDetails.logo_url')?.setValue(url); - this.logoUrl = url; - } - this.isLogoUploading = false; - this.cdr.markForCheck(); - }); - } - - public validateFirstStepAndNext(nameControl: AbstractControl): void { - nameControl.markAllAsTouched(); - const featureId = this.featureForm.get('primaryDetails.feature_id')?.value; - if (featureId === 1) { - this.featureForm.get('primaryDetails.redirect_url')?.markAllAsTouched(); - } - if (nameControl.invalid) { - return; - } - if (featureId === 1) { - const redirectUrlControl = this.featureForm.get('primaryDetails.redirect_url'); - if (redirectUrlControl?.invalid) { - return; - } - // Sync redirect_url into all service redirect_uri fields before showing Configure Method step - this.setRedirectUrlInServiceDetails(); - } - this.stepper?.first?.next(); - } - - public get validateFirstStep(): boolean { - const nameControl = this.featureForm.get('primaryDetails.name'); - const featureId = this.featureForm.get('primaryDetails.feature_id')?.value; - const redirectUrlControl = this.featureForm.get('primaryDetails.redirect_url'); - if (nameControl.invalid || redirectUrlControl?.invalid) { - return false; - } - if (featureId === 1) { - const redirectUrlControl = this.featureForm.get('primaryDetails.redirect_url'); - if (redirectUrlControl?.invalid) { - return false; - } - // Sync redirect_url into all service redirect_uri fields before showing Configure Method step - this.setRedirectUrlInServiceDetails(); - } - return true; - } - - /** Default primary color: black in light theme, white in dark theme */ - public getDefaultPrimaryColor(): string { - return typeof localStorage !== 'undefined' && localStorage.getItem('selected-theme') === 'dark-theme' - ? '#ffffff' - : '#000000'; - } - - /** Returns the effective primary color based on the selected branding theme */ - public getEffectivePrimaryColor(): string { - const theme = this.featureForm.get('brandingDetails.theme')?.value; - const isDark = - theme === 'dark' || - (theme === 'system' && - typeof localStorage !== 'undefined' && - localStorage.getItem('selected-theme') === 'dark-theme'); - if (isDark) { - return this.featureForm.get('brandingDetails.dark_theme_primary_color')?.value || '#ffffff'; - } - return this.featureForm.get('brandingDetails.light_theme_primary_color')?.value || '#000000'; - } - - public getBrandingBorderRadiusValue(): string { - const v = this.featureForm.get('brandingDetails.border_radius')?.value; - switch (v) { - case 'none': - return '0'; - case 'small': - return '4px'; - case 'medium': - return '8px'; - case 'large': - return '12px'; - default: - return '8px'; - } - } - - public onBrandingLogoFileSelected(event: Event): void { - const input = event.target as HTMLInputElement; - const file = input?.files?.[0]; - if (!file || !this.featureId) return; - input.value = ''; - const formData = new FormData(); - formData.append('logo', file); - this.isLogoUploading = true; - this.cdr.markForCheck(); - this.componentStore.uploadLogo({ id: this.featureId, formData }); } public setRedirectUrlInServiceDetails(): void { const serviceDetailsForm = this.featureForm.controls.serviceDetails; - const redirectUrl = this.featureForm.controls.primaryDetails.value.redirect_url; + const redirectUrl = this.featureForm.controls.authorizationDetails.value.redirect_url; serviceDetailsForm.controls.forEach((formGroup) => { const redirectUrlControl = formGroup.controls.configurations.controls['redirect_uri'] as FormControl; - if (redirectUrlControl && !redirectUrlControl.value) { + if (redirectUrlControl) { redirectUrlControl.setValue(redirectUrl); formGroup.markAsDirty(); } @@ -701,13 +531,10 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, }, session_time: featureFormData.authorizationDetails.session_time, extra_configurations: { - theme: featureFormData.brandingDetails.theme, - create_account_link: featureFormData.brandingDetails.create_account_link || false, + theme: featureFormData.authorizationDetails.theme || 'system', + allowNewUserRegistration: featureFormData.authorizationDetails.allowNewUserRegistration || false, }, services: this.getServicePayload(selectedMethod), - ui_preferences: { - ...featureFormData.brandingDetails, - }, }; // Added setTimeout because payload creation might contain promises setTimeout(() => { @@ -716,12 +543,10 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, } } - public updateFeature(type: 'name' | 'service' | 'authorization' | 'branding' | 'webhook') { + public updateFeature(type: 'name' | 'service' | 'authorization' | 'webhook') { let payload; const selectedMethod = cloneDeep(this.selectedMethod.getValue()); const featureDetails: IFeatureDetails = this.getValueFromObservable(this.featureDetails$); - const brandingDetailsForm = this.featureForm.controls.brandingDetails; - switch (type) { case 'name': const primaryDetailsForm = this.featureForm.controls.primaryDetails; @@ -739,8 +564,8 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, if (authorizationDetailsForm.valid) { payload = { extra_configurations: { - theme: brandingDetailsForm.controls.theme.value, - create_account_link: brandingDetailsForm.controls.create_account_link.value || false, + theme: authorizationDetailsForm.value.theme, + create_account_link: authorizationDetailsForm.value.allowNewUserRegistration || false, default_role: { name: 'Owner', value: 1, @@ -752,13 +577,13 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, ...featureDetails.authorization_format, key: authorizationDetailsForm.value.authorizationKey, }, - // ui_preferences: { - // theme: brandingDetailsForm.controls.theme.value || 'system', - // create_account_link: brandingDetailsForm.controls.create_account_link.value || false, - // icons: brandingDetailsForm.controls.icons.value || false, - // version: brandingDetailsForm.controls.version.value || 'v1', - // input_fields: this.previewInputPosition, - // }, + ui_preferences: { + theme: authorizationDetailsForm.value.theme, + create_account_link: authorizationDetailsForm.value.allowNewUserRegistration || false, + icons: authorizationDetailsForm.value.showSocialLoginIcons || false, + version: authorizationDetailsForm.value.version, + input_fields: this.previewInputPosition, + }, session_time: authorizationDetailsForm.value.session_time, }; } else { @@ -776,25 +601,6 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, return; } break; - case 'branding': - if (brandingDetailsForm.valid) { - const existingUiPrefs = (featureDetails as unknown as Record)?.ui_preferences as - | Record - | undefined; - const { primary_color: _removed, ...cleanedUiPrefs } = existingUiPrefs || ({} as any); - payload = { - ui_preferences: { - ...cleanedUiPrefs, - ...brandingDetailsForm.value, - input_fields: this.previewInputPosition, - logo_url: this.logoInputMode === 'file' ? this.logoUrl : brandingDetailsForm.value.logo_url, - }, - }; - } else { - brandingDetailsForm.markAllAsTouched(); - return; - } - break; case 'webhook': const webhookDetailsForm = this.featureForm.controls.webhookDetails; @@ -857,51 +663,6 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, if (!this.isEditMode && event?.previouslySelectedIndex === 0) { this.getServiceMethods(this.featureForm.value.primaryDetails.feature_id); } - // When entering Block Name step (index 0), focus the name input if empty - if (!this.isEditMode && event?.selectedIndex === 0) { - setTimeout(() => this.focusBlockNameInputIfEmpty(), 0); - } - // When entering Authorization Setup step (index 3), focus session time input if empty - if ( - !this.isEditMode && - this.featureForm.get('primaryDetails.feature_id')?.value === 1 && - event?.selectedIndex === 3 - ) { - setTimeout(() => this.focusSessionTimeInputIfEmpty(), 0); - } - // When entering Configure Method (step 1) for authorization block, ensure redirect_url is synced to all service redirect_uri fields - if ( - !this.isEditMode && - this.featureForm.get('primaryDetails.feature_id')?.value === 1 && - event?.selectedIndex === 1 - ) { - this.setRedirectUrlInServiceDetails(); - } - } - - public ngAfterViewInit(): void { - if (!this.isEditMode && this.stepper?.first?.selectedIndex === 0) { - setTimeout(() => this.focusBlockNameInputIfEmpty(), 100); - } - } - - private focusBlockNameInputIfEmpty(): void { - const nameValue = this.featureForm.get('primaryDetails.name')?.value; - if (nameValue != null && String(nameValue).trim() !== '') { - return; - } - this.blockNameStepContent?.nativeElement?.querySelector('input')?.focus(); - } - - private focusSessionTimeInputIfEmpty(): void { - const sessionTimeValue = this.featureForm.get('authorizationDetails.session_time')?.value; - const hasValue = - sessionTimeValue != null && - (typeof sessionTimeValue === 'string' ? String(sessionTimeValue).trim() !== '' : true); - if (hasValue) { - return; - } - this.authorizationStepContent?.nativeElement?.querySelector('input')?.focus(); } public ngOnDestroy(): void { @@ -1040,13 +801,8 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, this.featureForm.controls.planDetails.push(planFormGroup); } - public resetFormGroup(formGroup: FormGroup | null | undefined, index: number): void { - if (!formGroup) return; - const isEnableValue = formGroup.get('is_enable')?.value; + public resetFormGroup(formGroup: FormGroup, index: number): void { formGroup.reset(); - if (formGroup.get('is_enable') && isEnableValue !== undefined) { - formGroup.get('is_enable')?.setValue(isEnableValue); - } Object.keys(this.chipListValues) .filter((key) => +key.split('_')[1] === index) .forEach((key) => (this.chipListValues[key] = new Set(this.chipListReadOnlyValues[key]))); @@ -1055,36 +811,6 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, .forEach((key) => (this.fileValues[key] = null)); } - /** Deep-clone a FormGroup (and nested FormGroups/FormArrays/FormControls) for use in dialog. Copies values and validators. */ - private cloneFormGroup(source: FormGroup): FormGroup { - const clone = new FormGroup({}); - Object.keys(source.controls).forEach((key) => { - const control = source.get(key); - if (control instanceof FormGroup) { - clone.addControl(key, this.cloneFormGroup(control)); - } else if (control instanceof FormArray) { - clone.addControl( - key, - new FormArray( - control.controls.map((c) => - c instanceof FormGroup ? this.cloneFormGroup(c) : this.cloneFormControl(c as FormControl) - ) - ) - ); - } else { - clone.addControl(key, this.cloneFormControl(control as FormControl)); - } - }); - return clone; - } - - private cloneFormControl(source: FormControl): FormControl { - return new FormControl(source.value, { - validators: source.validator, - asyncValidators: source.asyncValidator, - }); - } - public updateChipListValues( operation: 'add' | 'delete', chipListKey: string, @@ -2007,50 +1733,4 @@ export class CreateFeatureComponent extends BaseComponent implements OnDestroy, this.http.post(url, sampleResponse).pipe(takeUntil(this.destroy$)).subscribe(); this.toast.success('Sample response sent successfully'); } - - /** Reset only the configure dialog form (duplicate), preserving is_enable. Does not touch chipListValues/fileValues. */ - public resetConfigureDialogForm(): void { - if (!this.configureMethodDialogForm) return; - const isEnableValue = this.configureMethodDialogForm.get('is_enable')?.value; - this.configureMethodDialogForm.reset(); - if (this.configureMethodDialogForm.get('is_enable') && isEnableValue !== undefined) { - this.configureMethodDialogForm.get('is_enable')?.setValue(isEnableValue); - } - } - - public editService(index: number): void { - this.selectedServiceIndex = index; - const serviceDetailsArray = this.featureForm.get('serviceDetails') as FormArray; - const sourceForm = serviceDetailsArray?.at(index) as ServiceFormGroup | null; - if (!sourceForm) return; - this.configureMethodDialogForm = this.cloneFormGroup(sourceForm) as ServiceFormGroup; - this.configureMethodDialog = this.dialog.open(this.configureMethodDialogTemplateRef); - this.configureMethodDialog - .afterClosed() - .pipe(takeUntil(this.destroy$)) - .subscribe((res) => { - if (res) { - if (this.configureMethodDialogForm) { - const targetForm = serviceDetailsArray?.at(this.selectedServiceIndex) as FormGroup | null; - if (targetForm) { - targetForm.patchValue(this.configureMethodDialogForm.getRawValue()); - } - this.configureMethodDialogForm = null; - } - } - }); - } - - public getSelectedServiceName(): string { - const method = this.selectedMethod.getValue(); - return method?.method_services?.[this.selectedServiceIndex]?.name ?? 'Configure Service'; - } - - public closeDialog(): void { - if (this.configureMethodDialogForm?.valid) { - this.configureMethodDialog.close('true'); - } else { - this.configureMethodDialogForm.markAllAsTouched(); - } - } } diff --git a/apps/proxy/src/app/features/create-feature/create-feature.module.ts b/apps/proxy/src/app/features/create-feature/create-feature.module.ts index 554ff593..f3d1b44b 100644 --- a/apps/proxy/src/app/features/create-feature/create-feature.module.ts +++ b/apps/proxy/src/app/features/create-feature/create-feature.module.ts @@ -2,7 +2,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule, Routes } from '@angular/router'; import { MatCardModule } from '@angular/material/card'; -import { ReactiveFormsModule, FormsModule } from '@angular/forms'; +import { ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { UiLoaderModule } from '@proxy/ui/loader'; import { MatIconModule } from '@angular/material/icon'; @@ -26,7 +26,6 @@ import { CreatePlanDialogComponent } from './create-plan-dialog/create-plan-dial import { CreateTaxDialogComponent } from './create-tax-dialog/create-tax-dialog.component'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatExpansionModule } from '@angular/material/expansion'; -import { MatRadioModule } from '@angular/material/radio'; const routes: Routes = [ { @@ -42,7 +41,6 @@ const routes: Routes = [ CommonModule, RouterModule.forChild(routes), ReactiveFormsModule, - FormsModule, MatInputModule, MatFormFieldModule, MatIconModule, @@ -63,7 +61,6 @@ const routes: Routes = [ HttpClientModule, MatTooltipModule, MatExpansionModule, - MatRadioModule, ], exports: [RouterModule], }) diff --git a/apps/proxy/src/app/features/create-feature/create-feature.store.ts b/apps/proxy/src/app/features/create-feature/create-feature.store.ts index c0afe600..635292be 100644 --- a/apps/proxy/src/app/features/create-feature/create-feature.store.ts +++ b/apps/proxy/src/app/features/create-feature/create-feature.store.ts @@ -29,7 +29,6 @@ export interface ICreateFeatureInitialState { paymentDetailsById: any; updatePaymentDetails: any; webhookEvents: any; - uploadLogo: any; } @Injectable() @@ -59,7 +58,6 @@ export class CreateFeatureComponentStore extends ComponentStore = this.select((state) => state.updatePaymentDetails); /** Selector for webhook events data */ readonly webhookEvents$: Observable = this.select((state) => state.webhookEvents); - /** Selector for upload logo data */ - readonly uploadLogo$: Observable = this.select((state) => state.uploadLogo); /** Get feature type data */ readonly getFeatureType = this.effect((data) => { return data.pipe( @@ -694,31 +690,6 @@ export class CreateFeatureComponentStore extends ComponentStore) => { - return data.pipe( - switchMap((req) => { - this.patchState({ isLoading: true, uploadLogo: null }); - return this.service.uploadLogo(req.id, req.formData).pipe( - tapResponse( - (res: BaseResponse) => { - if (res?.hasError) { - this.showError(res.errors); - return this.patchState({ isLoading: false }); - } - this.toast.success('Logo uploaded successfully'); - return this.patchState({ isLoading: false, uploadLogo: res.data }); - }, - (error: any) => { - this.showError(error.errors); - this.patchState({ isLoading: false }); - } - ), - catchError((err) => EMPTY) - ); - }) - ); - }); - private showError(error): void { const errorMessage = errorResolver(error); errorMessage.forEach((error) => { diff --git a/apps/proxy/src/app/users/user/user.component.html b/apps/proxy/src/app/users/user/user.component.html index 3de769b6..2aaec89d 100644 --- a/apps/proxy/src/app/users/user/user.component.html +++ b/apps/proxy/src/app/users/user/user.component.html @@ -124,43 +124,6 @@ - - - User ID - - - {{ element?.id }} - - - - - - - - Block Name - - - {{ element?.feature_configuration?.name }} - - - - - - - Last Login - - - - {{ element?.last_login_at | date: 'd MMM y' }} - - ({{ element?.last_login_at | date: 'mediumTime' }}) - - - - Created At diff --git a/apps/proxy/src/app/users/user/user.component.ts b/apps/proxy/src/app/users/user/user.component.ts index f080a95d..f0d3191a 100644 --- a/apps/proxy/src/app/users/user/user.component.ts +++ b/apps/proxy/src/app/users/user/user.component.ts @@ -34,15 +34,7 @@ export class UserComponent extends BaseComponent implements OnDestroy, OnInit { /** Store User data */ public users$: Observable> = this.componentStore.users$; /** Store display column */ - public displayedColumns: string[] = [ - 'name', - 'email', - 'phone_number', - 'user_id', - 'block_name', - 'last_login', - 'created_at', - ]; + public displayedColumns: string[] = ['name', 'email', 'phone_number', 'created_at']; /** Store params for fetching feature data */ public params: any = {}; /** Store default selected date range */ diff --git a/apps/proxy/src/assets/scss/component/_buttons.scss b/apps/proxy/src/assets/scss/component/_buttons.scss index 71c8f767..a11162b5 100644 --- a/apps/proxy/src/assets/scss/component/_buttons.scss +++ b/apps/proxy/src/assets/scss/component/_buttons.scss @@ -297,30 +297,4 @@ button.mat-btn-md.mat-btn-wran:hover[disabled] { background-color: var(--color-common-slate) !important; } } -} -.radio-button-custom-style { - .mat-radio-button { - .mat-radio-outer-circle { - border-color: var(--color-dark-accent) !important; - } - - .mat-radio-inner-circle { - background-color: var(--color-dark-accent) !important; - } - } -} -.accent-color { - .material-icons { - &.mat-icon { - &.mat-icon-no-color { - color: var(--color-dark-accent) !important; - &:hover { - color: var(--color-dark-accent) !important; - } - } - } - } -} -.accent-bg-color { - background-color: var(--color-dark-accent) !important; } \ No newline at end of file diff --git a/apps/proxy/src/styles.scss b/apps/proxy/src/styles.scss index 2cf1da14..060a77ff 100644 --- a/apps/proxy/src/styles.scss +++ b/apps/proxy/src/styles.scss @@ -105,9 +105,3 @@ canvas { width: 100%; } } - -// .block-feature-tabs { -// .mat-tab-body-wrapper { -// height: 100%; -// } -// } diff --git a/libs/models/features-model/src/index.ts b/libs/models/features-model/src/index.ts index 46c8c956..6003e565 100644 --- a/libs/models/features-model/src/index.ts +++ b/libs/models/features-model/src/index.ts @@ -98,7 +98,6 @@ export enum FeatureFieldType { ReadFile = 'readFile', Select = 'select', TextArea = 'textarea', - Password = 'password', } export const ProxyAuthScript = ( @@ -133,7 +132,6 @@ export enum FeatureServiceIds { Msg91OtpService = 6, GoogleAuthentication = 7, PasswordAuthentication = 9, - AppleAuthentication = 8, } export const ProxyUserManagementScript = ( baseUrl: string, diff --git a/libs/services/proxy/features/src/lib/services-proxy-features.module.ts b/libs/services/proxy/features/src/lib/services-proxy-features.module.ts index f165e2b0..3dcc3e01 100644 --- a/libs/services/proxy/features/src/lib/services-proxy-features.module.ts +++ b/libs/services/proxy/features/src/lib/services-proxy-features.module.ts @@ -122,10 +122,4 @@ export class FeaturesService { public getWebhookEvents(): Observable> { return this.http.get>(FeaturesUrls.getWebhookEvents(this.baseURL)); } - - public uploadLogo(id: string | number, formData: FormData): Observable> { - return this.http.post>(FeaturesUrls.uploadLogo(this.baseURL, id), formData, { - headers: {}, - }); - } } diff --git a/libs/urls/features-url/src/index.ts b/libs/urls/features-url/src/index.ts index c1fde911..373f99ee 100644 --- a/libs/urls/features-url/src/index.ts +++ b/libs/urls/features-url/src/index.ts @@ -20,5 +20,4 @@ export const FeaturesUrls = { getPaymentDetailsFormById: (baseUrl, refid) => createUrl(baseUrl, `${refid}/paymentForm`), updatePaymentDetails: (baseUrl, refid) => createUrl(baseUrl, `subscription/${refid}/updateCredentials`), getWebhookEvents: (baseUrl) => createUrl(baseUrl, `getWebhookTriggerEvents`), - uploadLogo: (baseUrl, id) => createUrl(baseUrl, `features/${id}/upload-logo`), };