Skip to content

Commit 4f2ea0b

Browse files
committed
feat: add minimal theme with StepList component and styling
- Implemented a new minimal theme for the itinerary application. - Created StepList component to display steps with date, time, title, location, and notes. - Added theme configuration in index.ts and corresponding CSS styles. - Updated itinerary page to utilize the new StepList component. - Refactored itinerary loading logic to include theme data. - Removed deprecated itinerary detail page and associated logic. - Updated database schema to support new step and itinerary attributes. - Added new types for steps and themes in the types package. - Updated package dependencies to latest versions.
1 parent 39dcbe1 commit 4f2ea0b

30 files changed

Lines changed: 552 additions & 803 deletions

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,6 @@ dist/
146146
.wrangler/
147147

148148
00_memo
149-
memo*
150149

151150
# TurboRepo cache
152151
.turbo/

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: build deploy deploy-api deploy-web dev
1+
.PHONY: build deploy deploy-api deploy-web dev migrate
22

33
build:
44
pnpm run build
@@ -13,3 +13,6 @@ deploy-web:
1313

1414
dev:
1515
pnpm run dev
16+
17+
migrate:
18+
cd apps/api && pnpm wrangler d1 execute tabitabi --local --file=../../migrations/0001_simple_schema.sql

apps/api/src/index.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,13 @@ import steps from './routes/steps';
66

77
const app = new Hono<{ Bindings: Env }>();
88

9-
// Apply CORS middleware
109
app.use('*', corsMiddleware);
1110

12-
// Health check
1311
app.get('/health', (c) => {
1412
return c.json({ status: 'ok', service: 'tabitabi-api' });
1513
});
1614

17-
// API routes
1815
app.route('/api/v1/itineraries', itineraries);
19-
app.route('/api/v1/itineraries', steps);
16+
app.route('/api/v1/steps', steps);
2017

2118
export default app;

apps/api/src/routes/itineraries.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@ import { ItineraryService } from '../services/itinerary.service';
44

55
const itineraries = new Hono<{ Bindings: Env }>();
66

7-
// List itineraries
87
itineraries.get('/', async (c) => {
98
const service = new ItineraryService(c.env.DB);
109
const data = await service.list();
1110
return c.json({ success: true, data });
1211
});
1312

