diff --git a/web/src/App.tsx b/web/src/App.tsx index 7547073..f011c75 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -9,6 +9,8 @@ import { CommandManager } from './components/CommandManager.tsx'; import { RoleManager } from './components/RoleManager.tsx'; import { JoinServer } from './components/JoinServer.tsx'; import { DMChat } from './components/DMChat.tsx'; +import { ForgotPasswordPage } from './components/ForgotPasswordPage.tsx'; +import { ResetPasswordPage } from './components/ResetPasswordPage.tsx'; import { useAuthStore } from './stores/auth.ts'; function ProtectedRoute({ children }: { children: React.ReactNode }) { @@ -32,6 +34,8 @@ function App() { } /> + } /> + } /> { + e.preventDefault(); + clearError(); + try { + await requestPasswordReset(email); + setSent(true); + } catch { + // error displayed from store + } + }; + + return ( +
+
+
+          {"┌─ DUMPSTER ─┐"}
+        
+

[FORGOT PASSWORD]

+ + {sent ? ( +
+

+ If an account exists for{" "} + {email}, we've sent a + password reset link. Check your inbox. +

+

+ The link expires in 1 hour. +

+
+ + [BACK TO LOGIN] + +
+
+ ) : ( +
+

+ Enter the email address associated with your account and we'll + send a link to reset your password. +

+
+ + setEmail(e.target.value)} + className="terminal-input" + required + autoComplete="email" + autoFocus + /> +
+ {error &&

ERR: {error}

} + +
+ + [BACK TO LOGIN] + +
+
+ )} +
+
+ ); +} diff --git a/web/src/components/LoginForm.tsx b/web/src/components/LoginForm.tsx index 2946d2b..9ae26d5 100644 --- a/web/src/components/LoginForm.tsx +++ b/web/src/components/LoginForm.tsx @@ -116,6 +116,16 @@ export function LoginForm() { : "[LOGIN]"} + {!isRegister && ( +
+ + [FORGOT PASSWORD?] + +
+ )} {!isRegister && (
+
+ ) : ( +
+
+ + setPassword(e.target.value)} + className="terminal-input" + required + minLength={8} + autoComplete="new-password" + autoFocus + /> +
+
+ + setConfirmPassword(e.target.value)} + className="terminal-input" + required + minLength={8} + autoComplete="new-password" + /> +
+ {(localError || error) && ( +

+ ERR: {localError || error} +

+ )} + +
+ + [BACK TO LOGIN] + +
+
+ )} + + + ); +} diff --git a/web/src/stores/auth.ts b/web/src/stores/auth.ts index 73e9535..614ed3c 100644 --- a/web/src/stores/auth.ts +++ b/web/src/stores/auth.ts @@ -62,6 +62,8 @@ interface AuthState { currentPassword: string, newPassword: string, ) => Promise; + requestPasswordReset: (email: string) => Promise; + resetPassword: (token: string, newPassword: string) => Promise; getPublicProfile: (userId: string) => Promise; listBlocks: () => Promise; blockUser: (userId: string) => Promise; @@ -178,6 +180,40 @@ export const useAuthStore = create((set) => ({ } }, + requestPasswordReset: async (email) => { + set({ isLoading: true, error: null }); + try { + await api.post("/auth/request-password-reset", { email }); + set({ isLoading: false }); + } catch (error) { + set({ + isLoading: false, + error: + error instanceof Error + ? error.message + : "Failed to request password reset", + }); + throw error; + } + }, + + resetPassword: async (token, newPassword) => { + set({ isLoading: true, error: null }); + try { + await api.post("/auth/reset-password", { token, new_password: newPassword }); + set({ isLoading: false }); + } catch (error) { + set({ + isLoading: false, + error: + error instanceof Error + ? error.message + : "Failed to reset password", + }); + throw error; + } + }, + clearError: () => set({ error: null }), getPublicProfile: async (userId) => api.get(`/users/${userId}/profile`), diff --git a/web/tsconfig.app.tsbuildinfo b/web/tsconfig.app.tsbuildinfo index 6e25078..6f7fc09 100644 --- a/web/tsconfig.app.tsbuildinfo +++ b/web/tsconfig.app.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/App.tsx","./src/main.tsx","./src/components/BotManager.tsx","./src/components/CalendarView.tsx","./src/components/ChannelList.tsx","./src/components/ChannelSettingsModal.tsx","./src/components/ChatArea.tsx","./src/components/CommandManager.tsx","./src/components/ConversationList.tsx","./src/components/CreateChannelModal.tsx","./src/components/CreateServerModal.tsx","./src/components/DMChat.tsx","./src/components/DocsView.tsx","./src/components/EmojiPicker.tsx","./src/components/ForumView.tsx","./src/components/GiphyPicker.tsx","./src/components/InstallPrompt.tsx","./src/components/InviteModal.tsx","./src/components/JoinServer.tsx","./src/components/JoinServerModal.tsx","./src/components/Layout.tsx","./src/components/ListView.tsx","./src/components/LoginForm.tsx","./src/components/MemberContextMenu.tsx","./src/components/MemberList.tsx","./src/components/MemberRoleAssign.tsx","./src/components/MentionDropdown.tsx","./src/components/MentionPopup.tsx","./src/components/MessageSearch.tsx","./src/components/MobileDrawer.tsx","./src/components/MobileNav.tsx","./src/components/NewConversationModal.tsx","./src/components/ReactionBar.tsx","./src/components/ReplyBar.tsx","./src/components/RoleManager.tsx","./src/components/ServerBar.tsx","./src/components/ServerSettingsModal.tsx","./src/components/SlashCommandPopup.tsx","./src/components/ThemeToggle.tsx","./src/components/ThreadListPanel.tsx","./src/components/ThreadPanel.tsx","./src/components/TypingIndicator.tsx","./src/components/UserProfileModal.tsx","./src/components/UserSettings.tsx","./src/components/VideoGrid.tsx","./src/components/VoiceChannel.tsx","./src/components/VoiceControls.tsx","./src/components/VoicePanel.tsx","./src/lib/api.ts","./src/lib/usePermissions.ts","./src/stores/auth.ts","./src/stores/bot.ts","./src/stores/channel.ts","./src/stores/conversation.ts","./src/stores/layout.ts","./src/stores/member.ts","./src/stores/message.ts","./src/stores/moderation.ts","./src/stores/notificationSettings.ts","./src/stores/permissions.ts","./src/stores/presence.ts","./src/stores/push.ts","./src/stores/readStates.ts","./src/stores/role.ts","./src/stores/server.ts","./src/stores/thread.ts","./src/stores/typing.ts","./src/stores/voice.ts","./src/stores/ws.ts"],"version":"5.9.3"} \ No newline at end of file +{"root":["./src/App.tsx","./src/main.tsx","./src/components/BotManager.tsx","./src/components/CalendarView.tsx","./src/components/ChannelList.tsx","./src/components/ChannelSettingsModal.tsx","./src/components/ChatArea.tsx","./src/components/CommandManager.tsx","./src/components/ConversationList.tsx","./src/components/CreateChannelModal.tsx","./src/components/CreateServerModal.tsx","./src/components/DMChat.tsx","./src/components/DocsView.tsx","./src/components/EmojiPicker.tsx","./src/components/ForgotPasswordPage.tsx","./src/components/ForumView.tsx","./src/components/GiphyPicker.tsx","./src/components/InstallPrompt.tsx","./src/components/InviteModal.tsx","./src/components/JoinServer.tsx","./src/components/JoinServerModal.tsx","./src/components/Layout.tsx","./src/components/ListView.tsx","./src/components/LoginForm.tsx","./src/components/MemberContextMenu.tsx","./src/components/MemberList.tsx","./src/components/MemberRoleAssign.tsx","./src/components/MentionDropdown.tsx","./src/components/MentionPopup.tsx","./src/components/MessageSearch.tsx","./src/components/MobileDrawer.tsx","./src/components/MobileNav.tsx","./src/components/NewConversationModal.tsx","./src/components/ReactionBar.tsx","./src/components/ReplyBar.tsx","./src/components/ResetPasswordPage.tsx","./src/components/RoleManager.tsx","./src/components/ServerBar.tsx","./src/components/ServerSettingsModal.tsx","./src/components/SlashCommandPopup.tsx","./src/components/ThemeToggle.tsx","./src/components/ThreadListPanel.tsx","./src/components/ThreadPanel.tsx","./src/components/TypingIndicator.tsx","./src/components/UserProfileModal.tsx","./src/components/UserSettings.tsx","./src/components/VideoGrid.tsx","./src/components/VoiceChannel.tsx","./src/components/VoiceControls.tsx","./src/components/VoicePanel.tsx","./src/lib/api.ts","./src/lib/usePermissions.ts","./src/stores/auth.ts","./src/stores/bot.ts","./src/stores/channel.ts","./src/stores/conversation.ts","./src/stores/layout.ts","./src/stores/member.ts","./src/stores/message.ts","./src/stores/moderation.ts","./src/stores/notificationSettings.ts","./src/stores/permissions.ts","./src/stores/presence.ts","./src/stores/push.ts","./src/stores/readStates.ts","./src/stores/role.ts","./src/stores/server.ts","./src/stores/thread.ts","./src/stores/typing.ts","./src/stores/voice.ts","./src/stores/ws.ts"],"version":"5.9.3"} \ No newline at end of file