feat(password change): added in password link to email and change in lst
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -150,3 +150,4 @@ dist
|
|||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
backend-0.1.3.zip
|
backend-0.1.3.zip
|
||||||
|
BulkForecastTemplate
|
||||||
|
|||||||
193
frontend/src/components/user/PasswordChange.tsx
Normal file
193
frontend/src/components/user/PasswordChange.tsx
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
import { useForm } from "@tanstack/react-form";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
import axios from "axios";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { LstCard } from "../extendedUI/LstCard";
|
||||||
|
import { CardContent, CardHeader } from "../ui/card";
|
||||||
|
|
||||||
|
import { Label } from "../ui/label";
|
||||||
|
import { Input } from "../ui/input";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
|
||||||
|
export default function PasswordChange() {
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const token = localStorage.getItem("auth_token");
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
defaultValues: {
|
||||||
|
password: "",
|
||||||
|
confirmPassword: "",
|
||||||
|
},
|
||||||
|
onSubmit: async ({ value }) => {
|
||||||
|
setSaving(true);
|
||||||
|
try {
|
||||||
|
const res = await axios.patch("/api/auth/profile", value, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
|
||||||
|
//console.log(res.data);
|
||||||
|
|
||||||
|
if (res.data.success) {
|
||||||
|
localStorage.removeItem("auth_token");
|
||||||
|
navigate({ to: "/login" });
|
||||||
|
form.reset();
|
||||||
|
toast.success(`Your password was just updated.`);
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res.data.success) {
|
||||||
|
toast.error(res.data.message);
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
toast.error("There was an error updating your password");
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<LstCard className="w-96 ml-7">
|
||||||
|
<CardHeader>
|
||||||
|
<p>Password Change Form</p>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<form.Field
|
||||||
|
name="password"
|
||||||
|
// We can choose between form-wide and field-specific validators
|
||||||
|
validators={{
|
||||||
|
onChangeAsyncDebounceMs: 500,
|
||||||
|
onChangeAsync: ({ value }) => {
|
||||||
|
// if (
|
||||||
|
// window.location.pathname.includes(
|
||||||
|
// "/users"
|
||||||
|
// ) &&
|
||||||
|
// value.length === 0
|
||||||
|
// ) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
if (value.length < 4) {
|
||||||
|
return "Password must be at least 4 characters long.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/[A-Z]/.test(value)) {
|
||||||
|
return "Password must contain at least one uppercase letter.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/[a-z]/.test(value)) {
|
||||||
|
return "Password must contain at least one lower case letter.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/[0-9]/.test(value)) {
|
||||||
|
return "Password must contain at least one number.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(
|
||||||
|
value
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return "Password must contain at least one special character.";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
children={(field) => {
|
||||||
|
return (
|
||||||
|
<div className="m-2 min-w-48 max-w-96 p-2">
|
||||||
|
<Label
|
||||||
|
htmlFor="password1"
|
||||||
|
className="mb-2"
|
||||||
|
>
|
||||||
|
Password
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
name={field.name}
|
||||||
|
value={field.state.value}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
type="password"
|
||||||
|
onChange={(e) =>
|
||||||
|
field.handleChange(
|
||||||
|
e.target.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{field.state.meta.errors.length ? (
|
||||||
|
<em>
|
||||||
|
{field.state.meta.errors.join(
|
||||||
|
","
|
||||||
|
)}
|
||||||
|
</em>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<form.Field
|
||||||
|
name="confirmPassword"
|
||||||
|
validators={{
|
||||||
|
onChange: ({ value, fieldApi }) => {
|
||||||
|
const password =
|
||||||
|
fieldApi.form.getFieldValue("password");
|
||||||
|
if (value !== password) {
|
||||||
|
return "Passwords do not match.";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(field) => {
|
||||||
|
return (
|
||||||
|
<div className="m-2 min-w-48 max-w-96 p-2">
|
||||||
|
<Label
|
||||||
|
htmlFor="confirmPassword"
|
||||||
|
className="mb-2"
|
||||||
|
>
|
||||||
|
Confirm Password
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
name={field.name}
|
||||||
|
value={field.state.value}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
type="password"
|
||||||
|
onChange={(e) =>
|
||||||
|
field.handleChange(
|
||||||
|
e.target.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{field.state.meta.errors.length ? (
|
||||||
|
<em>
|
||||||
|
{field.state.meta.errors.join(
|
||||||
|
", "
|
||||||
|
)}
|
||||||
|
</em>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</form.Field>
|
||||||
|
<div className="mt-4 ml-4 flex justify-end">
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
onClick={form.handleSubmit}
|
||||||
|
disabled={saving}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</CardContent>
|
||||||
|
</LstCard>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
28
frontend/src/routes/(user)/passwordChange.tsx
Normal file
28
frontend/src/routes/(user)/passwordChange.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import PasswordChange from "@/components/user/PasswordChange";
|
||||||
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/(user)/passwordChange")({
|
||||||
|
component: RouteComponent,
|
||||||
|
beforeLoad: async () => {
|
||||||
|
const auth = localStorage.getItem("auth_token");
|
||||||
|
if (!auth) {
|
||||||
|
throw redirect({
|
||||||
|
to: "/login",
|
||||||
|
search: {
|
||||||
|
// Use the current location to power a redirect after login
|
||||||
|
// (Do not use `router.state.resolvedLocation` as it can
|
||||||
|
// potentially lag behind the actual current location)
|
||||||
|
redirect: location.pathname + location.search,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PasswordChange />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -80,8 +80,12 @@ export const Route = createRootRoute({
|
|||||||
Hello {user?.username}
|
Hello {user?.username}
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
{/* <DropdownMenuItem>Profile</DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
<DropdownMenuItem>Billing</DropdownMenuItem>
|
<Link to="/passwordChange">
|
||||||
|
Password Change
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
{/* <DropdownMenuItem>Billing</DropdownMenuItem>
|
||||||
<DropdownMenuItem>Team</DropdownMenuItem>
|
<DropdownMenuItem>Team</DropdownMenuItem>
|
||||||
<DropdownMenuItem>Subscription</DropdownMenuItem> */}
|
<DropdownMenuItem>Subscription</DropdownMenuItem> */}
|
||||||
<hr className="solid"></hr>
|
<hr className="solid"></hr>
|
||||||
|
|||||||
106
frontend/src/routes/register.tsx
Normal file
106
frontend/src/routes/register.tsx
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import RegisterForm from "@/components/auth/Register";
|
||||||
|
import { LstCard } from "@/components/extendedUI/LstCard";
|
||||||
|
import { CardContent, CardHeader } from "@/components/ui/card";
|
||||||
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/register")({
|
||||||
|
component: RouteComponent,
|
||||||
|
beforeLoad: () => {
|
||||||
|
const isLoggedIn = localStorage.getItem("auth_token");
|
||||||
|
if (isLoggedIn) {
|
||||||
|
throw redirect({
|
||||||
|
to: "/",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row w-5/6 justify-between gap-2">
|
||||||
|
<RegisterForm />
|
||||||
|
<div>
|
||||||
|
<LstCard>
|
||||||
|
<CardHeader>
|
||||||
|
<h2 className="text-2xl font-bold mb-4">
|
||||||
|
Disclaimer and User Agreement
|
||||||
|
</h2>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="max-w-2xl mx-auto p-6 rounded-2xl shadow-md">
|
||||||
|
<ul className="list-decimal list-inside space-y-3">
|
||||||
|
<li>
|
||||||
|
<span className="font-bold">
|
||||||
|
Authentication Notice:
|
||||||
|
</span>
|
||||||
|
To interact with the Alpla prod through this
|
||||||
|
application, you must use your{" "}
|
||||||
|
<span className="font-semibold">
|
||||||
|
Windows login credentials
|
||||||
|
</span>
|
||||||
|
. These credentials are used solely for
|
||||||
|
authentication purposes.
|
||||||
|
</li>
|
||||||
|
{/* <li>
|
||||||
|
<span className="font-bold">
|
||||||
|
Password Privacy and Security:
|
||||||
|
</span>
|
||||||
|
This application{" "}
|
||||||
|
<span className="font-semibold">
|
||||||
|
does not store, sync, or transmit
|
||||||
|
</span>{" "}
|
||||||
|
your Windows password in any form.
|
||||||
|
Authentication is handled securely, and your
|
||||||
|
credentials are never logged or accessible
|
||||||
|
to the developers or third parties.
|
||||||
|
</li> */}
|
||||||
|
<li>
|
||||||
|
<span className="font-bold">
|
||||||
|
Data Handling:
|
||||||
|
</span>
|
||||||
|
All data accessed through the Alpla prod
|
||||||
|
remains the property of Alpla. The app
|
||||||
|
functions as a secure interface for
|
||||||
|
accessing and updating this information
|
||||||
|
where permitted by your role.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className="font-bold">
|
||||||
|
User Responsibility:
|
||||||
|
</span>
|
||||||
|
You are responsible for any actions
|
||||||
|
performed using your account. Please ensure
|
||||||
|
you do not share your credentials with
|
||||||
|
others and always log out of the application
|
||||||
|
when not in use.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className="font-bold">
|
||||||
|
No Warranty:
|
||||||
|
</span>
|
||||||
|
This application is provided "
|
||||||
|
<span className="italic">as is</span>"
|
||||||
|
without any warranty of any kind, either
|
||||||
|
expressed or implied. While every effort is
|
||||||
|
made to ensure functionality and data
|
||||||
|
accuracy, the app is subject to ongoing
|
||||||
|
development and may contain bugs or require
|
||||||
|
updates.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className="font-bold">
|
||||||
|
Consent Required:
|
||||||
|
</span>
|
||||||
|
By signing up or continuing to use this
|
||||||
|
application, you agree to the above terms
|
||||||
|
and acknowledge that you understand how your
|
||||||
|
login information is used.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</LstCard>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -26,7 +26,13 @@
|
|||||||
|
|
||||||
For security reasons, we strongly recommend changing your password as soon as possible.<br/><br/>
|
For security reasons, we strongly recommend changing your password as soon as possible.<br/><br/>
|
||||||
|
|
||||||
You can update it by logging into your account and navigating to the password settings section.<br/><br/>
|
You can update it by logging into your account, clicking your profile at the top right and click password change.<br/><br/>
|
||||||
|
|
||||||
|
|
||||||
|
Or <a href="http://{{server}}:{{port}}/passwordChange"
|
||||||
|
style="display:inline-block; padding:10px 20px; text-decoration:none; border-radius:5px;">
|
||||||
|
Click Here
|
||||||
|
</a> to login and change your password.<br/><br/>
|
||||||
|
|
||||||
Best regards,<br/><br/>
|
Best regards,<br/><br/>
|
||||||
LST team<br/>
|
LST team<br/>
|
||||||
|
|||||||
Reference in New Issue
Block a user