diff --git a/planventure-api/instance/planventure.db b/planventure-api/instance/planventure.db index 7b7cab7..b0955cd 100644 Binary files a/planventure-api/instance/planventure.db and b/planventure-api/instance/planventure.db differ diff --git a/planventure-api/requirements.txt b/planventure-api/requirements.txt index 6eb9584..20cf188 100644 --- a/planventure-api/requirements.txt +++ b/planventure-api/requirements.txt @@ -12,6 +12,4 @@ werkzeug==2.3.7 bcrypt==4.0.1 # Database migrations -Flask-Migrate==4.0.5 - -pytest==8.3.2 \ No newline at end of file +Flask-Migrate==4.0.5 \ No newline at end of file diff --git a/planventure-api/utils/test_validators.py b/planventure-api/utils/test_validators.py deleted file mode 100644 index e3b4de6..0000000 --- a/planventure-api/utils/test_validators.py +++ /dev/null @@ -1,69 +0,0 @@ -import pytest -from .validators import validate_email, validate_username - -def test_valid_emails(): - """Test validation of properly formatted email addresses""" - valid_emails = [ - "test@example.com", - "user.name@domain.com", - "user+tag@example.co.uk", - "123@domain.com", - "user@sub.domain.com" - ] - for email in valid_emails: - assert validate_email(email) is True - -def test_invalid_emails(): - """Test validation of improperly formatted email addresses""" - invalid_emails = [ - "test@.com", - "@domain.com", - "test@domain", - "test@domain.", - "test.domain.com", - "test@domain@.com", - "test space@domain.com" - ] - for email in invalid_emails: - assert validate_email(email) is False - -def test_edge_cases(): - """Test validation with edge cases""" - assert validate_email("") is False - with pytest.raises(TypeError): - validate_email(None) - -def test_valid_usernames(): - """Test validation of properly formatted usernames""" - valid_usernames = [ - "user123", - "_user", - "john_doe", - "a123", - "Developer42", - "code_master", - "Alice_Bob_123" - ] - for username in valid_usernames: - assert validate_username(username) is True - -def test_invalid_usernames(): - """Test validation of improperly formatted usernames""" - invalid_usernames = [ - "ab", # too short - "a" * 17, # too long - "123user", # starts with number - "__user", # multiple underscores at start - "user name", # contains space - "user@name", # special character - "-user", # starts with hyphen - "user-", # ends with hyphen - ] - for username in invalid_usernames: - assert validate_username(username) is False - -def test_username_edge_cases(): - """Test username validation with edge cases""" - assert validate_username("") is False - with pytest.raises(TypeError): - validate_username(None) \ No newline at end of file diff --git a/planventure-api/utils/validators.py b/planventure-api/utils/validators.py index 75c0b79..887f770 100644 --- a/planventure-api/utils/validators.py +++ b/planventure-api/utils/validators.py @@ -4,42 +4,3 @@ def validate_email(email: str) -> bool: """Validate email format using regex pattern""" pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' return bool(re.match(pattern, email)) - -def validate_username(username: str) -> bool: - """ - Validates a username according to the following rules: - - Between 3 and 16 characters - - Starts with a letter or single underscore - - Can contain letters, numbers, and underscores after first character - - No spaces or special characters - - Args: - username: The username to validate - - Returns: - bool: True if username is valid, False otherwise - - Raises: - TypeError: If username is None - """ - if username is None: - raise TypeError("Username cannot be None") - - # Check for empty string - if not username: - return False - - # Check length (3-16 characters) - if len(username) < 3 or len(username) > 16: - return False - - # Pattern explanation: - # ^ - Start of string - # [a-zA-Z_] - First character must be letter or underscore - # (?!_) - Negative lookahead to prevent multiple underscores at start - # [a-zA-Z0-9_] - Remaining characters can be letters, numbers, or underscores - # {2,15} - Length of remaining characters (2-15, plus first character = 3-16 total) - # $ - End of string - pattern = r'^[a-zA-Z_](?!_)[a-zA-Z0-9_]{2,15}$' - - return bool(re.match(pattern, username)) diff --git a/planventure-client/package-lock.json b/planventure-client/package-lock.json index 9b39ccf..0cc2fd0 100644 --- a/planventure-client/package-lock.json +++ b/planventure-client/package-lock.json @@ -10,10 +10,7 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", - "@mui/icons-material": "^6.2.0", "@mui/material": "^6.2.0", - "@mui/x-date-pickers": "^7.27.1", - "dayjs": "^1.11.13", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^7.0.2" @@ -1082,49 +1079,24 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.6.tgz", - "integrity": "sha512-rho5Q4IscbrVmK9rCrLTJmjLjfH6m/NcqKr/mchvck0EIXlyYUB9+Z0oVmkt/+Mben43LMRYBH8q/Uzxj/c4Vw==", + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.5.tgz", + "integrity": "sha512-zoXvHU1YuoodgMlPS+epP084Pqv9V+Vg+5IGv9n/7IIFVQ2nkTngYHYxElCq8pdTTbDcgji+nNh0lxri2abWgA==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" } }, - "node_modules/@mui/icons-material": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.6.tgz", - "integrity": "sha512-rGJBvIQQbQAlyKYljHQ8wAQS/K2/uYwvemcpygnAmCizmCI4zSF9HQPuiG8Ql4YLZ6V/uKjA3WHIYmF/8sV+pQ==", - "dependencies": { - "@babel/runtime": "^7.26.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@mui/material": "^6.4.6", - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@mui/material": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.6.tgz", - "integrity": "sha512-6UyAju+DBOdMogfYmLiT3Nu7RgliorimNBny1pN/acOjc+THNFVE7hlxLyn3RDONoZJNDi/8vO4AQQr6dLAXqA==", + "version": "6.4.5", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.5.tgz", + "integrity": "sha512-5eyEgSXocIeV1JkXs8mYyJXU0aFyXZIWI5kq2g/mCnIgJe594lkOBNAKnCIaGVfQTu2T6TTEHF8/hHIqpiIRGA==", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.4.6", - "@mui/system": "^6.4.6", + "@mui/core-downloads-tracker": "^6.4.5", + "@mui/system": "^6.4.3", "@mui/types": "^7.2.21", - "@mui/utils": "^6.4.6", + "@mui/utils": "^6.4.3", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", @@ -1143,7 +1115,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.4.6", + "@mui/material-pigment-css": "^6.4.3", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -1169,12 +1141,12 @@ "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==" }, "node_modules/@mui/private-theming": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.6.tgz", - "integrity": "sha512-T5FxdPzCELuOrhpA2g4Pi6241HAxRwZudzAuL9vBvniuB5YU82HCmrARw32AuCiyTfWzbrYGGpZ4zyeqqp9RvQ==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.3.tgz", + "integrity": "sha512-7x9HaNwDCeoERc4BoEWLieuzKzXu5ZrhRnEM6AUcRXUScQLvF1NFkTlP59+IJfTbEMgcGg1wWHApyoqcksrBpQ==", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.4.6", + "@mui/utils": "^6.4.3", "prop-types": "^15.8.1" }, "engines": { @@ -1195,9 +1167,9 @@ } }, "node_modules/@mui/styled-engine": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.6.tgz", - "integrity": "sha512-vSWYc9ZLX46be5gP+FCzWVn5rvDr4cXC5JBZwSIkYk9xbC7GeV+0kCvB8Q6XLFQJy+a62bbqtmdwS4Ghi9NBlQ==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.3.tgz", + "integrity": "sha512-OC402VfK+ra2+f12Gef8maY7Y9n7B6CZcoQ9u7mIkh/7PKwW/xH81xwX+yW+Ak1zBT3HYcVjh2X82k5cKMFGoQ==", "dependencies": { "@babel/runtime": "^7.26.0", "@emotion/cache": "^11.13.5", @@ -1228,15 +1200,15 @@ } }, "node_modules/@mui/system": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.6.tgz", - "integrity": "sha512-FQjWwPec7pMTtB/jw5f9eyLynKFZ6/Ej9vhm5kGdtmts1z5b7Vyn3Rz6kasfYm1j2TfrfGnSXRvvtwVWxjpz6g==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.3.tgz", + "integrity": "sha512-Q0iDwnH3+xoxQ0pqVbt8hFdzhq1g2XzzR4Y5pVcICTNtoCLJmpJS3vI4y/OIM1FHFmpfmiEC2IRIq7YcZ8nsmg==", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/private-theming": "^6.4.6", - "@mui/styled-engine": "^6.4.6", + "@mui/private-theming": "^6.4.3", + "@mui/styled-engine": "^6.4.3", "@mui/types": "^7.2.21", - "@mui/utils": "^6.4.6", + "@mui/utils": "^6.4.3", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -1280,9 +1252,9 @@ } }, "node_modules/@mui/utils": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.6.tgz", - "integrity": "sha512-43nZeE1pJF2anGafNydUcYFPtHwAqiBiauRtaMvurdrZI3YrUjHkAu43RBsxef7OFtJMXGiHFvq43kb7lig0sA==", + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.3.tgz", + "integrity": "sha512-jxHRHh3BqVXE9ABxDm+Tc3wlBooYz/4XPa0+4AI+iF38rV1/+btJmSUgG4shDtSWVs/I97aDn5jBCt6SF2Uq2A==", "dependencies": { "@babel/runtime": "^7.26.0", "@mui/types": "^7.2.21", @@ -1313,90 +1285,6 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==" }, - "node_modules/@mui/x-date-pickers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.27.1.tgz", - "integrity": "sha512-2YPhTM9TM39dmIkEQdSB6P6NASePB9LuhXXKQqq0PX4FXGymYEPz/acQXkk617zwfxJJaDhJZ6g8SAv5pklTJQ==", - "dependencies": { - "@babel/runtime": "^7.25.7", - "@mui/utils": "^5.16.6 || ^6.0.0", - "@mui/x-internals": "7.26.0", - "@types/react-transition-group": "^4.4.11", - "clsx": "^2.1.1", - "prop-types": "^15.8.1", - "react-transition-group": "^4.4.5" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.9.0", - "@emotion/styled": "^11.8.1", - "@mui/material": "^5.15.14 || ^6.0.0", - "@mui/system": "^5.15.14 || ^6.0.0", - "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", - "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0", - "dayjs": "^1.10.7", - "luxon": "^3.0.2", - "moment": "^2.29.4", - "moment-hijri": "^2.1.2 || ^3.0.0", - "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "date-fns": { - "optional": true - }, - "date-fns-jalali": { - "optional": true - }, - "dayjs": { - "optional": true - }, - "luxon": { - "optional": true - }, - "moment": { - "optional": true - }, - "moment-hijri": { - "optional": true - }, - "moment-jalaali": { - "optional": true - } - } - }, - "node_modules/@mui/x-internals": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.26.0.tgz", - "integrity": "sha512-VxTCYQcZ02d3190pdvys2TDg9pgbvewAVakEopiOgReKAUhLdRlgGJHcOA/eAuGLyK1YIo26A6Ow6ZKlSRLwMg==", - "dependencies": { - "@babel/runtime": "^7.25.7", - "@mui/utils": "^5.16.6 || ^6.0.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -2288,11 +2176,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" - }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", diff --git a/planventure-client/package.json b/planventure-client/package.json index ee1a425..b1aa52d 100644 --- a/planventure-client/package.json +++ b/planventure-client/package.json @@ -12,7 +12,6 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", - "@mui/icons-material": "^6.2.0", "@mui/material": "^6.2.0", "@mui/icons-material": "^6.2.0", "react": "^18.3.1", @@ -31,4 +30,4 @@ "globals": "^15.12.0", "vite": "^6.0.1" } -} +} \ No newline at end of file diff --git a/planventure-client/src/components/auth/LoginForm.jsx b/planventure-client/src/components/auth/LoginForm.jsx deleted file mode 100644 index 27c65ec..0000000 --- a/planventure-client/src/components/auth/LoginForm.jsx +++ /dev/null @@ -1,202 +0,0 @@ -import { useState, useEffect } from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; -import { - Box, - TextField, - Button, - Typography, - Alert, - InputAdornment, - IconButton -} from '@mui/material'; -import { Visibility, VisibilityOff } from '@mui/icons-material'; -import { useAuth } from '../../context/AuthContext'; -import { api } from '../../services/api'; - -const LoginForm = () => { - const navigate = useNavigate(); - const location = useLocation(); - const { login, setIsAuthenticated } = useAuth(); - const [showPassword, setShowPassword] = useState(false); - const [error, setError] = useState(''); - const [isLoading, setIsLoading] = useState(false); - const [formData, setFormData] = useState({ - email: '', - password: '' - }); - const [formErrors, setFormErrors] = useState({ - email: '', - password: '' - }); - - // Add state for success message - const [successMessage, setSuccessMessage] = useState( - location.state?.message || '' - ); - - // Pre-fill email if coming from signup - useEffect(() => { - if (location.state?.email) { - setFormData(prev => ({ - ...prev, - email: location.state.email - })); - } - }, [location.state]); - - const validateEmail = (email) => { - const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!email) return 'Email is required'; - if (!re.test(email)) return 'Invalid email format'; - return ''; - }; - - const validatePassword = (password) => { - if (!password) return 'Password is required'; - if (password.length < 6) return 'Password must be at least 6 characters'; - return ''; - }; - - const handleChange = (e) => { - const { name, value } = e.target; - setFormData(prev => ({ - ...prev, - [name]: value - })); - - // Clear errors when user types - setFormErrors(prev => ({ - ...prev, - [name]: '' - })); - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - - // Validate form - const emailError = validateEmail(formData.email); - const passwordError = validatePassword(formData.password); - - if (emailError || passwordError) { - setFormErrors({ - email: emailError, - password: passwordError - }); - return; - } - - setIsLoading(true); - setError(''); - - try { - const response = await api.auth.login(formData); - console.log('Login response:', response); - - if (response.token) { - login(response); // Pass the entire response - console.log('Login successful, redirecting to dashboard...'); - navigate('/dashboard', { replace: true }); - } else { - setError('Invalid login response'); - } - } catch (err) { - console.error('Login error:', err); - setError(err.message || 'An error occurred during login'); - } finally { - setIsLoading(false); - } - }; - - const togglePasswordVisibility = () => { - setShowPassword(!showPassword); - }; - - return ( - - - Login to Planventure - - - {successMessage && ( - - {successMessage} - - )} - - {error && ( - - {error} - - )} - - - - - - {showPassword ? : } - - - ), - }} - /> - - - - - Don't have an account?{' '} - - - - ); -}; - -export default LoginForm; \ No newline at end of file diff --git a/planventure-client/src/components/auth/SignupForm.jsx b/planventure-client/src/components/auth/SignupForm.jsx deleted file mode 100644 index b8e0ed9..0000000 --- a/planventure-client/src/components/auth/SignupForm.jsx +++ /dev/null @@ -1,213 +0,0 @@ -import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { - Box, - TextField, - Button, - Typography, - Alert, - InputAdornment, - IconButton -} from '@mui/material'; -import { Visibility, VisibilityOff } from '@mui/icons-material'; -import { useAuth } from '../../context/AuthContext'; -import { Link as RouterLink } from 'react-router-dom'; -import { api } from '../../services/api'; - -const SignupForm = () => { - const navigate = useNavigate(); - const { setIsAuthenticated } = useAuth(); - const [showPassword, setShowPassword] = useState(false); - const [error, setError] = useState(''); - const [isLoading, setIsLoading] = useState(false); - const [formData, setFormData] = useState({ - email: '', - password: '', - confirmPassword: '' - }); - const [formErrors, setFormErrors] = useState({ - email: '', - password: '', - confirmPassword: '' - }); - - const validateEmail = (email) => { - const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!email) return 'Email is required'; - if (!re.test(email)) return 'Invalid email format'; - return ''; - }; - - const validatePassword = (password) => { - if (!password) return 'Password is required'; - if (password.length < 6) return 'Password must be at least 6 characters'; - return ''; - }; - - const validateConfirmPassword = (confirmPassword, password) => { - if (!confirmPassword) return 'Please confirm your password'; - if (confirmPassword !== password) return 'Passwords do not match'; - return ''; - }; - - const handleChange = (e) => { - const { name, value } = e.target; - setFormData(prev => ({ - ...prev, - [name]: value - })); - - setFormErrors(prev => ({ - ...prev, - [name]: '' - })); - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - - // Validate form - const emailError = validateEmail(formData.email); - const passwordError = validatePassword(formData.password); - const confirmPasswordError = validateConfirmPassword( - formData.confirmPassword, - formData.password - ); - - if (emailError || passwordError || confirmPasswordError) { - setFormErrors({ - email: emailError, - password: passwordError, - confirmPassword: confirmPasswordError - }); - return; - } - - setIsLoading(true); - setError(''); - - try { - const userData = { - email: formData.email, - password: formData.password - }; - - const response = await api.auth.register(userData); - console.log('Signup response:', response); // Debug log - - // Don't check for accessToken, just redirect after successful registration - navigate('/login', { - replace: true, - state: { - message: 'Registration successful! Please log in.', - email: formData.email - } - }); - - } catch (err) { - console.error('Signup error:', err); // Debug log - setError(err.message || 'Registration failed. Please try again.'); - } finally { - setIsLoading(false); - } - }; - - const togglePasswordVisibility = () => { - setShowPassword(!showPassword); - }; - - return ( - - - Create Account - - - {error && ( - - {error} - - )} - - - - - - {showPassword ? : } - - - ), - }} - /> - - - - - - - Already have an account?{' '} - - Login - - - - ); -}; - -export default SignupForm; \ No newline at end of file diff --git a/planventure-client/src/components/itinerary/EmptyItinerary.jsx b/planventure-client/src/components/itinerary/EmptyItinerary.jsx deleted file mode 100644 index a6e51a9..0000000 --- a/planventure-client/src/components/itinerary/EmptyItinerary.jsx +++ /dev/null @@ -1,46 +0,0 @@ -import { - Box, - Typography, - Button, - Paper, - Stack -} from '@mui/material'; -import { PostAdd as PostAddIcon, AutoAwesome as AutoAwesomeIcon } from '@mui/icons-material'; - -const EmptyItinerary = ({ onCreateEmpty, onUseTemplate }) => { - return ( - - - Start Planning Your Trip - - - Create an itinerary to organize your daily activities, meals, and travel arrangements. - - - - - - - - ); -}; - -export default EmptyItinerary; diff --git a/planventure-client/src/components/itinerary/ItineraryDay.jsx b/planventure-client/src/components/itinerary/ItineraryDay.jsx deleted file mode 100644 index c0a7617..0000000 --- a/planventure-client/src/components/itinerary/ItineraryDay.jsx +++ /dev/null @@ -1,89 +0,0 @@ -import { useState } from 'react'; -import { - Box, - Paper, - Typography, - IconButton, - Button, - Collapse, - Divider -} from '@mui/material'; -import { - ExpandMore as ExpandMoreIcon, - ExpandLess as ExpandLessIcon, - Add as AddIcon -} from '@mui/icons-material'; -import TimeSlot from './TimeSlot'; -import dayjs from 'dayjs'; - -const ItineraryDay = ({ date, slots = [], onAddSlot, onUpdateSlot, onDeleteSlot }) => { - const [isExpanded, setIsExpanded] = useState(true); - - const handleAddSlot = () => { - const newSlot = { - id: Date.now(), - time: '12:00', - activity: '', - location: '', - type: 'activity' - }; - onAddSlot(date, newSlot); - }; - - const sortedSlots = [...slots].sort((a, b) => a.time.localeCompare(b.time)); - - return ( - - setIsExpanded(!isExpanded)} - > - - {dayjs(date).format('dddd, MMMM D')} - - - {isExpanded ? : } - - - - - - {sortedSlots.length === 0 ? ( - - No activities planned for this day yet. - - ) : ( - sortedSlots.map((slot) => ( - onUpdateSlot(date, updatedSlot)} - onDelete={() => onDeleteSlot(date, slot.id)} - /> - )) - )} - - - - - - ); -}; - -export default ItineraryDay; diff --git a/planventure-client/src/components/itinerary/TimeSlot.jsx b/planventure-client/src/components/itinerary/TimeSlot.jsx deleted file mode 100644 index 4cd98c8..0000000 --- a/planventure-client/src/components/itinerary/TimeSlot.jsx +++ /dev/null @@ -1,121 +0,0 @@ -import { useState } from 'react'; -import { - Box, - TextField, - IconButton, - Card, - CardContent, - Typography, - Stack, - MenuItem -} from '@mui/material'; -import { - Edit as EditIcon, - Delete as DeleteIcon, - Save as SaveIcon, - Cancel as CancelIcon, - AccessTime as TimeIcon, - Place as PlaceIcon -} from '@mui/icons-material'; - -const TimeSlot = ({ slot, onUpdate, onDelete }) => { - const [isEditing, setIsEditing] = useState(false); - const [editedSlot, setEditedSlot] = useState(slot); - - const handleSave = () => { - onUpdate(editedSlot); - setIsEditing(false); - }; - - const handleCancel = () => { - setEditedSlot(slot); - setIsEditing(false); - }; - - if (isEditing) { - return ( - - - - setEditedSlot({ ...editedSlot, time: e.target.value })} - InputLabelProps={{ shrink: true }} - /> - setEditedSlot({ ...editedSlot, activity: e.target.value })} - /> - setEditedSlot({ ...editedSlot, location: e.target.value })} - /> - setEditedSlot({ ...editedSlot, type: e.target.value })} - > - Activity - Transportation - Accommodation - Food - - - - - - - - - - - - - ); - } - - return ( - - - - - - {slot.activity} - - - - - {slot.time} - - {slot.location && ( - - - {slot.location} - - )} - - - - setIsEditing(true)} size="small"> - - - onDelete(slot.id)} color="error" size="small"> - - - - - - - ); -}; - -export default TimeSlot; diff --git a/planventure-client/src/components/navigation/Navbar.jsx b/planventure-client/src/components/navigation/Navbar.jsx index bb493df..d7bcc1c 100644 --- a/planventure-client/src/components/navigation/Navbar.jsx +++ b/planventure-client/src/components/navigation/Navbar.jsx @@ -1,64 +1,21 @@ -import { AppBar, Box, Toolbar, Typography, Button, Stack } from '@mui/material'; -import { useNavigate } from 'react-router-dom'; -import { useAuth } from '../../context/AuthContext'; +import { AppBar, Toolbar, Typography, Button } from '@mui/material'; const Navbar = () => { - const navigate = useNavigate(); - const { isAuthenticated, logout } = useAuth(); - - const handleLogout = () => { - logout(); - navigate('/'); - }; - return ( - - - navigate('/')} - > + + + Planventure - - {isAuthenticated ? ( - <> - - - - ) : ( - <> - - - - )} - + ); diff --git a/planventure-client/src/components/overview/AccommodationCard.jsx b/planventure-client/src/components/overview/AccommodationCard.jsx deleted file mode 100644 index d890064..0000000 --- a/planventure-client/src/components/overview/AccommodationCard.jsx +++ /dev/null @@ -1,120 +0,0 @@ -import { - Card, - CardContent, - Typography, - Box, - IconButton, - Button, - TextField, - Stack, - Collapse -} from '@mui/material'; -import { - Edit as EditIcon, - Delete as DeleteIcon, - Save as SaveIcon, - Cancel as CancelIcon, - Hotel as HotelIcon -} from '@mui/icons-material'; -import { useState } from 'react'; - -const AccommodationCard = ({ accommodation, onUpdate, onDelete }) => { - const [isEditing, setIsEditing] = useState(false); - const [editedData, setEditedData] = useState(accommodation); - - const handleSave = () => { - onUpdate(editedData); - setIsEditing(false); - }; - - if (isEditing) { - return ( - - - - setEditedData({ ...editedData, name: e.target.value })} - /> - setEditedData({ ...editedData, address: e.target.value })} - /> - setEditedData({ ...editedData, checkIn: e.target.value })} - InputLabelProps={{ shrink: true }} - /> - setEditedData({ ...editedData, checkOut: e.target.value })} - InputLabelProps={{ shrink: true }} - /> - setEditedData({ ...editedData, bookingRef: e.target.value })} - /> - - - - - - - - ); - } - - return ( - - - - - - {accommodation.name} - - - {accommodation.address} - - - Check-in: {new Date(accommodation.checkIn).toLocaleString()} - - - Check-out: {new Date(accommodation.checkOut).toLocaleString()} - - {accommodation.bookingRef && ( - - Booking Reference: {accommodation.bookingRef} - - )} - - - setIsEditing(true)}> - - - onDelete(accommodation.id)} color="error"> - - - - - - - ); -}; - -export default AccommodationCard; diff --git a/planventure-client/src/components/overview/EmptyOverviewSection.jsx b/planventure-client/src/components/overview/EmptyOverviewSection.jsx deleted file mode 100644 index 92ef903..0000000 --- a/planventure-client/src/components/overview/EmptyOverviewSection.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import { - Box, - Typography, - Button, - Paper -} from '@mui/material'; -import { Add as AddIcon } from '@mui/icons-material'; - -const EmptyOverviewSection = ({ title, description, onAdd }) => { - return ( - - - No {title} Added Yet - - - {description} - - - - ); -}; - -export default EmptyOverviewSection; diff --git a/planventure-client/src/components/overview/TransportationCard.jsx b/planventure-client/src/components/overview/TransportationCard.jsx deleted file mode 100644 index 0a9b412..0000000 --- a/planventure-client/src/components/overview/TransportationCard.jsx +++ /dev/null @@ -1,131 +0,0 @@ -import { - Card, - CardContent, - Typography, - Box, - IconButton, - Button, - TextField, - Stack, - MenuItem -} from '@mui/material'; -import { - Edit as EditIcon, - Delete as DeleteIcon, - Save as SaveIcon, - Cancel as CancelIcon -} from '@mui/icons-material'; -import { useState } from 'react'; - -const TransportationCard = ({ transport, onUpdate, onDelete }) => { - const [isEditing, setIsEditing] = useState(false); - const [editedData, setEditedData] = useState(transport); - - const handleSave = () => { - onUpdate(editedData); - setIsEditing(false); - }; - - if (isEditing) { - return ( - - - - setEditedData({ ...editedData, type: e.target.value })} - > - Flight - Train - Bus - Car Rental - - setEditedData({ ...editedData, from: e.target.value })} - /> - setEditedData({ ...editedData, to: e.target.value })} - /> - setEditedData({ ...editedData, departure: e.target.value })} - InputLabelProps={{ shrink: true }} - /> - setEditedData({ ...editedData, arrival: e.target.value })} - InputLabelProps={{ shrink: true }} - /> - setEditedData({ ...editedData, bookingRef: e.target.value })} - /> - - - - - - - - ); - } - - return ( - - - - - - {transport.type.charAt(0).toUpperCase() + transport.type.slice(1)} - - - {transport.from} → {transport.to} - - - Departure: {new Date(transport.departure).toLocaleString()} - - - Arrival: {new Date(transport.arrival).toLocaleString()} - - {transport.bookingRef && ( - - Booking Reference: {transport.bookingRef} - - )} - - - setIsEditing(true)}> - - - onDelete(transport.id)} color="error"> - - - - - - - ); -}; - -export default TransportationCard; diff --git a/planventure-client/src/components/routing/ProtectedRoute.jsx b/planventure-client/src/components/routing/ProtectedRoute.jsx index b5ee8c5..7158242 100644 --- a/planventure-client/src/components/routing/ProtectedRoute.jsx +++ b/planventure-client/src/components/routing/ProtectedRoute.jsx @@ -1,23 +1,11 @@ import { Navigate, useLocation } from 'react-router-dom'; import { useAuth } from '../../context/AuthContext'; -import { CircularProgress } from '@mui/material'; const ProtectedRoute = ({ children }) => { - const { isAuthenticated, loading } = useAuth(); + const { isAuthenticated, token } = useAuth(); const location = useLocation(); - console.log('ProtectedRoute - Auth State:', { isAuthenticated, loading }); - - if (loading) { - return ( -
- -
- ); - } - - if (!isAuthenticated) { - console.log('Not authenticated, redirecting to login...'); + if (!isAuthenticated || !token) { return ; } diff --git a/planventure-client/src/components/trips/EditTripForm.jsx b/planventure-client/src/components/trips/EditTripForm.jsx deleted file mode 100644 index bdb7cff..0000000 --- a/planventure-client/src/components/trips/EditTripForm.jsx +++ /dev/null @@ -1,173 +0,0 @@ -import { useState, useEffect } from 'react'; -import { - Box, - TextField, - Button, - Typography, - Alert, - Paper -} from '@mui/material'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import dayjs from 'dayjs'; -import { useNavigate } from 'react-router-dom'; -import { tripService } from '../../services/tripService'; - -const EditTripForm = ({ trip }) => { - const navigate = useNavigate(); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); - const [formData, setFormData] = useState({ - title: trip?.title || '', - destination: trip?.destination || '', - startDate: trip?.start_date ? dayjs(trip.start_date) : dayjs(), - endDate: trip?.end_date ? dayjs(trip.end_date) : dayjs().add(1, 'day') - }); - - // Add useEffect to update form data when trip prop changes - useEffect(() => { - if (trip) { - setFormData({ - title: trip.title || '', - destination: trip.destination || '', - startDate: dayjs(trip.start_date), - endDate: dayjs(trip.end_date) - }); - } - }, [trip]); - - const handleSubmit = async (e) => { - e.preventDefault(); - setLoading(true); - setError(''); - - try { - const tripData = { - title: formData.title, - destination: formData.destination, - start_date: formData.startDate.format('YYYY-MM-DD'), - end_date: formData.endDate.format('YYYY-MM-DD'), - }; - - await tripService.updateTrip(trip.id, tripData); - navigate(`/trips/${trip.id}`); - } catch (err) { - setError(err.message || 'Failed to update trip'); - } finally { - setLoading(false); - } - }; - - const handleChange = (e) => { - const { name, value } = e.target; - setFormData(prev => ({ - ...prev, - [name]: value - })); - }; - - const isDateRangeValid = () => { - return formData.endDate.isAfter(formData.startDate) || - formData.endDate.isSame(formData.startDate); - }; - - const isFormValid = () => { - return formData.title?.trim() && - formData.destination?.trim() && - isDateRangeValid() && - !loading; - }; - - return ( - - - - Edit Trip - - - {error && ( - - {error} - - )} - - - - - - - { - setFormData(prev => ({ ...prev, startDate: newValue })); - }} - slotProps={{ - textField: { fullWidth: true } - }} - /> - - { - setFormData(prev => ({ ...prev, endDate: newValue })); - }} - minDate={formData.startDate} - slotProps={{ - textField: { - fullWidth: true, - error: !isDateRangeValid(), - helperText: !isDateRangeValid() ? 'End date must be after start date' : '' - } - }} - /> - - - - - - - - - ); -}; - -export default EditTripForm; diff --git a/planventure-client/src/components/trips/NewTripForm.jsx b/planventure-client/src/components/trips/NewTripForm.jsx deleted file mode 100644 index f7855a9..0000000 --- a/planventure-client/src/components/trips/NewTripForm.jsx +++ /dev/null @@ -1,163 +0,0 @@ -import { useState } from 'react'; -import { - Box, - TextField, - Button, - Typography, - Alert, - Paper -} from '@mui/material'; -import { DatePicker } from '@mui/x-date-pickers/DatePicker'; -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import dayjs from 'dayjs'; -import { useNavigate } from 'react-router-dom'; -import { tripService } from '../../services/tripService'; - -const NewTripForm = () => { - const navigate = useNavigate(); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); - const [formData, setFormData] = useState({ - title: '', - destination: '', - startDate: dayjs(), - endDate: dayjs().add(7, 'day') - }); - - const handleSubmit = async (e) => { - e.preventDefault(); - setLoading(true); - setError(''); - - try { - const tripData = { - title: formData.title, - destination: formData.destination, - start_date: formData.startDate.format('YYYY-MM-DD'), - end_date: formData.endDate.format('YYYY-MM-DD'), - status: 'Upcoming' - }; - - const response = await tripService.createTrip(tripData); - navigate('/dashboard'); - } catch (err) { - setError(err.message || 'Failed to create trip'); - } finally { - setLoading(false); - } - }; - - const handleChange = (e) => { - const { name, value } = e.target; - setFormData(prev => ({ - ...prev, - [name]: value - })); - }; - - const isDateRangeValid = () => { - return formData.endDate.isAfter(formData.startDate) || - formData.endDate.isSame(formData.startDate); - }; - - const isFormValid = () => { - return formData.title && - formData.destination && - isDateRangeValid() && - !loading; - }; - - return ( - - - - Plan a New Trip - - - {error && ( - - {error} - - )} - - - - - - - { - setFormData(prev => ({ ...prev, startDate: newValue })); - }} - minDate={dayjs()} - slotProps={{ - textField: { fullWidth: true } - }} - /> - - { - setFormData(prev => ({ ...prev, endDate: newValue })); - }} - minDate={formData.startDate} - slotProps={{ - textField: { - fullWidth: true, - error: !isDateRangeValid(), - helperText: !isDateRangeValid() ? 'End date must be after start date' : '' - } - }} - /> - - - - - - - - - ); -}; - -export default NewTripForm; diff --git a/planventure-client/src/components/trips/TripCard.jsx b/planventure-client/src/components/trips/TripCard.jsx deleted file mode 100644 index be94f4d..0000000 --- a/planventure-client/src/components/trips/TripCard.jsx +++ /dev/null @@ -1,70 +0,0 @@ -import { - Card, - CardContent, - CardMedia, - Typography, - CardActions, - Button, - Chip, - Box, - Skeleton -} from '@mui/material'; -import { - LocationOn, - DateRange, - ArrowForward -} from '@mui/icons-material'; -import { useNavigate } from 'react-router-dom'; - -const TripCard = ({ trip, loading }) => { - const navigate = useNavigate(); - - if (loading) { - return ( - - - - - - - - - - - - ); - } - - return ( - - - - {trip.title} - - - - - {trip.destination} - - - - - - {new Date(trip.start_date).toLocaleDateString()} - {new Date(trip.end_date).toLocaleDateString()} - - - - - - - - ); -}; - -export default TripCard; diff --git a/planventure-client/src/components/trips/TripList.jsx b/planventure-client/src/components/trips/TripList.jsx deleted file mode 100644 index 15be031..0000000 --- a/planventure-client/src/components/trips/TripList.jsx +++ /dev/null @@ -1,98 +0,0 @@ -import { useState, useEffect } from 'react'; -import { - Grid, - Typography, - Box, - Alert, - Button -} from '@mui/material'; -import { Add as AddIcon } from '@mui/icons-material'; -import TripCard from './TripCard'; -import { useNavigate } from 'react-router-dom'; -import { tripService } from '../../services/tripService'; - -const TripList = ({ WelcomeMessage, ErrorState }) => { - const [trips, setTrips] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const navigate = useNavigate(); - - useEffect(() => { - const fetchTrips = async () => { - try { - setLoading(true); - const data = await tripService.getAllTrips(); - console.log('TripList received data:', data); // Debug log - - if (!data || !data.trips) { - console.error('Invalid data format:', data); - setError('Unexpected data format received'); - return; - } - - setTrips(data.trips); - } catch (err) { - console.error('TripList error:', err); - setError(err.message); - } finally { - setLoading(false); - } - }; - - fetchTrips(); - }, []); - - // Loading state with skeleton cards - if (loading) { - return ( - - {[1, 2, 3].map((skeleton) => ( - - - - ))} - - ); - } - - // Error state - if (error) { - return ; - } - - // Empty state - if (trips.length === 0) { - return ; - } - - // Loaded state with trips - return ( - - {trips.map((trip) => ( - - - - ))} - - - - - ); -}; - -export default TripList; diff --git a/planventure-client/src/context/AuthContext.jsx b/planventure-client/src/context/AuthContext.jsx index fdfabc8..be58f12 100644 --- a/planventure-client/src/context/AuthContext.jsx +++ b/planventure-client/src/context/AuthContext.jsx @@ -1,48 +1,15 @@ -import { createContext, useContext, useState, useEffect } from 'react'; +import { createContext, useContext, useState } from 'react'; const AuthContext = createContext(null); export const AuthProvider = ({ children }) => { const [isAuthenticated, setIsAuthenticated] = useState(false); - const [token, setToken] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - // Check for token in localStorage on initial load - const storedToken = localStorage.getItem('token'); - if (storedToken) { - setToken(storedToken); - setIsAuthenticated(true); - } - setLoading(false); - }, []); - - const login = (authData) => { - // Handle the new response structure - const token = authData.token; - localStorage.setItem('token', token); - setToken(token); - setIsAuthenticated(true); - }; - - const logout = () => { - localStorage.removeItem('token'); - setToken(null); - setIsAuthenticated(false); - }; const value = { isAuthenticated, - token, - loading, - login, - logout + setIsAuthenticated }; - if (loading) { - return null; // or a loading spinner - } - return {children}; }; diff --git a/planventure-client/src/data/itineraryTemplates.js b/planventure-client/src/data/itineraryTemplates.js deleted file mode 100644 index e463561..0000000 --- a/planventure-client/src/data/itineraryTemplates.js +++ /dev/null @@ -1,18 +0,0 @@ -export const defaultTemplate = { - activity: [ - { time: '09:00', activity: 'Breakfast', type: 'food' }, - { time: '10:00', activity: 'Morning Activity', type: 'activity' }, - { time: '12:30', activity: 'Lunch', type: 'food' }, - { time: '14:00', activity: 'Afternoon Activity', type: 'activity' }, - { time: '16:00', activity: 'Free Time / Rest', type: 'activity' }, - { time: '19:00', activity: 'Dinner', type: 'food' } - ] -}; - -export const generateTemplateForDate = (date) => { - return defaultTemplate.activity.map(item => ({ - ...item, - id: `${date}-${Date.now()}-${Math.random()}`, - location: '' - })); -}; diff --git a/planventure-client/src/layouts/AuthLayout.jsx b/planventure-client/src/layouts/AuthLayout.jsx deleted file mode 100644 index eda5865..0000000 --- a/planventure-client/src/layouts/AuthLayout.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Box, Container } from '@mui/material'; -import Navbar from '../components/navigation/Navbar'; -import Footer from '../components/navigation/Footer'; - -const AuthLayout = ({ children }) => { - return ( - - - - - {children} - - -