ali-ahnaf / ali-ahnaf/pocket_pixel
Add a new page called Adventures, where users can plan and track the gold they'll spend on a trip or outing.
- Lingua principale
- TypeScript
- Stelle
- 14
- Fork
- 91
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Descrizione
Right now there's no way to plan a one-off trip or event across multiple
expenses. Vaults are ongoing spending categories, not a "planning" tool for a
single bounded adventure with its own budget and item list.
## 📖 The Incantation
### Theme
Frame the page like preparing for a quest:
- Creating an Adventure = "Charting a new quest"
- The total cost = **Adventure Fund** / **Quest Chest**
- Each Adventure can have flavor text, e.g. *"A journey into the misty peaks —
pack wisely, adventurer."*
- A finished/past Adventure shows a "Quest Complete" summary with total gold spent
### Data Models (TypeORM, following `Vault.entity.ts` conventions)
**`Adventure.entity.ts`**
```ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, JoinColumn } from 'typeorm';
import { User } from './User.entity';
import { AdventureItem } from './AdventureItem.entity';
import { BaseEntity } from './BaseEntity';
@Entity('adventures')
export class Adventure extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'varchar' })
userId: string;
@Column({ type: 'varchar', length: 100 })
name: string;
@Column({ type: 'varchar', length: 255, nullable: true })
description: string | null;
@Column({ type: 'varchar', length: 100, nullable: true })
icon: string | null;
@Column({ type: 'varchar', length: 50, nullable: true })
backgroundColor: string | null;
@Column({ type: 'date', nullable: true })
startDate: string | null;
@Column({ type: 'date', nullable: true })
endDate: string | null;
@Column({ type: 'boolean', default: false })
isComplete: boolean;
@ManyToOne(() => User, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'userId' })
user: User;
@OneToMany(() => AdventureItem, (item) => item.adventure)
items: AdventureItem[];
}
```
**`AdventureItem.entity.ts`**
```ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
import { Adventure } from './Adventure.entity';
import { BaseEntity } from './BaseEntity';
@Entity('adventure_items')
export class AdventureItem extends BaseEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'varchar' })
adventureId: string;
@Column({ type: 'varchar', length: 150 })
name: string;
@Column({ type: 'decimal', precision: 10, scale: 2 })
amount: number;
@ManyToOne(() => Adventure, (adventure) => adventure.items, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'adventureId' })
adventure: Adventure;
}
```
### API Endpoints (mirroring `vaults.routes.ts` folder structure)
**Adventures** — `packages/api/src/routes/adventures/`
| Method | Route | Description |
|---|---|---|
| GET | `/adventures` | List all adventures for the logged-in user |
| GET | `/adventures/:adventureId` | Get one adventure with its items and total |
| POST | `/adventures` | Create a new adventure (`name`, `description?`, `icon?`, `backgroundColor?`, `startDate?`, `endDate?`) |
| PUT | `/adventures/:adventureId` | Update adventure details / mark `isComplete` |
| DELETE | `/adventures/:adventureId` | Delete an adventure (cascades to its items) |
**Adventure Items** — `packages/api/src/routes/adventures/items/`
| Method | Route | Description |
|---|---|---|
| GET | `/adventures/:adventureId/items` | List items for an adventure |
| POST | `/adventures/:adventureId/items` | Add an item (`name`, `amount`) |
| PUT | `/adventures/:adventureId/items/:itemId` | Update an item's name/amount |
| DELETE | `/adventures/:adventureId/items/:itemId` | Remove an item |
Validation via Joi, e.g.:
```ts
const createAdventureSchema = Joi.object({
name: Joi.string().max(100).required(),
description: Joi.string().max(255).allow('').optional(),
icon: Joi.string().max(100).optional(),
backgroundColor: Joi.string().max(50).optional(),
startDate: Joi.date().iso().optional(),
endDate: Joi.date().iso().min(Joi.ref('startDate')).optional(),
});
const createAdventureItemSchema = Joi.object({
name: Joi.string().max(150).required(),
amount: Joi.number().positive().precision(2).required(),
});
```
Services would follow the same pattern as `vaults.service.ts` / `vaults.repository.ts`
(`AdventuresService`, `AdventuresRepository`) with a computed `totalCost` field
summing `items.amount` for display as the "Quest Chest" total.
## 🗝️ Alternate Paths
- Could reuse Vaults with a "trip" flag instead of a new entity — rejected
because Adventures are bounded/one-off with a fixed item list, unlike ongoing
budget categories.
- Could store items as Tags on regular transactions — loses the dedicated
planning/estimate-vs-actual view.
## 💎 Treasures & Lore
- New nav item: **Adventures**
- Follows existing pixel-art UI conventions used by Vaults and Quests
- Frontend contract should live in `packages/shared/src/contracts/adventures.ts`,
mirroring `vaults.ts`
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Valutazione
Questa issue non è ancora stata valutata.