Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
2 changes: 2 additions & 0 deletions src/apim.runtime.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ import { ProductDetailsRuntimeModule } from "./components/products/product-detai
import { ProductSubscribeRuntimeModule } from "./components/products/product-subscribe/productSubscribe.runtime.module";
import { ProductSubscriptionsRuntimeModule } from "./components/products/product-subscriptions/productSubscriptions.runtime.module";
import { SignInRuntimeModule } from "./components/users/signin/signin.runtime.module";
import { SignInSocialRuntimeModule } from "./components/users/signin-social/signinSocial.runtime.module";

export class ApimRuntimeModule implements IInjectorModule {
public register(injector: IInjector): void {
Expand Down Expand Up @@ -188,6 +189,7 @@ export class ApimRuntimeModule implements IInjectorModule {
injector.bindModule(new ProductSubscribeRuntimeModule());
injector.bindModule(new ProductSubscriptionsRuntimeModule());
injector.bindModule(new SignInRuntimeModule());
injector.bindModule(new SignInSocialRuntimeModule());

if (process.env.NODE_ENV === staticDataEnvironment) {
injector.bind("httpClient", StaticDataHttpClient);
Expand Down
32 changes: 32 additions & 0 deletions src/components/BtnSpinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as React from "react";
import { useState } from "react";
import { Button, ButtonProps, Spinner } from "@fluentui/react-components";

export type TProps = ButtonProps & {
onClick: () => Promise<unknown>;
working?: boolean;
};

export const BtnSpinner = ({
children,
onClick,
disabled,
working: workingProp,
...props
}: TProps) => {
const [working, setWorking] = useState(false);

return (
<Button
{...props}
disabled={disabled || working || workingProp}
onClick={() => {
setWorking(true);
onClick().finally(() => setWorking(false));
}}
>
{(working || workingProp) && <Spinner size={"extra-tiny"} style={{ marginRight: ".5rem" }} />}
{children}
</Button>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Textarea,
} from "@fluentui/react-components";
import { ChevronDown12Regular, ChevronUp12Regular } from "@fluentui/react-icons";
import { BtnSpinner } from "../../../BtnSpinner";

export type TSubscribe = (
subscriptionName: string,
Expand All @@ -27,7 +28,6 @@ export const ProductSubscribeForm = ({ subscribe, tos, showTermsByDefault }: Pro
const [subscriptionName, setSubscriptionName] = React.useState("");
const [consented, setConsented] = useState(false);
const [showToS, setShowToS] = useState(showTermsByDefault ?? false);
const [working, setWorking] = useState(false);

const tosLabel = (
<>
Expand All @@ -54,25 +54,13 @@ export const ProductSubscribeForm = ({ subscribe, tos, showTermsByDefault }: Pro
/>
</Stack.Item>
<Stack.Item>
<Button
onClick={() => {
setWorking(true);
subscribe(subscriptionName, consented)
.finally(() => setWorking(false));
}}
<BtnSpinner
onClick={() => subscribe(subscriptionName, consented)}
appearance="primary"
disabled={
working || !subscriptionName || (tos && !consented)
}
disabled={!subscriptionName || (tos && !consented)}
>
{working && (
<Spinner
size={"extra-tiny"}
style={{ marginRight: ".5rem" }}
/>
)}
Subscribe
</Button>
</BtnSpinner>
</Stack.Item>
</Stack>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import { dispatchErrors, parseAndDispatchError } from "../../../validation-summa
import { ErrorSources } from "../../../validation-summary/constants";
import { Logger } from "@paperbits/common/logging";
import { eventTypes } from "../../../../../logging/clientLogger";


const aadb2cResetPasswordErrorCode = "AADB2C90118";
import { aadb2cResetPasswordErrorCode } from "../../../../../constants";

@RuntimeComponent({
selector: "signin-aad-b2c"
Expand All @@ -35,7 +33,7 @@ export class SignInAadB2C {
this.classNames = ko.observable();
this.label = ko.observable();
this.replyUrl = ko.observable();
// Is necessary for displaying Terms of Use. Will be called when the back-end implementation is done
// Is necessary for displaying Terms of Use. Will be called when the back-end implementation is done
this.termsOfUse = ko.observable();
}

Expand Down
16 changes: 14 additions & 2 deletions src/components/users/signin-social/ko/signinSocial.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
<div data-bind="secured: security">
<!-- ko if: aadConfig -->
<signin-aad data-bind="attr: { params: aadConfig }"></signin-aad>
<!-- ko if: isRedesignEnabled -->
<fui-signin-aad-runtime data-bind="attr: { props: aadConfig }"></fui-signin-aad-runtime>
<!-- /ko -->

<!-- ko ifnot: isRedesignEnabled -->
<signin-aad data-bind="attr: { params: aadConfig }"></signin-aad>
<!-- /ko -->
<!-- /ko -->

<!-- ko if: aadB2CConfig -->
<signin-aad-b2c data-bind="attr: { params: aadB2CConfig }"></signin-aad-b2c>
<!-- ko if: isRedesignEnabled -->
<fui-signin-aadb2c-runtime data-bind="attr: { props: aadB2CConfig }"></fui-signin-aadb2c-runtime>
<!-- /ko -->

<!-- ko ifnot: isRedesignEnabled -->
<signin-aad-b2c data-bind="attr: { params: aadB2CConfig }"></signin-aad-b2c>
<!-- /ko -->
<!-- /ko -->

<!-- ko if: showNoAadConfigMessage -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class SigninSocialViewModel {
public readonly security: ko.Observable<SecurityModel>;
public readonly mode: ko.Observable<string>;
public readonly showNoAadConfigMessage: ko.Computed<boolean>;
public readonly isRedesignEnabled: ko.Observable<boolean>;

constructor() {
this.aadConfig = ko.observable<string>();
Expand All @@ -24,5 +25,6 @@ export class SigninSocialViewModel {
this.security = ko.observable<SecurityModel>();
this.mode = ko.observable<string>();
this.showNoAadConfigMessage = ko.computed<boolean>(() => !this.aadConfig() && !this.aadB2CConfig() && this.mode() !== "publishing");
this.isRedesignEnabled = ko.observable<boolean>();
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { ISettingsProvider } from "@paperbits/common/configuration";
import { StyleCompiler } from "@paperbits/common/styles";
import { ViewModelBinder, WidgetState } from "@paperbits/common/widgets";
import { ISiteService } from "@paperbits/common/sites/ISiteService";
import { TermsOfService } from "../../../../contracts/identitySettings";
import { IdentityService } from "../../../../services/identityService";
import { isRedesignEnabledSetting } from "../../../../constants";
import { SigninSocialModel } from "../signinSocialModel";
import { SigninSocialViewModel } from "./signinSocialViewModel";

Expand All @@ -11,7 +13,8 @@ export class SigninSocialViewModelBinder implements ViewModelBinder<SigninSocial
constructor(
private readonly identityService: IdentityService,
private readonly styleCompiler: StyleCompiler,
private readonly settingsProvider: ISettingsProvider
private readonly settingsProvider: ISettingsProvider,
private readonly siteService: ISiteService,
) { }

public async getTermsOfService(): Promise<TermsOfService> {
Expand All @@ -24,6 +27,8 @@ export class SigninSocialViewModelBinder implements ViewModelBinder<SigninSocial
componentInstance.aadConfig(JSON.stringify(state.aadConfig));
componentInstance.aadB2CConfig(JSON.stringify(state.aadB2CConfig));
componentInstance.mode(state.mode);

componentInstance.isRedesignEnabled(state.isRedesignEnabled);
}

public async modelToState(model: SigninSocialModel, state: WidgetState): Promise<void> {
Expand Down Expand Up @@ -69,5 +74,7 @@ export class SigninSocialViewModelBinder implements ViewModelBinder<SigninSocial
if (model.styles) {
state.styles = await this.styleCompiler.getStyleModelAsync(model.styles);
}

state.isRedesignEnabled = !!(await this.siteService.getSetting(isRedesignEnabledSetting));
}
}
122 changes: 122 additions & 0 deletions src/components/users/signin-social/react/SignInAadB2cRuntime.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import * as React from "react";
import { FluentProvider } from "@fluentui/react-components";
import { Resolve } from "@paperbits/react/decorators";
import { EventManager } from "@paperbits/common/events";
import { ISettingsProvider } from "@paperbits/common/configuration";
import { Logger } from "@paperbits/common/logging";
import * as Constants from "../../../../constants";
import { AadService, AadServiceV2, IAadService } from "../../../../services";
import { ErrorSources } from "../../validation-summary/constants";
import { eventTypes } from "../../../../logging/clientLogger";
import { AadClientLibrary, SettingNames } from "../../../../constants";
import { dispatchErrors, parseAndDispatchError } from "../../validation-summary/utils";
import { AadB2CClientConfig } from "../../../../contracts/aadB2CClientConfig";
import { aadb2cResetPasswordErrorCode } from "../../../../constants";
import { BtnSpinner } from "../../../BtnSpinner";

type SignInAadB2cRuntimeProps = {
label: string
replyUrl: string
};
type SignInAadB2cRuntimeFCProps = SignInAadB2cRuntimeProps & {
disabled: boolean
signIn: () => Promise<void>
};

const ProductSubscribeRuntimeFC = ({ label, disabled, signIn }: SignInAadB2cRuntimeFCProps) => {
return (
<BtnSpinner disabled={disabled} onClick={signIn} >
<i className="icon-emb icon-svg-entraId"></i>
{label}
</BtnSpinner>
);
};

export class SignInAadB2cRuntime extends React.Component<SignInAadB2cRuntimeProps> {
@Resolve("aadService")
public aadService: AadService;

@Resolve("aadServiceV2")
public aadServiceV2: AadServiceV2;

@Resolve("eventManager")
public eventManager: EventManager;

@Resolve("settingsProvider")
public settingsProvider: ISettingsProvider;

@Resolve("logger")
public logger: Logger;

private selectedService: IAadService;
private aadConfig: AadB2CClientConfig;

componentDidMount() {
this.init()
}

async init(): Promise<void> {
this.aadConfig = await this.settingsProvider.getSetting<AadB2CClientConfig>(SettingNames.aadB2CClientConfig);

if (this.aadConfig) {
if (this.aadConfig.clientLibrary === AadClientLibrary.v2) {
this.selectedService = this.aadServiceV2;
} else {
this.selectedService = this.aadService;
}

await this.selectedService.checkCallbacks();
} else {
this.logger.trackEvent(eventTypes.aadB2CLogin, { message: "AAD B2C client configuration is missing." });
}

this.logger.trackEvent(eventTypes.aadB2CLogin, { message: "Signin AAD B2C component initialized." });
}

async signIn(): Promise<void> {
this.logger.trackEvent(eventTypes.aadB2CLogin, { message: "Login initiated." });
this.cleanValidationErrors();

if (!this.aadConfig) {
this.logger.trackEvent(eventTypes.aadB2CLogin, { message: "AAD B2C client configuration is missing." })
return;
}

try {
await this.selectedService.runAadB2CUserFlow(
this.aadConfig.clientId,
this.aadConfig.authority,
this.aadConfig.signinTenant,
this.aadConfig.signinPolicyName,
this.props.replyUrl,
);
} catch (error) {
if (this.aadConfig.passwordResetPolicyName && error.message.includes(aadb2cResetPasswordErrorCode)) { // Reset password requested
try {
await this.selectedService.runAadB2CUserFlow(this.aadConfig.clientId, this.aadConfig.authority, this.aadConfig.signinTenant, this.aadConfig.passwordResetPolicyName);
return;
} catch (resetPasswordError) {
error = resetPasswordError;
}
}

parseAndDispatchError(this.eventManager, ErrorSources.signInOAuth, error, this.logger);
}
}

private cleanValidationErrors(): void {
dispatchErrors(this.eventManager, ErrorSources.signInOAuth, []);
}

render() {
return (
<FluentProvider theme={Constants.fuiTheme} style={{ display: "inline" }}>
<ProductSubscribeRuntimeFC
{...this.props}
disabled={!this.aadConfig}
signIn={this.signIn.bind(this)}
/>
</FluentProvider>
);
}
}
Loading