Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Show SSO buttons on registration page (fixes #3285)
  • Loading branch information
Nutomic committed Jan 26, 2026
commit cc29d762d598e4845efd56fa8365f98aff36bad3
2 changes: 1 addition & 1 deletion lemmy-translations
205 changes: 90 additions & 115 deletions src/shared/components/home/signup.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { setIsoData, updateMyUserInfo } from "@utils/app";
import { isBrowser } from "@utils/browser";
import { getQueryParams, resourcesSettled, validEmail } from "@utils/helpers";
import { scrollMixin } from "../mixins/scroll-mixin";
import { resourcesSettled, validEmail } from "@utils/helpers";
import { Component, FormEvent } from "inferno";
import {
CaptchaResponse,
GetCaptchaResponse,
GetSiteResponse,
LoginResponse,
PublicOAuthProvider,
SiteView,
} from "lemmy-js-client";
import { validActorRegexPattern } from "@utils/config";
Expand All @@ -23,17 +24,15 @@ import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea";
import PasswordInput from "../common/password-input";
import { RouteComponentProps } from "inferno-router/dist/Route";
import { RouteData } from "@utils/types";
import { IRoutePropsWithFetch } from "@utils/routes";
import { handleUseOAuthProvider } from "./login";
import { secondsDurationToAlertClass, secondsDurationToStr } from "@utils/date";

interface SignupProps {
sso_provider_id?: string;
}
import { scrollMixin } from "@components/mixins/scroll-mixin";
import { RouteData } from "@utils/types";
import { RouteComponentProps } from "inferno-router/dist/Route";
import { IRoutePropsWithFetch } from "@utils/routes";

interface State {
siteRes: GetSiteResponse;
registerRes: RequestState<LoginResponse>;
captchaRes: RequestState<GetCaptchaResponse>;
form: {
Expand All @@ -51,21 +50,11 @@ interface State {
captchaPlaying: boolean;
}

export function getSignupQueryParams(source?: string): SignupProps {
return getQueryParams<SignupProps>(
{
sso_provider_id: (param?: string) => param,
},
source,
);
}

type SignupRouteProps = RouteComponentProps<Record<string, never>> &
SignupProps;
type SignupRouteProps = RouteComponentProps<Record<string, never>>;
export type SignupFetchConfig = IRoutePropsWithFetch<
RouteData,
Record<string, never>,
SignupProps
object
>;

@scrollMixin
Expand All @@ -74,6 +63,7 @@ export class Signup extends Component<SignupRouteProps, State> {
public audio?: HTMLAudioElement;

state: State = {
siteRes: this.isoData.siteRes,
registerRes: EMPTY_REQUEST,
captchaRes: EMPTY_REQUEST,
form: {
Expand Down Expand Up @@ -140,13 +130,38 @@ export class Signup extends Component<SignupRouteProps, State> {
{this.registerForm()}
</div>
</div>
{(this.state.siteRes.oauth_providers?.length || 0) > 0 && (
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this equivalent to what's on the login.tsx page? Would be good to extract into a component.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, done.

<>
<div className="row mt-3 mb-2">
<div className="col-12 col-lg-6 offset-lg-3">
{I18NextService.i18n.t("or")}
</div>
</div>
<div className="row">
<div className="col col-12 col-lg-6 offset-lg-3">
<h2 className="h4 mb-3">
{I18NextService.i18n.t("oauth_register_with_provider")}
</h2>
{(this.state.siteRes.oauth_providers ?? []).map(
(provider: PublicOAuthProvider) => (
<button
className="btn btn-primary my-2 d-block"
onClick={() => handleLoginWithProvider(provider)}
>
{provider.display_name}
</button>
),
)}
</div>
</div>
</>
)}
</div>
);
}

registerForm() {
const siteView = this.isoData.siteRes?.site_view;
const oauth_provider = getOAuthProvider(this);
const lastApplicationDurationSeconds =
this.isoData.siteRes.last_application_duration_seconds;

Expand Down Expand Up @@ -180,76 +195,55 @@ export class Signup extends Component<SignupRouteProps, State> {
</div>
</div>

{!oauth_provider && (
<>
{
<div className="mb-3 row">
<label
className="col-sm-2 col-form-label"
htmlFor="register-email"
>
{I18NextService.i18n.t("email")}
</label>
<div className="col-sm-10">
<input
type="email"
id="register-email"
className="form-control"
placeholder={
siteView?.local_site.require_email_verification
? I18NextService.i18n.t("required")
: I18NextService.i18n.t("optional")
}
value={this.state.form.email}
autoComplete="email"
onInput={e => handleRegisterEmailChange(this, e)}
required={siteView?.local_site.require_email_verification}
minLength={3}
/>
{!siteView?.local_site.require_email_verification &&
this.state.form.email &&
!validEmail(this.state.form.email) && (
<div
className="mt-2 mb-0 alert alert-warning"
role="alert"
>
<Icon
icon="alert-triangle"
classes="icon-inline me-2"
/>
{I18NextService.i18n.t("no_password_reset")}
</div>
)}
<div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="register-email">
{I18NextService.i18n.t("email")}
</label>
<div className="col-sm-10">
<input
type="email"
id="register-email"
className="form-control"
placeholder={
siteView?.local_site.require_email_verification
? I18NextService.i18n.t("required")
: I18NextService.i18n.t("optional")
}
value={this.state.form.email}
autoComplete="email"
onInput={e => handleRegisterEmailChange(this, e)}
required={siteView?.local_site.require_email_verification}
minLength={3}
/>
{!siteView?.local_site.require_email_verification &&
this.state.form.email &&
!validEmail(this.state.form.email) && (
<div className="mt-2 mb-0 alert alert-warning" role="alert">
<Icon icon="alert-triangle" classes="icon-inline me-2" />
{I18NextService.i18n.t("no_password_reset")}
</div>
</div>
}

{
<div className="mb-3">
<PasswordInput
id="register-password"
value={this.state.form.password}
onInput={e => handleRegisterPasswordChange(this, e)}
showStrength
label={I18NextService.i18n.t("password")}
isNew
/>
</div>
}

{
<div className="mb-3">
<PasswordInput
id="register-verify-password"
value={this.state.form.password_verify}
onInput={e => handleRegisterPasswordVerifyChange(this, e)}
label={I18NextService.i18n.t("verify_password")}
isNew
/>
</div>
}
</>
)}
)}
</div>
</div>
<div className="mb-3">
<PasswordInput
id="register-password"
value={this.state.form.password}
onInput={e => handleRegisterPasswordChange(this, e)}
showStrength
label={I18NextService.i18n.t("password")}
isNew
/>
</div>
<div className="mb-3">
<PasswordInput
id="register-verify-password"
value={this.state.form.password_verify}
onInput={e => handleRegisterPasswordVerifyChange(this, e)}
label={I18NextService.i18n.t("verify_password")}
isNew
/>
</div>

{siteView?.local_site.registration_mode === "require_application" && (
<>
Expand Down Expand Up @@ -358,12 +352,7 @@ export class Signup extends Component<SignupRouteProps, State> {
{this.state.registerRes.state === "loading" ? (
<Spinner />
) : (
[
this.titleName(siteView),
...(oauth_provider
? [`(${oauth_provider.display_name})`]
: []),
].join(" ")
this.titleName(siteView)
)}
</button>
</div>
Expand Down Expand Up @@ -459,18 +448,6 @@ async function handleRegisterSubmit(
stay_logged_in,
} = i.state.form;

const oauthProvider = getOAuthProvider(i);

// oauth registration
if (username && oauthProvider)
return handleUseOAuthProvider(
oauthProvider,
undefined,
username,
answer,
show_nsfw,
);

// normal registration
if (username && password && password_verify) {
i.setState({ registerRes: LOADING_REQUEST });
Expand Down Expand Up @@ -622,12 +599,10 @@ function handleCaptchaPlay(i: Signup) {
}
}

function getOAuthProvider(i: Signup) {
return (i.isoData.siteRes?.oauth_providers ?? []).find(
provider => provider.id === Number(i.props?.sso_provider_id ?? -1),
);
}

function captchaPngSrc(captcha: CaptchaResponse) {
return `data:image/png;base64,${captcha.png}`;
}

async function handleLoginWithProvider(oauth_provider: PublicOAuthProvider) {
handleUseOAuthProvider(oauth_provider, "/");
}
2 changes: 1 addition & 1 deletion src/shared/components/person/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
id="settings-tab-pane"
>
<div className="row">
{!userNotLoggedInOrBanned(this.isoData.myUserInfo) && (
{!userNotLoggedInOrBanned(this?.isoData?.myUserInfo) && (
Comment thread
Nutomic marked this conversation as resolved.
Outdated
<div className="col-12 col-md-6">
<div className="card border-secondary mb-3">
<div className="card-body">
Expand Down
7 changes: 1 addition & 6 deletions src/shared/utils/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,7 @@ import {
} from "@components/home/login";
import { LoginReset } from "@components/home/login-reset";
import { Setup } from "@components/home/setup";
import {
Signup,
SignupFetchConfig,
getSignupQueryParams,
} from "@components/home/signup";
import { Signup, SignupFetchConfig } from "@components/home/signup";
import {
Modlog,
ModlogFetchConfig,
Expand Down Expand Up @@ -157,7 +153,6 @@ export const routes: IRoutePropsWithFetch<RouteData, any, any>[] = [
},
{
path: `/signup`,
getQueryParams: getSignupQueryParams,
component: Signup,
} as SignupFetchConfig,
{
Expand Down