This commit is contained in:
jableader 2025-10-18 12:42:23 +11:00
parent ec28531df9
commit a6904524db
10 changed files with 78 additions and 13 deletions

12
.editorconfig Normal file
View file

@ -0,0 +1,12 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false

7
.prettierrc.json Normal file
View file

@ -0,0 +1,7 @@
{
"singleQuote": true,
"semi": false,
"trailingComma": "es5",
"printWidth": 100,
"arrowParens": "always"
}

View file

@ -31,14 +31,14 @@ Outcome: Routing logic is centralized and testable; pages redirect consistently
Outcome: Feature modules call cohesive services; logic for mapping/normalization is isolated and testable. Outcome: Feature modules call cohesive services; logic for mapping/normalization is isolated and testable.
### Phase 3 — Composables (UI-Facing Logic) ### Phase 3 — Composables (UI-Facing Logic)
- [ ] Add `src/composables/useAuth.js` (user ref, ensureAuth) - [x] Add `src/composables/useAuth.js` (user ref, ensureAuth)
- [ ] Add `src/composables/useMeals.js` (fetch and mutate meals) - [ ] Add `src/composables/useMeals.js` (fetch and mutate meals)
- [ ] Refactor pages to use composables and `<script setup>` where appropriate - [ ] Refactor pages to use composables and `<script setup>` where appropriate
Outcome: Components get smaller and easier to read; business logic is reusable. Outcome: Components get smaller and easier to read; business logic is reusable.
### Phase 4 — Tooling and Standards ### Phase 4 — Tooling and Standards
- [ ] Add Prettier config and .editorconfig; wire Prettier with ESLint - [x] Add Prettier config and .editorconfig; wire Prettier with ESLint
- [ ] Upgrade ESLint (if/when convenient) and align with Vue 3 rules - [ ] Upgrade ESLint (if/when convenient) and align with Vue 3 rules
- [ ] Ensure Volar is used (dev environment) for Vue 3 type intelligence - [ ] Ensure Volar is used (dev environment) for Vue 3 type intelligence
@ -63,3 +63,5 @@ Outcome: Confidence in refactors and easier onboarding.
- Added `api/mappers/recipeMapper.js`, `api/recipes.js`; updated recipes components - Added `api/mappers/recipeMapper.js`, `api/recipes.js`; updated recipes components
- Added `api/mappers/shoppingListMapper.js`, `api/shopping.js`; updated shopping components - Added `api/mappers/shoppingListMapper.js`, `api/shopping.js`; updated shopping components
- Added `api/auth.js` and `api/persons.js`; router uses auth API - Added `api/auth.js` and `api/persons.js`; router uses auth API
- Added `composables/useAuth.js` and used in `MyShoppingPage.vue`
- Added Prettier and EditorConfig

View file

