171 lines
5.6 KiB
TypeScript
171 lines
5.6 KiB
TypeScript
import type { Place } from "./types";
|
|
|
|
export type Screen = "places" | "collections" | "profile" | "place" | "collection";
|
|
|
|
export type Tab = "places" | "collections" | "profile";
|
|
|
|
export type StackFrame = {
|
|
screen: Screen;
|
|
placeId?: number;
|
|
collectionId?: number;
|
|
};
|
|
|
|
export type Modal =
|
|
| { type: "add" }
|
|
| { type: "editPlace"; placeId: number }
|
|
| { type: "saveToCollection"; placeId: number }
|
|
| { type: "createCollection" }
|
|
| { type: "editCollection"; collectionId: number }
|
|
| { type: "invite"; collectionId: number }
|
|
| { type: "members"; collectionId: number }
|
|
| { type: "confirmDeletePlace"; placeId: number }
|
|
| { type: "confirmDeleteCollection"; collectionId: number }
|
|
| null;
|
|
|
|
export type AppState = {
|
|
tab: Tab;
|
|
stack: StackFrame[];
|
|
filter: string;
|
|
search: string;
|
|
places: Place[];
|
|
modal: Modal;
|
|
toast: string | null;
|
|
toastKey: number;
|
|
offline: boolean;
|
|
};
|
|
|
|
export type Action =
|
|
| { type: "NAV"; screen: Screen; placeId?: number; collectionId?: number }
|
|
| { type: "BACK" }
|
|
| { type: "TAB"; tab: Tab }
|
|
| { type: "SET_FILTER"; value: string }
|
|
| { type: "SET_SEARCH"; value: string }
|
|
| { type: "TOGGLE_VISITED"; placeId: number }
|
|
| { type: "SET_RATING"; placeId: number; value: number }
|
|
| { type: "SET_NOTES"; placeId: number; value: string }
|
|
| { type: "ADD_PLACE"; place: Place }
|
|
| { type: "PATCH_PLACE"; placeId: number; patch: Partial<Place> }
|
|
| { type: "DELETE_PLACE"; placeId: number }
|
|
| { type: "TOAST"; value: string }
|
|
| { type: "CLEAR_TOAST"; key: number }
|
|
| { type: "OPEN_ADD" }
|
|
| { type: "OPEN_EDIT_PLACE"; placeId: number }
|
|
| { type: "OPEN_SAVE_TO_COLLECTION"; placeId: number }
|
|
| { type: "OPEN_CREATE_COLLECTION" }
|
|
| { type: "OPEN_EDIT_COLLECTION"; collectionId: number }
|
|
| { type: "OPEN_INVITE"; collectionId: number }
|
|
| { type: "OPEN_MEMBERS"; collectionId: number }
|
|
| { type: "CONFIRM_DELETE_PLACE"; placeId: number }
|
|
| { type: "CONFIRM_DELETE_COLLECTION"; collectionId: number }
|
|
| { type: "CLOSE_MODAL" }
|
|
| { type: "SET_OFFLINE"; value: boolean };
|
|
|
|
export function makeInitialState(places: Place[]): AppState {
|
|
return {
|
|
tab: "places",
|
|
stack: [{ screen: "places" }],
|
|
filter: "all",
|
|
search: "",
|
|
places,
|
|
modal: null,
|
|
toast: null,
|
|
toastKey: 0,
|
|
offline: false,
|
|
};
|
|
}
|
|
|
|
export function reducer(state: AppState, action: Action): AppState {
|
|
switch (action.type) {
|
|
case "NAV": {
|
|
const stack = [
|
|
...state.stack,
|
|
{ screen: action.screen, placeId: action.placeId, collectionId: action.collectionId },
|
|
];
|
|
return { ...state, stack };
|
|
}
|
|
case "BACK": {
|
|
if (state.stack.length <= 1) return state;
|
|
return { ...state, stack: state.stack.slice(0, -1) };
|
|
}
|
|
case "TAB":
|
|
return { ...state, tab: action.tab, stack: [{ screen: action.tab }] };
|
|
case "SET_FILTER":
|
|
return { ...state, filter: action.value };
|
|
case "SET_SEARCH":
|
|
return { ...state, search: action.value };
|
|
case "TOGGLE_VISITED": {
|
|
const places = state.places.map((p) =>
|
|
p.id === action.placeId
|
|
? {
|
|
...p,
|
|
visited: !p.visited,
|
|
visited_at: !p.visited
|
|
? new Date().toISOString().slice(0, 10)
|
|
: undefined,
|
|
}
|
|
: p,
|
|
);
|
|
return { ...state, places };
|
|
}
|
|
case "SET_RATING": {
|
|
const places = state.places.map((p) =>
|
|
p.id === action.placeId
|
|
? { ...p, my_rating: action.value || undefined }
|
|
: p,
|
|
);
|
|
return { ...state, places };
|
|
}
|
|
case "SET_NOTES": {
|
|
const places = state.places.map((p) =>
|
|
p.id === action.placeId ? { ...p, my_notes: action.value } : p,
|
|
);
|
|
return { ...state, places };
|
|
}
|
|
case "ADD_PLACE":
|
|
return { ...state, places: [action.place, ...state.places] };
|
|
case "PATCH_PLACE": {
|
|
const places = state.places.map((p) =>
|
|
p.id === action.placeId ? { ...p, ...action.patch } : p,
|
|
);
|
|
return { ...state, places };
|
|
}
|
|
case "DELETE_PLACE":
|
|
return {
|
|
...state,
|
|
places: state.places.filter((p) => p.id !== action.placeId),
|
|
stack: state.stack.length > 1 ? state.stack.slice(0, -1) : state.stack,
|
|
modal: null,
|
|
};
|
|
case "TOAST":
|
|
return { ...state, toast: action.value, toastKey: state.toastKey + 1 };
|
|
case "CLEAR_TOAST":
|
|
return state.toastKey === action.key ? { ...state, toast: null } : state;
|
|
case "OPEN_ADD":
|
|
return { ...state, modal: { type: "add" } };
|
|
case "OPEN_EDIT_PLACE":
|
|
return { ...state, modal: { type: "editPlace", placeId: action.placeId } };
|
|
case "OPEN_SAVE_TO_COLLECTION":
|
|
return { ...state, modal: { type: "saveToCollection", placeId: action.placeId } };
|
|
case "OPEN_CREATE_COLLECTION":
|
|
return { ...state, modal: { type: "createCollection" } };
|
|
case "OPEN_EDIT_COLLECTION":
|
|
return { ...state, modal: { type: "editCollection", collectionId: action.collectionId } };
|
|
case "OPEN_INVITE":
|
|
return { ...state, modal: { type: "invite", collectionId: action.collectionId } };
|
|
case "OPEN_MEMBERS":
|
|
return { ...state, modal: { type: "members", collectionId: action.collectionId } };
|
|
case "CONFIRM_DELETE_PLACE":
|
|
return { ...state, modal: { type: "confirmDeletePlace", placeId: action.placeId } };
|
|
case "CONFIRM_DELETE_COLLECTION":
|
|
return { ...state, modal: { type: "confirmDeleteCollection", collectionId: action.collectionId } };
|
|
case "CLOSE_MODAL":
|
|
return { ...state, modal: null };
|
|
case "SET_OFFLINE":
|
|
return { ...state, offline: action.value };
|
|
default:
|
|
return state;
|
|
}
|
|
}
|
|
|
|
export type Dispatch = (action: Action) => void;
|