mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
feat(auth): add username sign-in mode and recovery kits (#3215)
This commit is contained in:
1 parent
bf3d73a5f7
commit
e1eecc3b6c
355 files changed
+52138
-24097
No files matched your search
@@ -15,9 +15,16 @@ import {Switch} from '@app/features/ui/components/form/FormSwitch';
|
||||
import {Spinner} from '@app/features/ui/components/Spinner';
|
||||
import FocusRing from '@app/features/ui/focus_ring/FocusRing';
|
||||
import {RadioGroup, type RadioOption} from '@app/features/ui/radio_group/RadioGroup';
|
||||
import {WarningAlert} from '@app/features/ui/warning_alert/WarningAlert';
|
||||
import {ThemeSelector} from '@app/features/user/components/modals/tabs/appearance_tab/theme/ThemeTabContent';
|
||||
import {LanguageSelector} from '@app/features/user/components/modals/tabs/LanguageTab';
|
||||
import * as LocaleUtils from '@app/features/user/utils/LocaleUtils';
|
||||
import {
|
||||
type AccountIdentityMode,
|
||||
AccountIdentityModes,
|
||||
type TagStyle,
|
||||
TagStyles,
|
||||
} from '@fluxer/constants/src/AccountIdentityConstants';
|
||||
import type {ThemeType} from '@fluxer/constants/src/UserConstants';
|
||||
import type {MessageDescriptor} from '@lingui/core';
|
||||
import {msg} from '@lingui/core/macro';
|
||||
@@ -68,6 +75,93 @@ const THEME_BODY_DESCRIPTOR = msg({
|
||||
message: 'Pick the appearance this administrator account should start with.',
|
||||
comment: 'Setup wizard body for choosing the initial account theme.',
|
||||
});
|
||||
const SIGN_IN_METHOD_TITLE_DESCRIPTOR = msg({
|
||||
message: 'Choose how people sign in',
|
||||
comment: 'Setup wizard title for choosing whether accounts use usernames or email addresses to sign in.',
|
||||
});
|
||||
const SIGN_IN_METHOD_BODY_DESCRIPTOR = msg({
|
||||
message: 'You cannot change this once the first account exists.',
|
||||
comment: 'Setup wizard body for the sign-in method step.',
|
||||
});
|
||||
const SIGN_IN_METHOD_USERNAME_NAME_DESCRIPTOR = msg({
|
||||
message: 'Usernames (recommended)',
|
||||
comment: 'Setup wizard option where people sign in with a username and password and no email is collected.',
|
||||
});
|
||||
const SIGN_IN_METHOD_USERNAME_DESC_DESCRIPTOR = msg({
|
||||
message:
|
||||
'People sign in with a username and password. No email address is collected. Each person can save a recovery kit in case they forget their password.',
|
||||
comment: 'Description for the username sign-in option in the setup wizard.',
|
||||
});
|
||||
const SIGN_IN_METHOD_USERNAME_MOBILE_NOTICE_DESCRIPTOR = msg({
|
||||
message: 'Username sign-in is still rolling out to the mobile apps. Full support on every platform is coming soon.',
|
||||
comment: 'Warning shown in the setup wizard when the username sign-in option is selected. Keep it short and plain.',
|
||||
});
|
||||
const SIGN_IN_METHOD_EMAIL_NAME_DESCRIPTOR = msg({
|
||||
message: 'Email',
|
||||
comment: 'Setup wizard option where people sign in with an email address and password.',
|
||||
});
|
||||
const SIGN_IN_METHOD_EMAIL_DESC_DESCRIPTOR = msg({
|
||||
message:
|
||||
'People sign in with an email address and password. Password resets and verification need email delivery, which you can set up later.',
|
||||
comment: 'Description for the email sign-in option in the setup wizard.',
|
||||
});
|
||||
const SIGN_IN_METHOD_USERNAME_SUMMARY_DESCRIPTOR = msg({
|
||||
message: 'Usernames',
|
||||
comment: 'Summary value on the setup wizard finish step when people sign in with a username.',
|
||||
});
|
||||
const TAG_STYLE_NONE_SUMMARY_DESCRIPTOR = msg({
|
||||
message: 'No tags',
|
||||
comment: 'Setup wizard finish summary value when usernames are unique and shown without a tag.',
|
||||
});
|
||||
const TAG_STYLE_RANDOM_SUMMARY_DESCRIPTOR = msg({
|
||||
message: 'Random tags',
|
||||
comment: 'Setup wizard finish summary value when every username gets a random four digit tag.',
|
||||
});
|
||||
const TAG_STYLE_TITLE_DESCRIPTOR = msg({
|
||||
message: 'Username tags',
|
||||
comment: 'Heading above the choice of how usernames are tagged in the setup wizard.',
|
||||
});
|
||||
const TAG_STYLE_NONE_NAME_DESCRIPTOR = msg({
|
||||
message: 'No tags (recommended)',
|
||||
comment: 'Setup wizard option where every username is unique and shown without a tag.',
|
||||
});
|
||||
const TAG_STYLE_NONE_EMAIL_DESC_DESCRIPTOR = msg({
|
||||
message: 'Each name belongs to one person and shows on its own, like alex.',
|
||||
comment: 'Description for the no tags option when people sign in with email. Keep the example name alex.',
|
||||
});
|
||||
const TAG_STYLE_RANDOM_NAME_DESCRIPTOR = msg({
|
||||
message: 'Random tags',
|
||||
comment: 'Setup wizard option where every username gets a random four digit tag.',
|
||||
});
|
||||
const TAG_STYLE_RANDOM_EMAIL_DESC_DESCRIPTOR = msg({
|
||||
message: 'Names get a random tag, like alex#4821, so several people can share a name.',
|
||||
comment: 'Description for the random tags option when people sign in with email. Keep alex#4821.',
|
||||
});
|
||||
const ADMIN_RECOVERY_KIT_TITLE_DESCRIPTOR = msg({
|
||||
message: 'Save your recovery kit',
|
||||
comment: 'Setup wizard title asking the administrator to create an account recovery kit.',
|
||||
});
|
||||
const ADMIN_RECOVERY_KIT_BODY_DESCRIPTOR = msg({
|
||||
message:
|
||||
'Your account has no email address. If you forget your password, a recovery kit is the only way back in. Create one now and keep it somewhere safe.',
|
||||
comment: 'Setup wizard body explaining why the administrator needs a recovery kit.',
|
||||
});
|
||||
const ADMIN_RECOVERY_KIT_CREATE_DESCRIPTOR = msg({
|
||||
message: 'Create recovery kit',
|
||||
comment: 'Button in the setup wizard that creates a recovery kit for the administrator account.',
|
||||
});
|
||||
const ADMIN_RECOVERY_KIT_RECREATE_DESCRIPTOR = msg({
|
||||
message: 'Create a new kit',
|
||||
comment: 'Button in the setup wizard that replaces the administrator recovery kit with a new one.',
|
||||
});
|
||||
const ADMIN_RECOVERY_KIT_SAVED_DESCRIPTOR = msg({
|
||||
message: 'Your recovery kit is ready. You can continue.',
|
||||
comment: 'Status shown in the setup wizard once the administrator has a recovery kit.',
|
||||
});
|
||||
const ADMIN_RECOVERY_KIT_MISSING_DESCRIPTOR = msg({
|
||||
message: 'Create a recovery kit to continue.',
|
||||
comment: 'Status shown in the setup wizard while the administrator has no recovery kit.',
|
||||
});
|
||||
export const CREATE_ADMIN_ACCOUNT_DESCRIPTOR = msg({
|
||||
message: 'Create administrator account',
|
||||
comment: 'Button that opens the registration flow to create the first administrator account.',
|
||||
@@ -388,6 +482,10 @@ const SUMMARY_PREMIUM_DESCRIPTOR = msg({
|
||||
message: 'Premium model',
|
||||
comment: 'Summary row label for the premium model in the setup wizard.',
|
||||
});
|
||||
const SUMMARY_SIGN_IN_METHOD_DESCRIPTOR = msg({
|
||||
message: 'Sign-in method',
|
||||
comment: 'Summary row label for how people sign in, on the setup wizard finish step.',
|
||||
});
|
||||
const SUMMARY_ACCEPTED_DESCRIPTOR = msg({
|
||||
message: 'Accepted',
|
||||
comment: 'Summary value when the operator accepted the push relay notice.',
|
||||
@@ -596,6 +694,95 @@ export const ThemeStep = observer(
|
||||
},
|
||||
);
|
||||
|
||||
export const SignInMethodStep = observer(
|
||||
({
|
||||
mode,
|
||||
tagStyle,
|
||||
disabled,
|
||||
error,
|
||||
onChange,
|
||||
onTagStyleChange,
|
||||
}: {
|
||||
mode: AccountIdentityMode;
|
||||
tagStyle: TagStyle;
|
||||
disabled: boolean;
|
||||
error: string | null;
|
||||
onChange: (mode: AccountIdentityMode) => void;
|
||||
onTagStyleChange: (tagStyle: TagStyle) => void;
|
||||
}) => {
|
||||
const {i18n} = useLingui();
|
||||
const options: ReadonlyArray<RadioOption<AccountIdentityMode>> = [
|
||||
{
|
||||
value: AccountIdentityModes.USERNAME,
|
||||
name: i18n._(SIGN_IN_METHOD_USERNAME_NAME_DESCRIPTOR),
|
||||
desc: i18n._(SIGN_IN_METHOD_USERNAME_DESC_DESCRIPTOR),
|
||||
},
|
||||
{
|
||||
value: AccountIdentityModes.EMAIL,
|
||||
name: i18n._(SIGN_IN_METHOD_EMAIL_NAME_DESCRIPTOR),
|
||||
desc: i18n._(SIGN_IN_METHOD_EMAIL_DESC_DESCRIPTOR),
|
||||
},
|
||||
];
|
||||
const usernameSignIn = mode === AccountIdentityModes.USERNAME;
|
||||
const tagStyleOptions: ReadonlyArray<RadioOption<TagStyle>> = [
|
||||
{
|
||||
value: TagStyles.NONE,
|
||||
name: i18n._(TAG_STYLE_NONE_NAME_DESCRIPTOR),
|
||||
desc: i18n._(TAG_STYLE_NONE_EMAIL_DESC_DESCRIPTOR),
|
||||
},
|
||||
{
|
||||
value: TagStyles.RANDOM,
|
||||
name: i18n._(TAG_STYLE_RANDOM_NAME_DESCRIPTOR),
|
||||
desc: i18n._(TAG_STYLE_RANDOM_EMAIL_DESC_DESCRIPTOR),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<section className={styles.step} data-flx="app.self-hosted-setup-wizard-gate.sign-in-method-step">
|
||||
<StepHeader
|
||||
title={i18n._(SIGN_IN_METHOD_TITLE_DESCRIPTOR)}
|
||||
body={i18n._(SIGN_IN_METHOD_BODY_DESCRIPTOR)}
|
||||
data-flx="app.setup.setup-wizard-steps.sign-in-method-step.step-header"
|
||||
/>
|
||||
<RadioGroup
|
||||
options={options}
|
||||
value={mode}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
aria-label={i18n._(SIGN_IN_METHOD_TITLE_DESCRIPTOR)}
|
||||
data-flx="app.self-hosted-setup-wizard-gate.sign-in-method-radio-group"
|
||||
/>
|
||||
{usernameSignIn && (
|
||||
<WarningAlert data-flx="app.self-hosted-setup-wizard-gate.username-mobile-notice">
|
||||
{i18n._(SIGN_IN_METHOD_USERNAME_MOBILE_NOTICE_DESCRIPTOR)}
|
||||
</WarningAlert>
|
||||
)}
|
||||
{!usernameSignIn && (
|
||||
<div className={styles.usernameStyle} data-flx="app.self-hosted-setup-wizard-gate.username-style">
|
||||
<h3 className={styles.usernameStyleTitle}>{i18n._(TAG_STYLE_TITLE_DESCRIPTOR)}</h3>
|
||||
<RadioGroup
|
||||
options={tagStyleOptions}
|
||||
value={tagStyle}
|
||||
onChange={onTagStyleChange}
|
||||
disabled={disabled}
|
||||
aria-label={i18n._(TAG_STYLE_TITLE_DESCRIPTOR)}
|
||||
data-flx="app.self-hosted-setup-wizard-gate.username-style-radio-group"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{error && (
|
||||
<p
|
||||
className={styles.submitError}
|
||||
role="alert"
|
||||
data-flx="app.self-hosted-setup-wizard-gate.sign-in-method-error"
|
||||
>
|
||||
{error}
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export const AdminIntroStep = observer(() => {
|
||||
const {i18n} = useLingui();
|
||||
return (
|
||||
@@ -609,20 +796,22 @@ export const AdminIntroStep = observer(() => {
|
||||
);
|
||||
});
|
||||
|
||||
export const AdminAccountStep = observer(({theme}: {theme: ThemeType}) => {
|
||||
export const AdminAccountStep = observer(({theme, usernameSignIn}: {theme: ThemeType; usernameSignIn: boolean}) => {
|
||||
const {i18n} = useLingui();
|
||||
return (
|
||||
<section className={styles.step} data-flx="app.self-hosted-setup-wizard-gate.admin-account-step">
|
||||
<div className={styles.accountSetupForm} data-flx="app.self-hosted-setup-wizard-gate.admin-form">
|
||||
<AuthRegisterFormCore
|
||||
fields={{
|
||||
showEmail: true,
|
||||
showEmail: !usernameSignIn,
|
||||
showPassword: true,
|
||||
showPasswordConfirmation: true,
|
||||
showUsernameValidation: true,
|
||||
requireUsername: usernameSignIn,
|
||||
}}
|
||||
submitLabel={i18n._(CREATE_ADMIN_ACCOUNT_DESCRIPTOR)}
|
||||
redirectPath=""
|
||||
offerRecoveryKit={usernameSignIn}
|
||||
theme={theme}
|
||||
showLegalConsent={false}
|
||||
data-flx="app.self-hosted-setup-wizard-gate.admin-register-form"
|
||||
@@ -632,6 +821,34 @@ export const AdminAccountStep = observer(({theme}: {theme: ThemeType}) => {
|
||||
);
|
||||
});
|
||||
|
||||
export const AdminRecoveryKitStep = observer(
|
||||
({hasRecoveryKit, creating, onCreate}: {hasRecoveryKit: boolean; creating: boolean; onCreate: () => void}) => {
|
||||
const {i18n} = useLingui();
|
||||
return (
|
||||
<section className={styles.centeredStep} data-flx="app.self-hosted-setup-wizard-gate.admin-recovery-kit-step">
|
||||
<StepHeader
|
||||
title={i18n._(ADMIN_RECOVERY_KIT_TITLE_DESCRIPTOR)}
|
||||
body={i18n._(ADMIN_RECOVERY_KIT_BODY_DESCRIPTOR)}
|
||||
data-flx="app.setup.setup-wizard-steps.admin-recovery-kit-step.step-header"
|
||||
/>
|
||||
<Button
|
||||
variant={hasRecoveryKit ? 'secondary' : 'primary'}
|
||||
submitting={creating}
|
||||
onClick={onCreate}
|
||||
data-flx="app.self-hosted-setup-wizard-gate.admin-recovery-kit-step.button.create"
|
||||
>
|
||||
{hasRecoveryKit
|
||||
? i18n._(ADMIN_RECOVERY_KIT_RECREATE_DESCRIPTOR)
|
||||
: i18n._(ADMIN_RECOVERY_KIT_CREATE_DESCRIPTOR)}
|
||||
</Button>
|
||||
<p className={styles.body} role="status" data-flx="app.self-hosted-setup-wizard-gate.admin-recovery-kit-status">
|
||||
{hasRecoveryKit ? i18n._(ADMIN_RECOVERY_KIT_SAVED_DESCRIPTOR) : i18n._(ADMIN_RECOVERY_KIT_MISSING_DESCRIPTOR)}
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export const LoadingStep = observer(() => {
|
||||
const {i18n} = useLingui();
|
||||
return (
|
||||
@@ -1640,6 +1857,8 @@ const SummaryRow: React.FC<SummaryRowProps> = ({label, value}) => (
|
||||
export const FinishStep = observer(
|
||||
({
|
||||
productName,
|
||||
accountIdentity,
|
||||
tagStyle,
|
||||
registrationMode,
|
||||
singleCommunityEnabled,
|
||||
directMessagesDisabled,
|
||||
@@ -1649,6 +1868,8 @@ export const FinishStep = observer(
|
||||
submitError,
|
||||
}: {
|
||||
productName: string;
|
||||
accountIdentity: AccountIdentityMode;
|
||||
tagStyle: TagStyle;
|
||||
registrationMode: RegistrationMode;
|
||||
singleCommunityEnabled: boolean;
|
||||
directMessagesDisabled: boolean;
|
||||
@@ -1681,6 +1902,24 @@ export const FinishStep = observer(
|
||||
value={productName}
|
||||
data-flx="app.setup.setup-wizard-steps.finish-step.summary-row"
|
||||
/>
|
||||
<SummaryRow
|
||||
label={i18n._(SUMMARY_SIGN_IN_METHOD_DESCRIPTOR)}
|
||||
value={
|
||||
accountIdentity === AccountIdentityModes.USERNAME
|
||||
? i18n._(SIGN_IN_METHOD_USERNAME_SUMMARY_DESCRIPTOR)
|
||||
: i18n._(SIGN_IN_METHOD_EMAIL_NAME_DESCRIPTOR)
|
||||
}
|
||||
data-flx="app.setup.setup-wizard-steps.finish-step.summary-row.sign-in-method"
|
||||
/>
|
||||
{accountIdentity === AccountIdentityModes.EMAIL && (
|
||||
<SummaryRow
|
||||
label={i18n._(TAG_STYLE_TITLE_DESCRIPTOR)}
|
||||
value={i18n._(
|
||||
tagStyle === TagStyles.NONE ? TAG_STYLE_NONE_SUMMARY_DESCRIPTOR : TAG_STYLE_RANDOM_SUMMARY_DESCRIPTOR,
|
||||
)}
|
||||
data-flx="app.setup.setup-wizard-steps.finish-step.summary-row.username-style"
|
||||
/>
|
||||
)}
|
||||
<SummaryRow
|
||||
label={i18n._(SUMMARY_REGISTRATION_DESCRIPTOR)}
|
||||
value={registrationLabel}
|
||||
|
||||
Reference in new issue
Block a user