diff --git a/src/apps/stable/routes/asyncRoutes/index.ts b/src/apps/stable/routes/asyncRoutes/index.ts index e5abc8565..3e37e3b9d 100644 --- a/src/apps/stable/routes/asyncRoutes/index.ts +++ b/src/apps/stable/routes/asyncRoutes/index.ts @@ -1 +1,2 @@ export * from './user'; +export * from './public'; diff --git a/src/apps/stable/routes/asyncRoutes/public.ts b/src/apps/stable/routes/asyncRoutes/public.ts new file mode 100644 index 000000000..9c8b28e31 --- /dev/null +++ b/src/apps/stable/routes/asyncRoutes/public.ts @@ -0,0 +1,5 @@ +import { AsyncRoute } from '../../../../components/router/AsyncRoute'; + +export const ASYNC_PUBLIC_ROUTES: AsyncRoute[] = [ + { path: 'forgotpassword', page: 'session/forgotPassword' } +]; diff --git a/src/apps/stable/routes/legacyRoutes/public.ts b/src/apps/stable/routes/legacyRoutes/public.ts index abf6439bc..7b491625c 100644 --- a/src/apps/stable/routes/legacyRoutes/public.ts +++ b/src/apps/stable/routes/legacyRoutes/public.ts @@ -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: { diff --git a/src/apps/stable/routes/routes.tsx b/src/apps/stable/routes/routes.tsx index bcd328ace..317479829 100644 --- a/src/apps/stable/routes/routes.tsx +++ b/src/apps/stable/routes/routes.tsx @@ -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: , children: [ + ...ASYNC_PUBLIC_ROUTES.map(toAsyncPageRoute), ...LEGACY_PUBLIC_ROUTES.map(toViewManagerPageRoute), /* Fallback route for invalid paths */ { diff --git a/src/apps/stable/routes/session/forgotPassword/index.tsx b/src/apps/stable/routes/session/forgotPassword/index.tsx new file mode 100644 index 000000000..5d6e0ebb6 --- /dev/null +++ b/src/apps/stable/routes/session/forgotPassword/index.tsx @@ -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 += ''; + msg += globalize.translate('MessageForgotPasswordPinReset'); + msg += ''; + msg += result.PinFile; + msg += ''; + 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) => { + setUsername(e.target.value); + }, []); + + return ( + + + + + {globalize.translate('ButtonForgotPassword')} + + + + + {globalize.translate('LabelForgotPasswordUsernameHelp')} + + + + + + + + + + + + + ); +}; + +export default ForgotPasswordPage; diff --git a/src/controllers/session/forgotPassword/index.html b/src/controllers/session/forgotPassword/index.html deleted file mode 100644 index 31e92c2c5..000000000 --- a/src/controllers/session/forgotPassword/index.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - ${ButtonForgotPassword} - - - - ${LabelForgotPasswordUsernameHelp} - - - - - ${ButtonSubmit} - - - - ${ButtonCancel} - - - - - - diff --git a/src/controllers/session/forgotPassword/index.js b/src/controllers/session/forgotPassword/index.js deleted file mode 100644 index d94dac455..000000000 --- a/src/controllers/session/forgotPassword/index.js +++ /dev/null @@ -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 += ''; - msg += ''; - msg += 'Enter PIN here to finish Password Reset'; - msg += ''; - msg += result.PinFile; - msg += ''; - 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); -} - diff --git a/src/strings/en-us.json b/src/strings/en-us.json index a59ef8524..5094858ac 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -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.",