Merge pull request #6792 from MontejoJorge/refactor/forgotPassword-react

Migrate forgot password page to react component
This commit is contained in:
Bill Thornton
2025-05-12 16:31:21 -04:00
committed by GitHub
8 changed files with 137 additions and 88 deletions
@@ -1 +1,2 @@
export * from './user';
export * from './public';
@@ -0,0 +1,5 @@
import { AsyncRoute } from '../../../../components/router/AsyncRoute';
export const ASYNC_PUBLIC_ROUTES: AsyncRoute[] = [
{ path: 'forgotpassword', page: 'session/forgotPassword' }
];
@@ -22,13 +22,6 @@ export const LEGACY_PUBLIC_ROUTES: LegacyRoute[] = [
view: 'session/login/index.html'
}
},
{
path: 'forgotpassword',
pageProps: {
controller: 'session/forgotPassword/index',
view: 'session/forgotPassword/index.html'
}
},
{
path: 'forgotpasswordpin',
pageProps: {
+2 -1
View File
@@ -9,7 +9,7 @@ import FallbackRoute from 'components/router/FallbackRoute';
import AppLayout from '../AppLayout';
import { ASYNC_USER_ROUTES } from './asyncRoutes';
import { ASYNC_PUBLIC_ROUTES, ASYNC_USER_ROUTES } from './asyncRoutes';
import { LEGACY_PUBLIC_ROUTES, LEGACY_USER_ROUTES } from './legacyRoutes';
export const STABLE_APP_ROUTES: RouteObject[] = [
@@ -33,6 +33,7 @@ export const STABLE_APP_ROUTES: RouteObject[] = [
/* Public routes */
element: <ConnectionRequired level='public' />,
children: [
...ASYNC_PUBLIC_ROUTES.map(toAsyncPageRoute),
...LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute),
/* Fallback route for invalid paths */
{
@@ -0,0 +1,128 @@
import React, { useCallback, useState } from 'react';
import Page from 'components/Page';
import { useNavigate } from 'react-router-dom';
import globalize from 'lib/globalize';
import Button from 'elements/emby-button/Button';
import Input from 'elements/emby-input/Input';
import { useMutation } from '@tanstack/react-query';
import alert from 'components/alert';
import { getUserApi } from '@jellyfin/sdk/lib/utils/api/user-api';
import { ForgotPasswordAction } from '@jellyfin/sdk/lib/generated-client/models/forgot-password-action';
import ServerConnections from 'components/ServerConnections';
export const ForgotPasswordPage = () => {
const navigate = useNavigate();
const [username, setUsername] = useState('');
const forgotPasswordMutation = useMutation({
mutationFn: async (enteredUsername: string) => {
const currentApi = ServerConnections.getCurrentApi();
if (!currentApi) {
throw new Error('API not available');
}
const response = await getUserApi(currentApi).forgotPassword({
forgotPasswordDto: {
EnteredUsername: enteredUsername
}
});
return response.data;
},
onSuccess: (result) => {
let msg = '';
let callback: () => void | undefined = () => undefined;
switch (result.Action) {
case ForgotPasswordAction.ContactAdmin:
msg = globalize.translate('MessageContactAdminToResetPassword');
break;
case ForgotPasswordAction.InNetworkRequired:
msg = globalize.translate('MessageForgotPasswordInNetworkRequired');
break;
case ForgotPasswordAction.PinCode:
msg = globalize.translate('MessageForgotPasswordFileCreated');
msg += '<br/><br/>';
msg += globalize.translate('MessageForgotPasswordPinReset');
msg += '<br/><br/>';
msg += result.PinFile;
msg += '<br/>';
callback = () => navigate('/forgotpasswordpin');
break;
default:
return;
}
return alert({
text: msg,
title: globalize.translate('ButtonForgotPassword')
}).then(() => {
if (callback) callback();
});
}
});
const handleCancel = useCallback(() => {
navigate(-1);
}, [navigate]);
const handleSubmit = useCallback(async (e: React.FormEvent) => {
e.preventDefault();
forgotPasswordMutation.mutate(username);
}, [username, forgotPasswordMutation]);
const handleUsernameChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setUsername(e.target.value);
}, []);
return (
<Page
id='forgotPasswordPage'
className='standalonePage forgotPasswordPage mainAnimatedPage'
>
<div className='padded-left padded-right padded-bottom-page'>
<form
className='forgotPasswordForm'
style={{ textAlign: 'center', margin: '0 auto' }}
onSubmit={handleSubmit}
>
<div style={{ textAlign: 'left' }}>
<h1>{globalize.translate('ButtonForgotPassword')}</h1>
<div className='inputContainer'>
<Input
type='text'
id='txtName'
label={globalize.translate('LabelUser')}
autoComplete='off'
value={username}
onChange={handleUsernameChange}
/>
<div className='fieldDescription'>
{globalize.translate('LabelForgotPasswordUsernameHelp')}
</div>
</div>
<div>
<Button
type='submit'
id='btnSubmit'
className='raised submit block'
title={globalize.translate('ButtonSubmit')}
/>
<Button
type='button'
id='btnCancel'
className='raised cancel block btnCancel'
title={globalize.translate('ButtonCancel')}
onClick={handleCancel}
/>
</div>
</div>
</form>
</div>
</Page>
);
};
export default ForgotPasswordPage;
@@ -1,24 +0,0 @@
<div data-role="page" id="forgotPasswordPage" class="page standalonePage forgotPasswordPage">
<div class="padded-left padded-right padded-bottom-page">
<form class="forgotPasswordForm" style="text-align: center; margin: 0 auto;">
<div style="text-align: left;">
<h1>${ButtonForgotPassword}</h1>
<div class="inputContainer">
<input is="emby-input" type="text" id="txtName" label="${LabelUser}" autocomplete="off"/>
<div class="fieldDescription">${LabelForgotPasswordUsernameHelp}</div>
</div>
<div>
<button is="emby-button" type="submit" class="raised submit block">
<span>${ButtonSubmit}</span>
</button>
<button is="emby-button" type="button" class="raised cancel block btnCancel" onclick="history.back();">
<span>${ButtonCancel}</span>
</button>
</div>
</div>
</form>
</div>
</div>
@@ -1,56 +0,0 @@
import globalize from 'lib/globalize';
import Dashboard from 'utils/dashboard';
function processForgotPasswordResult(result) {
if (result.Action == 'ContactAdmin') {
Dashboard.alert({
message: globalize.translate('MessageContactAdminToResetPassword'),
title: globalize.translate('ButtonForgotPassword')
});
return;
}
if (result.Action == 'InNetworkRequired') {
Dashboard.alert({
message: globalize.translate('MessageForgotPasswordInNetworkRequired'),
title: globalize.translate('ButtonForgotPassword')
});
return;
}
if (result.Action == 'PinCode') {
let msg = globalize.translate('MessageForgotPasswordFileCreated');
msg += '<br/>';
msg += '<br/>';
msg += 'Enter PIN here to finish Password Reset<br/>';
msg += '<br/>';
msg += result.PinFile;
msg += '<br/>';
Dashboard.alert({
message: msg,
title: globalize.translate('ButtonForgotPassword'),
callback: function () {
Dashboard.navigate('forgotpasswordpin');
}
});
}
}
export default function (view) {
function onSubmit(e) {
ApiClient.ajax({
type: 'POST',
url: ApiClient.getUrl('Users/ForgotPassword'),
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify({
EnteredUsername: view.querySelector('#txtName').value
})
}).then(processForgotPasswordResult);
e.preventDefault();
return false;
}
view.querySelector('form').addEventListener('submit', onSubmit);
}
+1
View File
@@ -1129,6 +1129,7 @@
"MessageEnablingOptionLongerScans": "Enabling this option may result in significantly longer library scans.",
"MessageFileReadError": "There was an error reading the file. Please try again.",
"MessageForgotPasswordFileCreated": "The following file has been created on your server and contains instructions on how to proceed",
"MessageForgotPasswordPinReset": "Enter PIN here to finish Password reset",
"MessageForgotPasswordInNetworkRequired": "Please try again within your home network to initiate the password reset process.",
"MessageGetInstalledPluginsError": "An error occurred while getting the list of currently installed plugins.",
"MessageImageFileTypeAllowed": "Only JPEG and PNG files are supported.",