Update environment configuration, Docker setup, and frontend/backend dependencies
- Updated .env.example and .env files for backend and frontend with placeholder values. - Enhanced .gitignore to include additional files and directories. - Modified docker-compose.yml to change image names and improve service health checks. - Updated README.md and SETUP_THE_PROJECT.md for clearer setup instructions. - Added database initialization scripts and SQL files for structured database setup. - Updated frontend Dockerfile to use Node.js 22 and adjusted package.json scripts. - Improved error handling and logging in start scripts for better debugging. - Added reCAPTCHA support in the signup page with conditional loading based on environment variables.
This commit is contained in:
@@ -5,13 +5,14 @@ VITE_APP_TITLE=Worklenz
|
||||
VITE_APP_ENV=development
|
||||
|
||||
# Mixpanel
|
||||
VITE_MIXPANEL_TOKEN=bb330b6bd25db4a6c988da89046f4b80
|
||||
VITE_MIXPANEL_TOKEN=mixpanel-token
|
||||
|
||||
# Recaptcha
|
||||
VITE_RECAPTCHA_SITE_KEY=6LeUWjYqAAAAAFhi9Z8KPeiix3RRjxoZtJhLJZXb
|
||||
VITE_ENABLE_RECAPTCHA=false
|
||||
VITE_RECAPTCHA_SITE_KEY=recaptcha-site-key
|
||||
|
||||
# Session ID
|
||||
VITE_WORKLENZ_SESSION_ID=worklenz.sid
|
||||
VITE_WORKLENZ_SESSION_ID=worklenz-session-id
|
||||
|
||||
# Google Login
|
||||
VITE_ENABLE_GOOGLE_LOGIN=true
|
||||
VITE_ENABLE_GOOGLE_LOGIN=false
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:18-alpine AS build
|
||||
FROM node:22-alpine AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -9,7 +9,7 @@ RUN npm ci
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM node:18-alpine AS production
|
||||
FROM node:22-alpine AS production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Worklenz - React Application
|
||||
# Worklenz - React Frontend
|
||||
|
||||
Worklenz is a task management application built with React and bundled using [Vite](https://vitejs.dev/).
|
||||
Worklenz is a project management application built with React, TypeScript, and Ant Design. The project is bundled using [Vite](https://vitejs.dev/).
|
||||
|
||||
## Table of Contents
|
||||
- [Getting Started](#getting-started)
|
||||
@@ -15,11 +15,11 @@ To get started with the project, follow these steps:
|
||||
|
||||
1. **Clone the repository**:
|
||||
```bash
|
||||
git clone https://github.com/Worklenz/worklenz-v2.git
|
||||
git clone https://github.com/Worklenz/worklenz.git
|
||||
```
|
||||
2. **Navigate to the project directory**:
|
||||
```bash
|
||||
cd worklenz-v2
|
||||
cd worklenz/worklenz-frontend
|
||||
```
|
||||
3. **Install dependencies**:
|
||||
```bash
|
||||
@@ -29,7 +29,7 @@ To get started with the project, follow these steps:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
5. Open [http://localhost:3000](http://localhost:3000) in your browser to view the application.
|
||||
5. Open [http://localhost:5000](http://localhost:5000) in your browser to view the application.
|
||||
|
||||
## Available Scripts
|
||||
|
||||
@@ -38,7 +38,7 @@ In the project directory, you can run:
|
||||
### `npm run dev`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:5173](http://localhost:5173) to view it in the browser.
|
||||
Open [http://localhost:5000](http://localhost:5000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.\
|
||||
You will also see any lint errors in the console.
|
||||
@@ -58,7 +58,22 @@ Open [http://localhost:4173](http://localhost:4173) to preview the build.
|
||||
|
||||
## Project Structure
|
||||
|
||||
The project structure is organized as follows:
|
||||
The project is organized around a feature-based structure:
|
||||
|
||||
```
|
||||
src/
|
||||
├── components/ # Reusable UI components
|
||||
├── hooks/ # Custom React hooks
|
||||
├── lib/ # Feature-specific logic
|
||||
├── pages/ # Route components
|
||||
├── services/ # API services
|
||||
├── shared/ # Shared utilities, constants, and types
|
||||
├── store/ # Global state management
|
||||
├── types/ # TypeScript type definitions
|
||||
├── utils/ # Utility functions
|
||||
├── App.tsx # Main application component
|
||||
└── main.tsx # Application entry point
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -72,6 +87,9 @@ Contributions are welcome! If you'd like to contribute, please follow these step
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Vite, check out the [Vite documentation](https://vitejs.dev/guide/).
|
||||
To learn more about the technologies used in this project:
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
- [React Documentation](https://react.dev/)
|
||||
- [TypeScript Documentation](https://www.typescriptlang.org/docs/)
|
||||
- [Ant Design Documentation](https://ant.design/docs/react/introduce)
|
||||
- [Vite Documentation](https://vitejs.dev/guide/)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"prebuild": "node scripts/copy-tinymce.js",
|
||||
"build": "node --max-old-space-size=4096 node_modules/.bin/vite build",
|
||||
"build": "vite build",
|
||||
"dev-build": "vite build",
|
||||
"serve": "vite preview",
|
||||
"format": "prettier --write ."
|
||||
|
||||
@@ -8,7 +8,7 @@ type EmptyListPlaceholderProps = {
|
||||
};
|
||||
|
||||
const EmptyListPlaceholder = ({
|
||||
imageSrc = 'https://app.worklenz.com/assets/images/empty-box.webp',
|
||||
imageSrc = '/assets/images/empty-box.webp',
|
||||
imageHeight = 60,
|
||||
text,
|
||||
}: EmptyListPlaceholderProps) => {
|
||||
|
||||
@@ -24,6 +24,16 @@ import logger from '@/utils/errorLogger';
|
||||
import alertService from '@/services/alerts/alertService';
|
||||
import { WORKLENZ_REDIRECT_PROJ_KEY } from '@/shared/constants';
|
||||
|
||||
// Define the global grecaptcha type
|
||||
declare global {
|
||||
interface Window {
|
||||
grecaptcha?: {
|
||||
ready: (callback: () => void) => void;
|
||||
execute: (siteKey: string, options: { action: string }) => Promise<string>;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const SignupPage = () => {
|
||||
const [form] = Form.useForm();
|
||||
const navigate = useNavigate();
|
||||
@@ -58,6 +68,7 @@ const SignupPage = () => {
|
||||
};
|
||||
|
||||
const enableGoogleLogin = import.meta.env.VITE_ENABLE_GOOGLE_LOGIN === 'true' || false;
|
||||
const enableRecaptcha = import.meta.env.VITE_ENABLE_RECAPTCHA === 'true' && import.meta.env.VITE_RECAPTCHA_SITE_KEY && import.meta.env.VITE_RECAPTCHA_SITE_KEY !== 'recaptcha-site-key';
|
||||
|
||||
useEffect(() => {
|
||||
trackMixpanelEvent(evt_signup_page_visit);
|
||||
@@ -79,26 +90,35 @@ const SignupPage = () => {
|
||||
}, [trackMixpanelEvent]);
|
||||
|
||||
useEffect(() => {
|
||||
const script = document.createElement('script');
|
||||
script.src = `https://www.google.com/recaptcha/api.js?render=${import.meta.env.VITE_RECAPTCHA_SITE_KEY}`;
|
||||
script.async = true;
|
||||
script.defer = true;
|
||||
document.body.appendChild(script);
|
||||
|
||||
return () => {
|
||||
if (script && script.parentNode) {
|
||||
script.parentNode.removeChild(script);
|
||||
// Only load recaptcha script if recaptcha is enabled and site key is valid
|
||||
if (enableRecaptcha && import.meta.env.VITE_RECAPTCHA_SITE_KEY) {
|
||||
// Check if site key is not the placeholder value
|
||||
if (import.meta.env.VITE_RECAPTCHA_SITE_KEY === 'recaptcha-site-key') {
|
||||
console.warn('Using placeholder reCAPTCHA site key. Please set a valid key in your environment variables.');
|
||||
return;
|
||||
}
|
||||
|
||||
const recaptchaElements = document.getElementsByClassName('grecaptcha-badge');
|
||||
while (recaptchaElements.length > 0) {
|
||||
const element = recaptchaElements[0];
|
||||
if (element.parentNode) {
|
||||
element.parentNode.removeChild(element);
|
||||
const script = document.createElement('script');
|
||||
script.src = `https://www.google.com/recaptcha/api.js?render=${import.meta.env.VITE_RECAPTCHA_SITE_KEY}`;
|
||||
script.async = true;
|
||||
script.defer = true;
|
||||
document.body.appendChild(script);
|
||||
|
||||
return () => {
|
||||
if (script && script.parentNode) {
|
||||
script.parentNode.removeChild(script);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const recaptchaElements = document.getElementsByClassName('grecaptcha-badge');
|
||||
while (recaptchaElements.length > 0) {
|
||||
const element = recaptchaElements[0];
|
||||
if (element.parentNode) {
|
||||
element.parentNode.removeChild(element);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}, [enableRecaptcha]);
|
||||
|
||||
const getInvitationQueryParams = () => {
|
||||
const params = [`team=${urlParams.teamId}`, `teamMember=${urlParams.teamMemberId}`];
|
||||
@@ -109,33 +129,72 @@ const SignupPage = () => {
|
||||
};
|
||||
|
||||
const getRecaptchaToken = async () => {
|
||||
return new Promise<string>(resolve => {
|
||||
window.grecaptcha?.ready(() => {
|
||||
window.grecaptcha
|
||||
?.execute(import.meta.env.VITE_RECAPTCHA_SITE_KEY, { action: 'signup' })
|
||||
.then((token: string) => {
|
||||
resolve(token);
|
||||
});
|
||||
if (!enableRecaptcha) return '';
|
||||
|
||||
// Check if site key is valid
|
||||
if (!import.meta.env.VITE_RECAPTCHA_SITE_KEY || import.meta.env.VITE_RECAPTCHA_SITE_KEY === 'recaptcha-site-key') {
|
||||
console.warn('Invalid reCAPTCHA site key. Skipping reCAPTCHA verification.');
|
||||
return 'skip-verification';
|
||||
}
|
||||
|
||||
try {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
if (!window.grecaptcha) {
|
||||
reject('reCAPTCHA not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
window.grecaptcha.ready(() => {
|
||||
window.grecaptcha!
|
||||
.execute(import.meta.env.VITE_RECAPTCHA_SITE_KEY, { action: 'signup' })
|
||||
.then((token: string) => {
|
||||
resolve(token);
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error('reCAPTCHA execution error:', error);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error getting reCAPTCHA token:', error);
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const onFinish = async (values: IUserSignUpRequest) => {
|
||||
try {
|
||||
setValidating(true);
|
||||
const token = await getRecaptchaToken();
|
||||
|
||||
if (enableRecaptcha) {
|
||||
try {
|
||||
const token = await getRecaptchaToken();
|
||||
|
||||
if (!token) {
|
||||
logger.error('Failed to get reCAPTCHA token');
|
||||
alertService.error(t('reCAPTCHAVerificationError'), t('reCAPTCHAVerificationErrorMessage'));
|
||||
return;
|
||||
}
|
||||
if (!token) {
|
||||
logger.error('Failed to get reCAPTCHA token');
|
||||
alertService.error(t('reCAPTCHAVerificationError'), t('reCAPTCHAVerificationErrorMessage'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip verification if we're using the special token due to invalid site key
|
||||
if (token !== 'skip-verification') {
|
||||
const verifyToken = await authApiService.verifyRecaptchaToken(token);
|
||||
|
||||
const veriftToken = await authApiService.verifyRecaptchaToken(token);
|
||||
|
||||
if (!veriftToken.done) {
|
||||
logger.error('Failed to verify reCAPTCHA token');
|
||||
return;
|
||||
if (!verifyToken.done) {
|
||||
logger.error('Failed to verify reCAPTCHA token');
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('reCAPTCHA error:', error);
|
||||
// Continue with sign up even if reCAPTCHA fails in development
|
||||
if (import.meta.env.DEV) {
|
||||
console.warn('Continuing signup despite reCAPTCHA error in development mode');
|
||||
} else {
|
||||
alertService.error(t('reCAPTCHAVerificationError'), t('reCAPTCHAVerificationErrorMessage'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const body = {
|
||||
|
||||
Reference in New Issue
Block a user