Skip to content

Commit 9c77e3c

Browse files
committed
Add NewTripForm and NewTripPage components; implement trip creation functionality and routing
1 parent e52b1d4 commit 9c77e3c

6 files changed

Lines changed: 275 additions & 2 deletions

File tree

8 KB
Binary file not shown.

planventure-client/package-lock.json

Lines changed: 91 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

planventure-client/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
"dependencies": {
1313
"@emotion/react": "^11.14.0",
1414
"@emotion/styled": "^11.14.0",
15-
"@mui/material": "^6.2.0",
1615
"@mui/icons-material": "^6.2.0",
16+
"@mui/material": "^6.2.0",
17+
"@mui/x-date-pickers": "^7.27.1",
18+
"dayjs": "^1.11.13",
1719
"react": "^18.3.1",
1820
"react-dom": "^18.3.1",
1921
"react-router-dom": "^7.0.2"
@@ -30,4 +32,4 @@
3032
"globals": "^15.12.0",
3133
"vite": "^6.0.1"
3234
}
33-
}
35+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import { useState } from 'react';
2+
import {
3+
Box,
4+
TextField,
5+
Button,
6+
Typography,
7+
Alert,
8+
Paper
9+
} from '@mui/material';
10+
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
11+
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
12+
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
13+
import dayjs from 'dayjs';
14+
import { useNavigate } from 'react-router-dom';
15+
import { tripService } from '../../services/tripService';
16+
17+
const NewTripForm = () => {
18+
const navigate = useNavigate();
19+
const [loading, setLoading] = useState(false);
20+
const [error, setError] = useState('');
21+
const [formData, setFormData] = useState({
22+
title: '',
23+
destination: '',
24+
startDate: dayjs(),
25+
endDate: dayjs().add(7, 'day')
26+
});
27+
28+
const handleSubmit = async (e) => {
29+
e.preventDefault();
30+
setLoading(true);
31+
setError('');
32+
33+
try {
34+
const tripData = {
35+
title: formData.title,
36+
destination: formData.destination,
37+
start_date: formData.startDate.format('YYYY-MM-DD'),
38+
end_date: formData.endDate.format('YYYY-MM-DD'),
39+
status: 'Upcoming'
40+
};
41+
42+
const response = await tripService.createTrip(tripData);
43+
navigate('/dashboard');
44+
} catch (err) {
45+
setError(err.message || 'Failed to create trip');
46+
} finally {
47+
setLoading(false);
48+
}
49+
};
50+
51+
const handleChange = (e) => {
52+
const { name, value } = e.target;
53+
setFormData(prev => ({
54+
...prev,
55+
[name]: value
56+
}));
57+
};
58+
59+
const isDateRangeValid = () => {
60+
return formData.endDate.isAfter(formData.startDate) ||
61+
formData.endDate.isSame(formData.startDate);
62+
};
63+
64+
const isFormValid = () => {
65+
return formData.title &&
66+
formData.destination &&
67+
isDateRangeValid() &&
68+
!loading;
69+
};
70+
71+
return (
72+
<Paper elevation={2} sx={{ p: 4, maxWidth: 600, mx: 'auto', mt: 4 }}>
73+
<Box
74+
component="form"
75+
onSubmit={handleSubmit}
76+
sx={{
77+
display: 'flex',
78+
flexDirection: 'column',
79+
gap: 3
80+
}}
81+
>
82+
<Typography variant="h5" component="h1" gutterBottom>
83+
Plan a New Trip
84+
</Typography>
85+
86+
{error && (
87+
<Alert severity="error" sx={{ mb: 2 }}>
88+
{error}
89+
</Alert>
90+
)}
91+
92+
<TextField
93+
fullWidth
94+
label="Trip Title"
95+
name="title"
96+
value={formData.title}
97+
onChange={handleChange}
98+
required
99+
/>
100+
101+
<TextField
102+
fullWidth
103+
label="Destination"
104+
name="destination"
105+
value={formData.destination}
106+
onChange={handleChange}
107+
required
108+
/>
109+
110+
<LocalizationProvider dateAdapter={AdapterDayjs}>
111+
<DatePicker
112+
label="Start Date"
113+
value={formData.startDate}
114+
onChange={(newValue) => {
115+
setFormData(prev => ({ ...prev, startDate: newValue }));
116+
}}
117+
minDate={dayjs()}
118+
slotProps={{
119+
textField: { fullWidth: true }
120+
}}
121+
/>
122+
123+
<DatePicker
124+
label="End Date"
125+
value={formData.endDate}
126+
onChange={(newValue) => {
127+
setFormData(prev => ({ ...prev, endDate: newValue }));
128+
}}
129+
minDate={formData.startDate}
130+
slotProps={{
131+
textField: {
132+
fullWidth: true,
133+
error: !isDateRangeValid(),
134+
helperText: !isDateRangeValid() ? 'End date must be after start date' : ''
135+
}
136+
}}
137+
/>
138+
</LocalizationProvider>
139+
140+
<Box sx={{ display: 'flex', gap: 2, mt: 2 }}>
141+
<Button
142+
type="button"
143+
variant="outlined"
144+
onClick={() => navigate('/dashboard')}
145+
sx={{ flex: 1 }}
146+
>
147+
Cancel
148+
</Button>
149+
<Button
150+
type="submit"
151+
variant="contained"
152+
disabled={!isFormValid()}
153+
sx={{ flex: 1 }}
154+
>
155+
{loading ? 'Creating...' : 'Create Trip'}
156+
</Button>
157+
</Box>
158+
</Box>
159+
</Paper>
160+
);
161+
};
162+
163+
export default NewTripForm;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Container } from '@mui/material';
2+
import NewTripForm from '../components/trips/NewTripForm';
3+
4+
const NewTripPage = () => {
5+
return (
6+
<Container>
7+
<NewTripForm />
8+
</Container>
9+
);
10+
};
11+
12+
export default NewTripPage;

planventure-client/src/routes/routes.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import LoginPage from '../pages/LoginPage';
33
import SignUpPage from '../pages/SignUpPage';
44
import Dashboard from '../pages/Dashboard';
55
import DashboardLayout from '../layouts/DashboardLayout';
6+
import NewTripPage from '../pages/NewTripPage';
67

78
export const publicRoutes = [
89
{
@@ -28,4 +29,8 @@ export const protectedRoutes = [
2829
path: '/trips',
2930
element: <DashboardLayout><Dashboard /></DashboardLayout>,
3031
},
32+
{
33+
path: '/trips/new',
34+
element: <DashboardLayout><NewTripPage /></DashboardLayout>,
35+
}
3136
];

0 commit comments

Comments
 (0)