mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-09 19:22:21 +00:00
Compare commits
2 Commits
test/uplot
...
feat/self-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78c97c670e | ||
|
|
491791f2c4 |
@@ -202,6 +202,10 @@ export const PasswordReset = Loadable(
|
||||
() => import(/* webpackChunkName: "ResetPassword" */ 'pages/ResetPassword'),
|
||||
);
|
||||
|
||||
export const ForgotPassword = Loadable(
|
||||
() => import(/* webpackChunkName: "ForgotPassword" */ 'pages/ForgotPassword'),
|
||||
);
|
||||
|
||||
export const SomethingWentWrong = Loadable(
|
||||
() =>
|
||||
import(
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
DashboardWidget,
|
||||
EditRulesPage,
|
||||
ErrorDetails,
|
||||
ForgotPassword,
|
||||
Home,
|
||||
InfrastructureMonitoring,
|
||||
InstalledIntegrations,
|
||||
@@ -353,6 +354,13 @@ const routes: AppRoutes[] = [
|
||||
key: 'PASSWORD_RESET',
|
||||
isPrivate: false,
|
||||
},
|
||||
{
|
||||
path: ROUTES.FORGOT_PASSWORD,
|
||||
exact: true,
|
||||
component: ForgotPassword,
|
||||
key: 'FORGOT_PASSWORD',
|
||||
isPrivate: false,
|
||||
},
|
||||
{
|
||||
path: ROUTES.SOMETHING_WENT_WRONG,
|
||||
exact: true,
|
||||
|
||||
15
frontend/src/api/v2/factor_password/forgotPassword.ts
Normal file
15
frontend/src/api/v2/factor_password/forgotPassword.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ApiV2Instance } from 'api';
|
||||
|
||||
interface ForgotPasswordPayload {
|
||||
orgId: string;
|
||||
email: string;
|
||||
frontendBaseURL: string;
|
||||
}
|
||||
|
||||
const forgotPassword = async (
|
||||
payload: ForgotPasswordPayload,
|
||||
): Promise<void> => {
|
||||
await ApiV2Instance.post('/factor_password/forgot', payload);
|
||||
};
|
||||
|
||||
export default forgotPassword;
|
||||
@@ -50,6 +50,7 @@ const ROUTES = {
|
||||
LIVE_LOGS: '/logs/logs-explorer/live',
|
||||
LOGS_PIPELINES: '/logs/pipelines',
|
||||
PASSWORD_RESET: '/password-reset',
|
||||
FORGOT_PASSWORD: '/forgot-password',
|
||||
LIST_LICENSES: '/licenses',
|
||||
LOGS_INDEX_FIELDS: '/logs-explorer/index-fields',
|
||||
TRACE_EXPLORER: '/trace-explorer',
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
.forgot-password-card {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.forgot-password-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.forgot-password-header-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
background: var(--bg-slate-400);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16px;
|
||||
color: var(--text-vanilla-400);
|
||||
|
||||
&--success {
|
||||
background: var(--bg-forest-400);
|
||||
color: var(--text-forest-400);
|
||||
}
|
||||
}
|
||||
|
||||
.forgot-password-header-title {
|
||||
margin-bottom: 8px !important;
|
||||
}
|
||||
|
||||
.forgot-password-header-subtitle {
|
||||
color: var(--text-vanilla-400);
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.forgot-password-form-container {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.forgot-password-field-container {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.forgot-password-form-input {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.forgot-password-warning-callout {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.forgot-password-form-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.forgot-password-back-button {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.forgot-password-submit-button {
|
||||
flex: 1;
|
||||
}
|
||||
179
frontend/src/container/ForgotPassword/index.tsx
Normal file
179
frontend/src/container/ForgotPassword/index.tsx
Normal file
@@ -0,0 +1,179 @@
|
||||
import './ForgotPassword.styles.scss';
|
||||
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Callout } from '@signozhq/callout';
|
||||
import { Form, Input, Typography } from 'antd';
|
||||
import forgotPasswordApi from 'api/v2/factor_password/forgotPassword';
|
||||
import AuthError from 'components/AuthError/AuthError';
|
||||
import AuthPageContainer from 'components/AuthPageContainer';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import { ArrowLeft, ArrowRight, CheckCircle, KeyRound } from 'lucide-react';
|
||||
import { Label } from 'pages/SignUp/styles';
|
||||
import { useState } from 'react';
|
||||
import { useLocation } from 'react-use';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import { FormContainer } from './styles';
|
||||
|
||||
type FormValues = { email: string };
|
||||
|
||||
function ForgotPassword(): JSX.Element {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState<APIError | null>(null);
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const { search } = useLocation();
|
||||
const params = new URLSearchParams(search);
|
||||
const emailFromQuery = params.get('email') || '';
|
||||
const orgIdFromQuery = params.get('orgId') || '';
|
||||
|
||||
const [form] = Form.useForm<FormValues>();
|
||||
|
||||
const handleSubmit = async (): Promise<void> => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setErrorMessage(null);
|
||||
const { email } = form.getFieldsValue();
|
||||
|
||||
await forgotPasswordApi({
|
||||
email: email || emailFromQuery,
|
||||
orgId: orgIdFromQuery,
|
||||
frontendBaseURL: window.location.origin,
|
||||
});
|
||||
|
||||
setSubmitted(true);
|
||||
notifications.success({
|
||||
message: 'Password reset email sent',
|
||||
});
|
||||
} catch (error) {
|
||||
setErrorMessage(error as APIError);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBackToLogin = (): void => {
|
||||
history.push(ROUTES.LOGIN);
|
||||
};
|
||||
|
||||
// Success state after submission
|
||||
if (submitted) {
|
||||
return (
|
||||
<AuthPageContainer>
|
||||
<div className="forgot-password-card">
|
||||
<div className="forgot-password-header">
|
||||
<div className="forgot-password-header-icon forgot-password-header-icon--success">
|
||||
<CheckCircle size={32} />
|
||||
</div>
|
||||
<Typography.Title level={4} className="forgot-password-header-title">
|
||||
Check Your Email
|
||||
</Typography.Title>
|
||||
<Typography.Paragraph className="forgot-password-header-subtitle">
|
||||
We have sent a password reset link to your email address. Please check
|
||||
your inbox and follow the instructions to reset your password.
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
|
||||
<div className="forgot-password-form-actions">
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
onClick={handleBackToLogin}
|
||||
className="forgot-password-submit-button"
|
||||
prefixIcon={<ArrowLeft size={16} />}
|
||||
>
|
||||
Back to Login
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</AuthPageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthPageContainer>
|
||||
<div className="forgot-password-card">
|
||||
<div className="forgot-password-header">
|
||||
<div className="forgot-password-header-icon">
|
||||
<KeyRound size={32} />
|
||||
</div>
|
||||
<Typography.Title level={4} className="forgot-password-header-title">
|
||||
Forgot Password?
|
||||
</Typography.Title>
|
||||
<Typography.Paragraph className="forgot-password-header-subtitle">
|
||||
Enter your email address and we will send you a link to reset your
|
||||
password.
|
||||
</Typography.Paragraph>
|
||||
</div>
|
||||
|
||||
<FormContainer
|
||||
form={form}
|
||||
onFinish={handleSubmit}
|
||||
className="forgot-password-form"
|
||||
>
|
||||
<div className="forgot-password-form-container">
|
||||
<div className="forgot-password-form-fields">
|
||||
<div className="forgot-password-field-container">
|
||||
<Label htmlFor="email">Email Address</Label>
|
||||
<Form.Item
|
||||
name="email"
|
||||
initialValue={emailFromQuery}
|
||||
rules={[
|
||||
{ required: true, message: 'Please enter your email!' },
|
||||
{ type: 'email', message: 'Please enter a valid email!' },
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
type="email"
|
||||
id="email"
|
||||
data-testid="email"
|
||||
placeholder="Enter your email address"
|
||||
className="forgot-password-form-input"
|
||||
disabled={!!emailFromQuery}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!orgIdFromQuery && (
|
||||
<Callout
|
||||
type="warning"
|
||||
size="small"
|
||||
className="forgot-password-warning-callout"
|
||||
description="Please go back to the login page and enter your email first to reset your password."
|
||||
/>
|
||||
)}
|
||||
|
||||
{errorMessage && <AuthError error={errorMessage} />}
|
||||
|
||||
<div className="forgot-password-form-actions">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={handleBackToLogin}
|
||||
className="forgot-password-back-button"
|
||||
prefixIcon={<ArrowLeft size={16} />}
|
||||
>
|
||||
Back to Login
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={loading || !orgIdFromQuery}
|
||||
className="forgot-password-submit-button"
|
||||
suffixIcon={<ArrowRight size={16} />}
|
||||
>
|
||||
{loading ? 'Sending...' : 'Send Reset Link'}
|
||||
</Button>
|
||||
</div>
|
||||
</FormContainer>
|
||||
</div>
|
||||
</AuthPageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default ForgotPassword;
|
||||
8
frontend/src/container/ForgotPassword/styles.ts
Normal file
8
frontend/src/container/ForgotPassword/styles.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Form } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const FormContainer = styled(Form)`
|
||||
& .ant-form-item {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
`;
|
||||
@@ -1,7 +1,7 @@
|
||||
import './Login.styles.scss';
|
||||
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Form, Input, Select, Tooltip, Typography } from 'antd';
|
||||
import { Form, Input, Select, Typography } from 'antd';
|
||||
import getVersion from 'api/v1/version/get';
|
||||
import get from 'api/v2/sessions/context/get';
|
||||
import post from 'api/v2/sessions/email_password/post';
|
||||
@@ -343,11 +343,19 @@ function Login(): JSX.Element {
|
||||
<ParentContainer>
|
||||
<div className="password-label-container">
|
||||
<Label htmlFor="Password">Password</Label>
|
||||
<Tooltip title="Ask your admin to reset your password and send you a new invite link">
|
||||
<Typography.Link className="forgot-password-link">
|
||||
Forgot password?
|
||||
</Typography.Link>
|
||||
</Tooltip>
|
||||
<Typography.Link
|
||||
className="forgot-password-link"
|
||||
onClick={(): void => {
|
||||
const email = form.getFieldValue('email');
|
||||
history.push(
|
||||
`${ROUTES.FORGOT_PASSWORD}?email=${encodeURIComponent(
|
||||
email,
|
||||
)}&orgId=${encodeURIComponent(sessionsOrgId)}`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
Forgot password?
|
||||
</Typography.Link>
|
||||
</div>
|
||||
<FormContainer.Item name="password">
|
||||
<Input.Password
|
||||
|
||||
7
frontend/src/pages/ForgotPassword/index.tsx
Normal file
7
frontend/src/pages/ForgotPassword/index.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import ForgotPasswordContainer from 'container/ForgotPassword';
|
||||
|
||||
function ForgotPassword(): JSX.Element {
|
||||
return <ForgotPasswordContainer />;
|
||||
}
|
||||
|
||||
export default ForgotPassword;
|
||||
@@ -70,6 +70,7 @@ export const routePermission: Record<keyof typeof ROUTES, ROLES[]> = {
|
||||
LOGIN: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
NOT_FOUND: ['ADMIN', 'VIEWER', 'EDITOR'],
|
||||
PASSWORD_RESET: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
FORGOT_PASSWORD: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
SERVICE_METRICS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
SETTINGS: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
SIGN_UP: ['ADMIN', 'EDITOR', 'VIEWER'],
|
||||
|
||||
Reference in New Issue
Block a user