@ -16,3 +16,16 @@ export async function markMealConsumed(mealId) {
export async function deleteMeal(mealId) { export async function deleteMeal(mealId) {
return http.del(`/meals/${encodeURIComponent(mealId)}`) return http.del(`/meals/${encodeURIComponent(mealId)}`)
} }
export async function getMeal(id) {
const meal = await http.get(`/meals/${encodeURIComponent(id)}`)
return mapMeal(meal)
}
export async function saveMeal(meal) {
const hasId = meal.id >= 0
const path = hasId ? `/meals/${encodeURIComponent(meal.id)}` : '/meals'
const method = hasId ? http.put : http.post
const saved = await method(path, meal)
return mapMeal(saved)
}

View file

@ -3,3 +3,7 @@ import { http } from './http'
export async function getPersonsInHome() { export async function getPersonsInHome() {
return http.get('/persons') return http.get('/persons')
} }
export async function searchPerson(name) {
return http.get(`/persons?q=${encodeURIComponent(name)}`)
}

View file

@ -73,7 +73,8 @@ li:nth-child(4) > button {
</style> </style>
<script> <script>
import data from '@/data'; import { getPersonsInHome } from '@/api/persons';
import { login } from '@/api/auth';
export default { export default {
name: 'LoginVue', name: 'LoginVue',
@ -89,11 +90,11 @@ export default {
} }
}, },
async beforeMount() { async beforeMount() {
this.persons = await data.getPersonsInHome(); this.persons = await getPersonsInHome();
}, },
methods: { methods: {
async login(selectedPerson) { async login(selectedPerson) {
const person = await data.login(selectedPerson.name); const person = await login(selectedPerson.name);
if (person?.id >= 0) { if (person?.id >= 0) {
this.$router.push(this.redirect); this.$router.push(this.redirect);
return; return;

View file

@ -59,7 +59,9 @@
<script> <script>
import data from '@/data.js'; import { getMeal, saveMeal } from '@/api/meals';
import { getRecipe } from '@/api/recipes';
import { currentUser } from '@/api/auth';
import alert from '@/alert.js'; import alert from '@/alert.js';
import { ago } from '@/dateformats.js'; import { ago } from '@/dateformats.js';
@ -96,10 +98,10 @@ export default {
}, },
async beforeMount() { async beforeMount() {
if (this.id >= 0) { if (this.id >= 0) {
this.meal = await data.getMeal(this.id); this.meal = await getMeal(this.id);
} }
else { else {
const self = await data.currentUser(); const self = await currentUser();
this.meal = {...this.meal, chefs: [self], consumers: [self], cleanup: [self], }; this.meal = {...this.meal, chefs: [self], consumers: [self], cleanup: [self], };
} }
}, },
@ -107,7 +109,7 @@ export default {
ago, ago,
async selectRecipe(recipe) { async selectRecipe(recipe) {
// Refetch to get additional details // Refetch to get additional details
recipe = await data.getRecipe(recipe.id); recipe = await getRecipe(recipe.id);
if (recipe.created_by) { if (recipe.created_by) {
addPersonIfNotExists(this.meal.chefs, recipe.created_by); addPersonIfNotExists(this.meal.chefs, recipe.created_by);
@ -144,7 +146,7 @@ export default {
addPersonIfNotExists(this.meal[list], person); addPersonIfNotExists(this.meal[list], person);
}, },
async saveMeal() { async saveMeal() {
const meal = await data.saveMeal(this.meal); const meal = await saveMeal(this.meal);
if (meal?.id >= 0) { if (meal?.id >= 0) {
this.meal = meal; this.meal = meal;

View file

@ -99,7 +99,7 @@
<script> <script>
import { ref } from 'vue'; import { ref } from 'vue';
import data from '@/data.js' import { searchPerson } from '@/api/persons'
export default { export default {
name: 'PersonList', name: 'PersonList',
@ -142,7 +142,7 @@ export default {
}, },
methods: { methods: {
async updateSearchResults() { async updateSearchResults() {
const results = await data.searchPerson(this.searchName); const results = await searchPerson(this.searchName);
// Exclude people already in the list // Exclude people already in the list
const idSet = new Set(this.people.map(p => p.id)); const idSet = new Set(this.people.map(p => p.id));
this.searchResults = results.filter(p => !idSet.has(p.id)); this.searchResults = results.filter(p => !idSet.has(p.id));

View file

@ -37,6 +37,7 @@
<script> <script>
import { getMyShoppingList, saveMyShoppingList } from '@/api/shopping' import { getMyShoppingList, saveMyShoppingList } from '@/api/shopping'
import { useAuth } from '@/composables/useAuth'
import EditableIngredientsPanel from '@/components/ingredients/EditableIngredientsPanel.vue' import EditableIngredientsPanel from '@/components/ingredients/EditableIngredientsPanel.vue'
@ -47,7 +48,8 @@ export default {
return { ingredients: [], person: null } return { ingredients: [], person: null }
}, },
async beforeMount() { async beforeMount() {
const person = await this.$router.app?.config?.globalProperties?.$user || null; const { loadUser } = useAuth()
const person = await loadUser()
if (!person) if (!person)
return this.$router.push({ name: 'login' }); return this.$router.push({ name: 'login' });

View file

@ -0,0 +1,22 @@
import { ref } from 'vue'
import { currentUser as apiCurrentUser, login as apiLogin } from '@/api/auth'
const user = ref(null)
let initialized = false
export async function loadUser() {
if (!initialized) {
user.value = await apiCurrentUser()
initialized = true
}
return user.value
}
export async function login(username) {
user.value = await apiLogin(username)
return user.value
}
export function useAuth() {
return { user, loadUser, login }
}