Skip to content
Merged

Stage #656

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 112 additions & 48 deletions apps/36-blocks-widget/src/app/otp/widget/widget.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,12 @@ export class ProxyAuthWidgetComponent extends BaseComponent implements OnInit, O
private hcaptchaLoading: boolean = false;
private hcaptchaRenderQueue: Array<() => void> = [];
public isUserProxyContainer: boolean = true;
/** Host page URL where this widget script is embedded. */
public hostPageUrl: string = '';
/** Origin of the host page (protocol + host). */
public hostPageOrigin: string = '';
/** Absolute URL of the proxy-auth.js script tag, if found. */
public widgetScriptUrl: string = '';

constructor() {
super();
Expand Down Expand Up @@ -235,6 +241,7 @@ export class ProxyAuthWidgetComponent extends BaseComponent implements OnInit, O
}

ngOnInit() {
this.captureHostPageUrl();
this.store.dispatch(resetAll());
this._authToken$.set(this.authToken);
this._type$.set(this.type);
Expand Down Expand Up @@ -359,6 +366,41 @@ export class ProxyAuthWidgetComponent extends BaseComponent implements OnInit, O
});
}

/**
* Reads the page URL where the widget is loaded, plus the script src if available.
* Uses standard browser APIs — works for both CDN embeds and local serve.
*/
private captureHostPageUrl(): void {
this.hostPageUrl = window.location?.href ?? '';
this.hostPageOrigin = window.location?.origin ?? '';
this.widgetScriptUrl =
(document.currentScript as HTMLScriptElement | null)?.src ||
(document.querySelector('script[src*="proxy-auth"]') as HTMLScriptElement | null)?.src ||
'';
console.log('[36Blocks] Host page URL:', this.hostPageUrl);
}

/** True when the host page URL looks like a register / sign-up route. */
private isSignupRoute(): boolean {
const path = `${window.location?.pathname ?? ''} ${window.location?.href ?? ''}`.toLowerCase();
return /sign[-_]?up|register|registration|signup/.test(path);
}

/**
* On register/sign-up routes, rewrite login-oriented auth button labels to "Sign up".
* e.g. "Continue with Google" → "Sign up with Google"
* "Login with OTP" → "Sign up with OTP"
*/
private getAuthButtonText(text: string): string {
if (!text || !this.isSignupRoute()) {
return text;
}
return text
.replace(/\bcontinue\b/gi, 'Sign up')
.replace(/\blog[\s-]?in\b/gi, 'Sign up')
.replace(/\bsign[\s-]?in\b/gi, 'Sign up');
}