14-
// Get itinerary by ID
1513
itineraries.get('/:id', async (c) => {
1614
const id = c.req.param('id');
1715
const service = new ItineraryService(c.env.DB);
@@ -24,15 +22,13 @@ itineraries.get('/:id', async (c) => {
2422
return c.json({ success: true, data });
2523
});
2624

27-
// Create itinerary
2825
itineraries.post('/', async (c) => {
2926
const input = await c.req.json();
3027
const service = new ItineraryService(c.env.DB);
3128
const data = await service.create(input);
3229
return c.json({ success: true, data }, 201);
3330
});
3431

35-
// Update itinerary
3632
itineraries.put('/:id', async (c) => {
3733
const id = c.req.param('id');
3834
const input = await c.req.json();
@@ -46,7 +42,6 @@ itineraries.put('/:id', async (c) => {
4642
return c.json({ success: true, data });
4743
});
4844

49-
// Delete itinerary
5045
itineraries.delete('/:id', async (c) => {
5146
const id = c.req.param('id');
5247
const service = new ItineraryService(c.env.DB);

apps/api/src/routes/steps.ts

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,80 @@ import { StepService } from '../services/step.service';
44

55
const steps = new Hono<{ Bindings: Env }>();
66

7-
// Get steps for an itinerary
8-
steps.get('/:itineraryId/steps', async (c) => {
9-
const itineraryId = c.req.param('itineraryId');
7+
steps.get('/', async (c) => {
8+
const itineraryId = c.req.query('itinerary_id');
9+
10+
if (!itineraryId) {
11+
return c.json({
12+
success: false,
13+
error: { code: 'VALIDATION_ERROR', message: 'itinerary_id is required' }
14+
}, 400);
15+
}
16+
1017
const service = new StepService(c.env.DB);
1118
const data = await service.list(itineraryId);
1219
return c.json({ success: true, data });
1320
});
1421

15-
// Add step
16-
steps.post('/:itineraryId/steps', async (c) => {
17-
const itineraryId = c.req.param('itineraryId');
22+
steps.get('/:stepId', async (c) => {
23+
const stepId = c.req.param('stepId');
24+
const service = new StepService(c.env.DB);
25+
const data = await service.get(stepId);
26+
27+
if (!data) {
28+
return c.json({
29+
success: false,
30+
error: { code: 'NOT_FOUND', message: 'Step not found' }
31+
}, 404);
32+
}
33+
34+
return c.json({ success: true, data });
35+
});
36+
37+
steps.post('/', async (c) => {
1838
const input = await c.req.json();
39+
40+
if (!input.itinerary_id || !input.title || !input.date || !input.time) {
41+
return c.json({
42+
success: false,
43+
error: {
44+
code: 'VALIDATION_ERROR',
45+
message: 'itinerary_id, title, date, and time are required'
46+
}
47+
}, 400);
48+
}
49+
1950
const service = new StepService(c.env.DB);
20-
const data = await service.create(itineraryId, input);
51+
const data = await service.create(input);
2152
return c.json({ success: true, data }, 201);
2253
});
2354

24-
// Update step
25-
steps.put('/steps/:stepId', async (c) => {
55+
steps.put('/:stepId', async (c) => {
2656
const stepId = c.req.param('stepId');
2757
const input = await c.req.json();
2858
const service = new StepService(c.env.DB);
2959
const data = await service.update(stepId, input);
3060

3161
if (!data) {
32-
return c.json({ success: false, error: { code: 'NOT_FOUND', message: 'Step not found' } }, 404);
62+
return c.json({
63+
success: false,
64+
error: { code: 'NOT_FOUND', message: 'Step not found' }
65+
}, 404);
3366
}
3467

3568
return c.json({ success: true, data });
3669
});
3770

38-
// Delete step
39-
steps.delete('/steps/:stepId', async (c) => {
71+
steps.delete('/:stepId', async (c) => {
4072
const stepId = c.req.param('stepId');
4173
const service = new StepService(c.env.DB);
4274
const success = await service.delete(stepId);
4375

4476
if (!success) {
45-
return c.json({ success: false, error: { code: 'NOT_FOUND', message: 'Step not found' } }, 404);
77+
return c.json({
78+
success: false,
79+
error: { code: 'NOT_FOUND', message: 'Step not found' }
80+
}, 404);
4681
}
4782

4883
return c.json({ success: true, data: null });

apps/api/src/services/itinerary.service.ts

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ import { generateId, getCurrentTimestamp } from '../utils';
55
export class ItineraryService {
66
constructor(private db: D1Database) {}
77

8+
async list(): Promise<Itinerary[]> {
9+
const result = await this.db
10+
.prepare('SELECT * FROM itineraries ORDER BY created_at DESC')
11+
.all();
12+
13+
return result.results ? result.results.map(row => this.mapToItinerary(row)) : [];
14+
}
15+
816
async get(id: string): Promise<Itinerary | null> {
917
const result = await this.db
1018
.prepare('SELECT * FROM itineraries WHERE id = ?')
@@ -15,21 +23,20 @@ export class ItineraryService {
1523
}
1624

1725
async create(input: CreateItineraryInput): Promise<Itinerary> {
18-
const id = generateId(8);
26+
const id = generateId(32);
1927
const now = getCurrentTimestamp();
2028

2129
const itinerary: Itinerary = {
2230
id,
2331
title: input.title,
24-
startDate: input.startDate,
25-
endDate: input.endDate,
26-
themeId: input.themeId || 'standard',
27-
createdAt: now,
32+
theme_id: input.theme_id || 'minimal',
33+
created_at: now,
34+
updated_at: now,
2835
};
2936

3037
await this.db
31-
.prepare('INSERT INTO itineraries (id, title, start_date, end_date, theme_id, created_at) VALUES (?, ?, ?, ?, ?, ?)')
32-
.bind(itinerary.id, itinerary.title, itinerary.startDate, itinerary.endDate, itinerary.themeId, itinerary.createdAt)
38+
.prepare('INSERT INTO itineraries (id, title, theme_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?)')
39+
.bind(itinerary.id, itinerary.title, itinerary.theme_id, itinerary.created_at, itinerary.updated_at)
3340
.run();
3441

3542
return itinerary;
@@ -39,40 +46,28 @@ export class ItineraryService {
3946
const existing = await this.get(id);
4047
if (!existing) return null;
4148

42-
const updated: Itinerary = {
43-
...existing,
44-
...input,
45-
};
46-
47-
const fields = [];
48-
const values = [];
49+
const now = getCurrentTimestamp();
50+
const fields = ['updated_at = ?'];
51+
const values = [now];
4952

5053
if (input.title !== undefined) {
5154
fields.push('title = ?');
5255
values.push(input.title);
5356
}
54-
if (input.startDate !== undefined) {
55-
fields.push('start_date = ?');
56-
values.push(input.startDate);
57-
}
58-
if (input.endDate !== undefined) {
59-
fields.push('end_date = ?');
60-
values.push(input.endDate);
61-
}
62-
if (input.themeId !== undefined) {
57+
if (input.theme_id !== undefined) {
6358
fields.push('theme_id = ?');
64-
values.push(input.themeId);
59+
values.push(input.theme_id);
6560
}
6661

67-
if (fields.length > 0) {
62+
if (fields.length > 1) {
6863
values.push(id);
6964
await this.db
7065
.prepare(`UPDATE itineraries SET ${fields.join(', ')} WHERE id = ?`)
7166
.bind(...values)
7267
.run();
7368
}
7469

75-
return updated;
70+
return await this.get(id);
7671
}
7772

7873
async delete(id: string): Promise<boolean> {
@@ -88,10 +83,9 @@ export class ItineraryService {
8883
return {
8984
id: row.id,
9085
title: row.title,
91-
startDate: row.start_date,
92-
endDate: row.end_date,
93-
themeId: row.theme_id,
94-
createdAt: row.created_at,
86+
theme_id: row.theme_id,
87+
created_at: row.created_at,
88+
updated_at: row.updated_at,
9589
};
9690
}
9791
}

0 commit comments

Comments
 (0)