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.
### 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)
- [ ] Refactor pages to use composables and `<script setup>` where appropriate
Outcome: Components get smaller and easier to read; business logic is reusable.
### 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
- [ ] 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/shoppingListMapper.js`, `api/shopping.js`; updated shopping components
- 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) {
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() {
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>
<script>
import data from '@/data';
import { getPersonsInHome } from '@/api/persons';
import { login } from '@/api/auth';
export default {
name: 'LoginVue',
@ -89,11 +90,11 @@ export default {
}
},
async beforeMount() {
this.persons = await data.getPersonsInHome();
this.persons = await getPersonsInHome();
},
methods: {
async login(selectedPerson) {
const person = await data.login(selectedPerson.name);
const person = await login(selectedPerson.name);
if (person?.id >= 0) {
this.$router.push(this.redirect);
return;

View file

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

View file

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

View file

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