private loadExternalFonts() {
const node = document.querySelector('proxy-auth')?.shadowRoot;
const styleElement = document.createElement('link');
Expand Down Expand Up @@ -667,11 +709,20 @@ export class ProxyAuthWidgetComponent extends BaseComponent implements OnInit, O
}
});
} else {
if (
buttonsData?.service_id !== FeatureServiceIds.PasswordAuthentication ||
(buttonsData?.service_id === FeatureServiceIds.PasswordAuthentication &&
this.version === 'v1')
) {
const isPasswordAuth = buttonsData?.service_id === FeatureServiceIds.PasswordAuthentication;

// On signup/register routes, skip password login entirely.
if (isPasswordAuth && this.isSignupRoute()) {
buttonsProcessed++;
this.checkAndAppendCreateAccountText(
element,
buttonsProcessed,
totalButtons,
fallbackTimeout,
immediateFallback,
otpTimeout
);
} else if (!isPasswordAuth || this.version === 'v1') {
this.appendButton(element, buttonsData);
buttonsProcessed++;
this.checkAndAppendCreateAccountText(
Expand Down Expand Up @@ -740,6 +791,10 @@ export class ProxyAuthWidgetComponent extends BaseComponent implements OnInit, O
}

public appendPasswordAuthenticationButtonV2(element: HTMLElement, buttonsData: any, totalButtons: number): void {
// Password login form is not shown on signup/register routes.
if (this.isSignupRoute()) {
return;
}
if (this.showSkeleton) {
this.showSkeleton = false;
this.domBuilder.removeSkeletonLoader(this.renderer, element);
Expand Down Expand Up @@ -1261,6 +1316,10 @@ export class ProxyAuthWidgetComponent extends BaseComponent implements OnInit, O
}

private appendButton(element, buttonsData): void {
// Password login button is not shown on signup/register routes.
if (buttonsData?.service_id === FeatureServiceIds.PasswordAuthentication && this.isSignupRoute()) {
return;
}
if (this.showSkeleton) {
this.showSkeleton = false;
this.domBuilder.removeSkeletonLoader(this.renderer, element);
Expand Down Expand Up @@ -1331,8 +1390,9 @@ export class ProxyAuthWidgetComponent extends BaseComponent implements OnInit, O
width: 24px;
${invertIcon ? 'filter: invert(1);' : ''}
`;
const authButtonText = this.getAuthButtonText(buttonsData.text);
image.src = buttonsData.icon;
image.alt = buttonsData.text;
image.alt = authButtonText;
image.loading = 'lazy';

if (buttonsData?.service_id) {
Expand All @@ -1352,6 +1412,7 @@ export class ProxyAuthWidgetComponent extends BaseComponent implements OnInit, O
this.renderer.appendChild(iconsContainer, button);
} else {
const span: HTMLSpanElement = this.renderer.createElement('span');
const authButtonText = this.getAuthButtonText(buttonsData.text);

button.setAttribute('data-paw-button', 'true');
button.style.cssText = `
Expand Down Expand Up @@ -1386,9 +1447,9 @@ export class ProxyAuthWidgetComponent extends BaseComponent implements OnInit, O
min-width: 170px
`;
image.src = buttonsData.icon;
image.alt = buttonsData.text;
image.alt = authButtonText;
image.loading = 'lazy';
span.innerText = buttonsData.text;
span.innerText = authButtonText;

if (buttonsData?.service_id) {
button.setAttribute('data-service-id', buttonsData.service_id);
Expand Down Expand Up @@ -1454,50 +1515,53 @@ export class ProxyAuthWidgetComponent extends BaseComponent implements OnInit, O
const selectWidgetTheme = this.widgetTheme() as any;
const primaryColor = this.getPrimaryColorForCurrentTheme(selectWidgetTheme?.ui_preferences);

const paragraph: HTMLParagraphElement = this.renderer.createElement('p');
const span: HTMLSpanElement = this.renderer.createElement('span');
const link: HTMLAnchorElement = this.renderer.createElement('a');

paragraph.setAttribute('data-create-account', 'true');

paragraph.style.cssText = `
margin: 20px 8px 8px 8px !important;
font-size: 14px !important;
outline: none !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
flex-wrap: wrap;
gap: 8px !important;
color: ${primaryColor} !important;
cursor: pointer !important;
width: 316px !important;
max-width:100%;
`;
// Hide "Are you a new user? Create an account" on signup/register routes.
if (!this.isSignupRoute()) {
const paragraph: HTMLParagraphElement = this.renderer.createElement('p');
const span: HTMLSpanElement = this.renderer.createElement('span');
const link: HTMLAnchorElement = this.renderer.createElement('a');

paragraph.setAttribute('data-create-account', 'true');

paragraph.style.cssText = `
margin: 20px 8px 8px 8px !important;
font-size: 14px !important;
outline: none !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
flex-wrap: wrap;
gap: 8px !important;
color: ${primaryColor} !important;
cursor: pointer !important;
width: 316px !important;
max-width:100%;
`;

// Style the link
link.style.cssText = `
color: #007bff !important;
text-decoration: none;
cursor: pointer;
font-weight: 500 !important;
`;
// Style the link
link.style.cssText = `
color: #007bff !important;
text-decoration: none;
cursor: pointer;
font-weight: 500 !important;
`;

// Set the text content
span.textContent = 'Are you a new user? ';
link.textContent = selectWidgetTheme?.ui_preferences?.sign_up_button_text || 'Create an account';
// Set the text content
span.textContent = 'Are you a new user? ';
link.textContent = selectWidgetTheme?.ui_preferences?.sign_up_button_text || 'Create an account';

// Add click event to the link
link.addEventListener('click', (event) => {
event.preventDefault();
this.cameFromLogin = false; // Set flag to indicate user came from dynamically appended buttons
this.setShowRegistration(true);
});
// Add click event to the link
link.addEventListener('click', (event) => {
event.preventDefault();
this.cameFromLogin = false; // Set flag to indicate user came from dynamically appended buttons
this.setShowRegistration(true);
});

// Append elements
this.renderer.appendChild(paragraph, span);
this.renderer.appendChild(paragraph, link);
this.renderer.appendChild(element, paragraph);
// Append elements
this.renderer.appendChild(paragraph, span);
this.renderer.appendChild(paragraph, link);
this.renderer.appendChild(element, paragraph);
}

// Powered by footer — hidden when branding is removed (ui_preferences.remove_branding)
if (selectWidgetTheme?.ui_preferences?.remove_branding) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,22 @@
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *matCellDef="let element" data-label="Actions">
<div class="actions flex gap-3 justify-end">
<div class="actions flex gap-3 justify-end items-center">
@if (element.feature_configuration_id) {
<span
[matTooltip]="element.is_hidden ? 'Enable role' : 'Disable role'"
matTooltipPosition="above"
>
<mat-slide-toggle
color="primary"
[checked]="!element.is_hidden"
(change)="toggleRoleVisibility(element, $event)"
[attr.aria-label]="
element.is_hidden ? 'Enable role' : 'Disable role'
"
></mat-slide-toggle>
</span>
}
<span
[matTooltip]="
!element.feature_configuration_id ? 'Default Role is not editable' : ''
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatDialogModule } from '@angular/material/dialog';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSlideToggleChange, MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatDividerModule } from '@angular/material/divider';
import { ServiceListComponent } from '@proxy/ui/service-list';
import { NoRecordFoundComponent } from '@proxy/ui/no-record-found';
Expand Down Expand Up @@ -53,6 +53,9 @@ interface IRole {
permissions: string;
permissionsList: any[];
description?: string;
is_hidden?: boolean;
feature_configuration_id?: number;
is_default?: boolean;
}

interface ITestIdentity {
Expand Down Expand Up @@ -272,6 +275,7 @@ export class ManagementComponent implements OnInit, OnDestroy, OnChanges {
permissionsList: permissionsList,
feature_configuration_id: role.feature_configuration_id,
is_default: role.is_default || false,
is_hidden: !!role.is_hidden,
description: role.description || '',
};
});
Expand Down Expand Up @@ -559,6 +563,27 @@ export class ManagementComponent implements OnInit, OnDestroy, OnChanges {
this.dialogRef.close(false);
}

/**
* Toggle role visibility via PUT /:referenceId/cRoles/:id with { is_hidden }.
* Checked (enabled) => is_hidden: false; unchecked (disabled) => is_hidden: true.
*/
public toggleRoleVisibility(role: IRole, event: MatSlideToggleChange): void {
const referenceId = this.roleForm.get('feature_id')?.value;
if (!referenceId || !role?.id) {
event.source.checked = !event.checked;
return;
}
const is_hidden = !event.checked;
role.is_hidden = is_hidden;
this.userComponentStore.updateRole(
of({
id: role.id,
referenceId,
is_hidden,
})
);
}

public deleteRole(role: IRole): void {
const confirmDialogRef: MatDialogRef<ConfirmDialogComponent> = this.dialog.open(ConfirmDialogComponent, {
panelClass: ['mat-dialog'],
Expand Down
Loading