Compare commits
3 الالتزامات
1fda64ec43
...
master
| المؤلف | SHA1 | التاريخ | |
|---|---|---|---|
| c2a0dc426c | |||
| 7ae3010b7f | |||
| e94d92bf3b |
@@ -1,23 +1,12 @@
|
|||||||
.git/
|
|
||||||
.gitignore
|
|
||||||
.dockerignore
|
|
||||||
docker-compose*
|
|
||||||
Dockerfile
|
|
||||||
makefile
|
|
||||||
htmlcov/
|
|
||||||
coverage.xml
|
|
||||||
.coverage*
|
|
||||||
|
|
||||||
.vscode/
|
|
||||||
*.dat
|
|
||||||
|
|
||||||
.DS_Store
|
|
||||||
node_modules
|
node_modules
|
||||||
/build
|
.git
|
||||||
/package
|
.gitignore
|
||||||
|
README.md
|
||||||
.env
|
.env
|
||||||
.env.*
|
.npm
|
||||||
!.env.example
|
.cache
|
||||||
vite.config.js.timestamp-*
|
dist
|
||||||
vite.config.ts.timestamp-*
|
*.log
|
||||||
.idea
|
.DS_Store
|
||||||
|
coverage
|
||||||
|
.nyc_output
|
||||||
@@ -9,7 +9,7 @@ AWS.config.update({
|
|||||||
|
|
||||||
|
|
||||||
const s3 = new AWS.S3({
|
const s3 = new AWS.S3({
|
||||||
endpoint: "https://s3-16cd54cc.hosted.cumin.dev", // Example for DigitalOcean Spaces
|
endpoint: "https://s3.eu-central-003.backblazeb2.com", // Example for DigitalOcean Spaces
|
||||||
s3ForcePathStyle: true, //s Often needed for S3-compatible services
|
s3ForcePathStyle: true, //s Often needed for S3-compatible services
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -74,9 +74,14 @@ app.use(middlewareErrorHandler);
|
|||||||
//console.log("Node Enviroment Mode:", nodeMode);
|
//console.log("Node Enviroment Mode:", nodeMode);
|
||||||
|
|
||||||
if (process.env.NODE_ENV === "production") {
|
if (process.env.NODE_ENV === "production") {
|
||||||
app.get("*", (_: Request, res: Response) => {
|
// Serve React app for specific client routes
|
||||||
|
const clientRoutes = ['/', '/home', '/login', '/register', '/forgot-password', '/settings'];
|
||||||
|
|
||||||
|
clientRoutes.forEach(route => {
|
||||||
|
app.get(route, (_: Request, res: Response) => {
|
||||||
res.sendFile(path.join(publicPath, "index.html"));
|
res.sendFile(path.join(publicPath, "index.html"));
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { server, serverHttps };
|
export default { server, serverHttps };
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
/* Basic Options */
|
/* Basic Options */
|
||||||
// "incremental": true, /* Enable incremental compilation */
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
|
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
|
||||||
"module": "CommonJS" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
|
"module": "node16" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
|
||||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||||
"allowJs": true /* Allow javascript files to be compiled. */,
|
"allowJs": true /* Allow javascript files to be compiled. */,
|
||||||
// "checkJs": true, /* Report errors in .js files. */
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
|
|
||||||
/* Module Resolution Options */
|
/* Module Resolution Options */
|
||||||
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
|
"moduleResolution": "node16" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
|
||||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
|
|||||||
47
debug-resend.ts
Normal file
47
debug-resend.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// debug-resend.js
|
||||||
|
require('dotenv').config();
|
||||||
|
const { Resend } = require('resend');
|
||||||
|
|
||||||
|
console.log('🔍 Debugging Resend...');
|
||||||
|
|
||||||
|
// Check environment
|
||||||
|
console.log('RESEND_API_KEY exists:', !!process.env.RESEND_API_KEY);
|
||||||
|
console.log('RESEND_API_KEY starts with:', process.env.RESEND_API_KEY ? process.env.RESEND_API_KEY.substring(0, 3) : 'N/A');
|
||||||
|
console.log('FROM_EMAIL:', process.env.FROM_EMAIL);
|
||||||
|
|
||||||
|
if (!process.env.RESEND_API_KEY) {
|
||||||
|
console.log('❌ RESEND_API_KEY is missing!');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resend = new Resend(process.env.RESEND_API_KEY);
|
||||||
|
|
||||||
|
async function test() {
|
||||||
|
try {
|
||||||
|
console.log('\n🚀 Testing Resend API...');
|
||||||
|
|
||||||
|
const { data, error } = await resend.emails.send({
|
||||||
|
from: process.env.FROM_EMAIL || 'myDrive <onboarding@resend.dev>',
|
||||||
|
to: 'hamodeh.mnzk@gmail.com',
|
||||||
|
subject: 'Resend Test',
|
||||||
|
text: 'This is a test email from Resend.',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.log('❌ Resend API Error:');
|
||||||
|
console.log('Error name:', error.name);
|
||||||
|
console.log('Error message:', error.message);
|
||||||
|
console.log('Error status:', error.statusCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ Email sent!');
|
||||||
|
console.log('Email ID:', data.id);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.log('❌ Unexpected error:');
|
||||||
|
console.log(err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test();
|
||||||
11
index.html
11
index.html
@@ -2,14 +2,17 @@
|
|||||||
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>myDrive</title>
|
<title>FlowSync | Drive</title>
|
||||||
<link rel="icon" href="/images/icon.png" />
|
<link rel="icon" href="/images/icon.png" />
|
||||||
<link rel="shortcut icon" type="image/png" href="/images/icon.png" />
|
<link rel="shortcut icon" type="image/png" href="/public/images/favicon-32x32.png" />
|
||||||
<link rel="shortcut icon" sizes="192x192" href="/images/icon.png" />
|
<link rel="shortcut icon" sizes="192x192" href="/public/images/favicon-32x32.png" />
|
||||||
<link rel="apple-touch-icon" href="/images/icon.png" />
|
<link rel="apple-touch-icon" href="/public/images/favicon-32x32.png" />
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
|
<script defer src="https://cloud.umami.is/script.js" data-website-id="ce3be986-8a39-489c-8e77-c77c253acbd4"></script>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
12
package.json
12
package.json
@@ -27,7 +27,7 @@
|
|||||||
"aws-sdk": "^2.657.0",
|
"aws-sdk": "^2.657.0",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^2.2.1",
|
||||||
"bytes": "^3.1.0",
|
"bytes": "^3.1.0",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"cli-progress": "^3.6.0",
|
"cli-progress": "^3.6.0",
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"express": "^4.19.2",
|
"express": "^5.1.0",
|
||||||
"express-validator": "^7.1.0",
|
"express-validator": "^7.1.0",
|
||||||
"fluent-ffmpeg": "^2.1.3",
|
"fluent-ffmpeg": "^2.1.3",
|
||||||
"helmet": "^3.21.2",
|
"helmet": "^3.21.2",
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"mongodb": "^6.20.0",
|
"mongodb": "^6.20.0",
|
||||||
"mongoose": "^8.4.1",
|
"mongoose": "^8.4.1",
|
||||||
"nodemailer": "^6.9.14",
|
"nodemailer": "^7.0.10",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
"password-prompt": "^1.1.2",
|
"password-prompt": "^1.1.2",
|
||||||
"progress-stream": "^2.0.0",
|
"progress-stream": "^2.0.0",
|
||||||
@@ -64,6 +64,7 @@
|
|||||||
"react-toastify": "^10.0.5",
|
"react-toastify": "^10.0.5",
|
||||||
"redux": "^5.0.1",
|
"redux": "^5.0.1",
|
||||||
"regenerator-runtime": "^0.13.3",
|
"regenerator-runtime": "^0.13.3",
|
||||||
|
"resend": "^6.5.2",
|
||||||
"sharp": "^0.33.4",
|
"sharp": "^0.33.4",
|
||||||
"sweetalert2": "^11.15.10",
|
"sweetalert2": "^11.15.10",
|
||||||
"temp": "^0.9.1",
|
"temp": "^0.9.1",
|
||||||
@@ -88,6 +89,7 @@
|
|||||||
"@types/connect-busboy": "0.0.2",
|
"@types/connect-busboy": "0.0.2",
|
||||||
"@types/cookie-parser": "^1.4.2",
|
"@types/cookie-parser": "^1.4.2",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
|
"@types/dotenv": "^6.1.1",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/fluent-ffmpeg": "^2.1.24",
|
"@types/fluent-ffmpeg": "^2.1.24",
|
||||||
"@types/helmet": "0.0.45",
|
"@types/helmet": "0.0.45",
|
||||||
@@ -128,7 +130,7 @@
|
|||||||
"tailwindcss": "^3.4.4",
|
"tailwindcss": "^3.4.4",
|
||||||
"typescript": "^5.4.5",
|
"typescript": "^5.4.5",
|
||||||
"typescript-eslint": "^7.14.1",
|
"typescript-eslint": "^7.14.1",
|
||||||
"vite": "^5.2.13",
|
"vite": "^6.0.0",
|
||||||
"vite-plugin-pwa": "^0.21.1"
|
"vite-plugin-pwa": "^1.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
ثنائية
public/images/favicon-32x32.png
Normal file
ثنائية
public/images/favicon-32x32.png
Normal file
ملف ثنائي غير معروض.
|
بعد العرض: | الارتفاع: | الحجم: 785 B |
ثنائية
public/images/icon.png
ثنائية
public/images/icon.png
ملف ثنائي غير معروض.
|
قبل العرض: | الارتفاع: | الحجم: 6.3 KiB |
@@ -4,7 +4,7 @@ import axios from "../axiosInterceptor";
|
|||||||
|
|
||||||
export const getUserToken = async () => {
|
export const getUserToken = async () => {
|
||||||
const response = await axios.post("/user-service/get-token");
|
const response = await axios.post("/user-service/get-token");
|
||||||
response.data;
|
return response.data; // FIXED: Added return
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUserAPI = async () => {
|
export const getUserAPI = async () => {
|
||||||
@@ -54,6 +54,12 @@ export const getAccessToken = async (uuid: string) => {
|
|||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const sendPasswordResetAPI = async (email: string) => {
|
||||||
|
const response = await axios.post("/api/user-service/send-password-reset", {
|
||||||
|
email,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
// PATCH
|
// PATCH
|
||||||
|
|
||||||
export const changePasswordAPI = async (
|
export const changePasswordAPI = async (
|
||||||
@@ -79,13 +85,6 @@ export const verifyEmailAPI = async (emailToken: string) => {
|
|||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendPasswordResetAPI = async (email: string) => {
|
|
||||||
const response = await axios.patch("/user-service/send-password-reset", {
|
|
||||||
email,
|
|
||||||
});
|
|
||||||
return response.data;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const resetPasswordAPI = async (
|
export const resetPasswordAPI = async (
|
||||||
password: string,
|
password: string,
|
||||||
passwordToken: string
|
passwordToken: string
|
||||||
|
|||||||
116
src/components/ForgotPasswordPage/ForgotPasswordPage.tsx
Normal file
116
src/components/ForgotPasswordPage/ForgotPasswordPage.tsx
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { toast, ToastContainer } from 'react-toastify';
|
||||||
|
import { sendPasswordResetAPI } from '../../api/userAPI';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import Spinner from '../Spinner/Spinner';
|
||||||
|
|
||||||
|
const ForgotPasswordPage = () => {
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
console.log('🔄 Form submitted with email:', email);
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
toast.error('Please enter your email');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('📞 Calling sendPasswordResetAPI...');
|
||||||
|
await sendPasswordResetAPI(email);
|
||||||
|
console.log('✅ API call completed');
|
||||||
|
toast.success('If an account exists, a password reset email has been sent.');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate('/login');
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error('💥 Forgot password error:', error);
|
||||||
|
// Still show success message for security
|
||||||
|
toast.success('If an account exists, a password reset email has been sent.');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center p-4">
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
{/* Logo Section with Infinity Symbol */}
|
||||||
|
<div className="flex justify-center mb-6">
|
||||||
|
<div className="flex items-center justify-center w-16 h-16 rounded-2xl bg-gradient-to-br from-blue-500 to-blue-600 shadow-lg">
|
||||||
|
{!loading ? (
|
||||||
|
<svg
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="white"
|
||||||
|
strokeWidth="2"
|
||||||
|
>
|
||||||
|
{/* Infinity Symbol */}
|
||||||
|
<path
|
||||||
|
d="M18.178 8c5.096 0 5.096 8 0 8-5.095 0-7.133-8-12.228-8-5.095 0-5.095 8 0 8 5.095 0 7.133-8 12.228-8z"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<Spinner />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Card Container */}
|
||||||
|
<div className="bg-white rounded-2xl shadow-xl p-8">
|
||||||
|
<h2 className="text-2xl font-bold text-gray-800 text-center mb-3">
|
||||||
|
Forgot Password
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p className="text-gray-600 text-center mb-6">
|
||||||
|
Enter your email address and we'll send you a link to reset your password.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
placeholder="Enter your email address"
|
||||||
|
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
required
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={loading || !email}
|
||||||
|
className="w-full bg-blue-500 hover:bg-blue-600 disabled:bg-blue-300 text-white font-medium py-3 px-4 rounded-lg transition-colors disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{loading ? 'Sending Reset Link...' : 'Send Reset Link'}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="text-center mt-6">
|
||||||
|
<button
|
||||||
|
onClick={() => navigate('/login')}
|
||||||
|
className="text-blue-500 hover:text-blue-600 font-medium text-sm transition-colors"
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
← Back to Login
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ToastContainer position="bottom-left" pauseOnFocusLoss={false} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ForgotPasswordPage;
|
||||||
@@ -9,7 +9,7 @@ const LandingPage = () => {
|
|||||||
<div className="landing-page">
|
<div className="landing-page">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="header">
|
<div className="header">
|
||||||
<div className="logo">🌊 FlowSync</div>
|
<div className="logo">♾️ FlowSync</div>
|
||||||
<div className="header-buttons">
|
<div className="header-buttons">
|
||||||
<button className="btn-secondary" onClick={() => navigate('/login')}>Sign In</button>
|
<button className="btn-secondary" onClick={() => navigate('/login')}>Sign In</button>
|
||||||
<button className="btn-primary" onClick={() => navigate('/register')}>Get Started</button>
|
<button className="btn-primary" onClick={() => navigate('/register')}>Get Started</button>
|
||||||
@@ -516,8 +516,7 @@ const LandingPage = () => {
|
|||||||
<div className="footer">
|
<div className="footer">
|
||||||
<div className="footer-content">
|
<div className="footer-content">
|
||||||
<div className="footer-brand">
|
<div className="footer-brand">
|
||||||
<div className="logo">🌊 FlowSpace</div>
|
<div className="logo">♾️ FlowSync</div> <p>Innovating the future of digital storage and collaboration</p>
|
||||||
<p>Innovating the future of digital storage and collaboration</p>
|
|
||||||
<p className="footer-tagline">Secure. Simple. Seamless.</p>
|
<p className="footer-tagline">Secure. Simple. Seamless.</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="footer-links">
|
<div className="footer-links">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState, useCallback ,FormEvent } from "react";
|
||||||
import {
|
import {
|
||||||
createAccountAPI,
|
createAccountAPI,
|
||||||
getUserAPI,
|
getUserAPI,
|
||||||
@@ -16,6 +16,8 @@ import Swal from "sweetalert2";
|
|||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
import isEmail from "validator/es/lib/isEmail";
|
import isEmail from "validator/es/lib/isEmail";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ADD THE defaultView PROP HERE
|
// ADD THE defaultView PROP HERE
|
||||||
const LoginPage = ({ defaultView = "login" }) => {
|
const LoginPage = ({ defaultView = "login" }) => {
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
@@ -30,7 +32,7 @@ const LoginPage = ({ defaultView = "login" }) => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const lastSentPassowordReset = useRef(0);
|
const lastSentPassowordReset = useRef(0);
|
||||||
|
|
||||||
const attemptLoginWithToken = async () => {
|
const attemptLoginWithToken = useCallback(async () => {
|
||||||
setAttemptingLogin(true);
|
setAttemptingLogin(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -47,7 +49,7 @@ const LoginPage = ({ defaultView = "login" }) => {
|
|||||||
window.localStorage.removeItem("hasPreviouslyLoggedIn");
|
window.localStorage.removeItem("hasPreviouslyLoggedIn");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}, [location.state?.from?.pathname, dispatch, navigate]);
|
||||||
|
|
||||||
const login = async () => {
|
const login = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -147,7 +149,7 @@ const LoginPage = ({ defaultView = "login" }) => {
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const onSubmit = (e: any) => {
|
const onSubmit = (e: FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (mode === "login") {
|
if (mode === "login") {
|
||||||
login();
|
login();
|
||||||
@@ -290,7 +292,7 @@ const LoginPage = ({ defaultView = "login" }) => {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="text-[#3182ce] text-[14px] font-medium no-underline mr-4 hover:text-[#2c5aa0] transition-colors duration-200"
|
className="text-[#3182ce] text-[14px] font-medium no-underline mr-4 hover:text-[#2c5aa0] transition-colors duration-200"
|
||||||
onClick={() => setMode("reset")}
|
onClick={() => navigate("/forgot-password")} // This navigates to the actual page
|
||||||
>
|
>
|
||||||
Forgot?
|
Forgot?
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import Homepage from "../components/Homepage/Homepage";
|
|||||||
import SettingsPage from "../components/SettingsPage/SettingsPage";
|
import SettingsPage from "../components/SettingsPage/SettingsPage";
|
||||||
import LandingPage from '../components/LandingPage/LandingPage';
|
import LandingPage from '../components/LandingPage/LandingPage';
|
||||||
import PrivateRoute from "./PrivateRoute";
|
import PrivateRoute from "./PrivateRoute";
|
||||||
|
import ForgotPasswordPage from "../components/ForgotPasswordPage/ForgotPasswordPage";
|
||||||
|
|
||||||
const AppRouter = () => {
|
const AppRouter = () => {
|
||||||
return (
|
return (
|
||||||
@@ -23,6 +24,7 @@ const AppRouter = () => {
|
|||||||
<Route path="/welcome" element={<Navigate to="/" replace />} />
|
<Route path="/welcome" element={<Navigate to="/" replace />} />
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
<Route path="/register" element={<LoginPage defaultView="create" />} />
|
<Route path="/register" element={<LoginPage defaultView="create" />} />
|
||||||
|
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
|
||||||
<Route path="/public-download/:id/:tempToken" element={<DownloadPage />} />
|
<Route path="/public-download/:id/:tempToken" element={<DownloadPage />} />
|
||||||
<Route path="/verify-email/:token" element={<VerifyEmailPage />} />
|
<Route path="/verify-email/:token" element={<VerifyEmailPage />} />
|
||||||
<Route path="/reset-password/:token" element={<ResetPasswordPage />} />
|
<Route path="/reset-password/:token" element={<ResetPasswordPage />} />
|
||||||
|
|||||||
@@ -20,6 +20,6 @@
|
|||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src", "backend/utils/emailService.ts"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
|
|||||||
المرجع في مشكلة جديدة
حظر مستخدم