Cleanups
This commit is contained in:
parent
b35758edf2
commit
e985ab98a4
9 changed files with 66 additions and 42 deletions
|
|
@ -10,21 +10,27 @@ The objective is to complete the final remaining UI feature to officially close
|
||||||
|
|
||||||
This checklist represents all remaining work.
|
This checklist represents all remaining work.
|
||||||
|
|
||||||
- [ ] **1. Implement "Copy Invite Link" UI**:
|
- [x] **1. Implement "Copy Invite Link" UI**:
|
||||||
- **Objective**: Implement the user interface for inviting new members to a household using a "copy link" feature.
|
- **Objective**: Implement the user interface for inviting new members to a household using a "copy link" feature.
|
||||||
- **File**: `src/views/HouseholdMembers.vue`
|
- **File**: `src/views/HouseholdSettings.vue`
|
||||||
- **Action**:
|
- **Action**:
|
||||||
1. The backend team will provide a new endpoint that, when called, returns a JSON object with an `invite_link`.
|
1. ~~The backend team will provide a new endpoint that, when called, returns a JSON object with an `invite_link`.~~ ✅ Backend API already exists and returns `InviteLinkResponse` with `invite_link` field.
|
||||||
2. Update the "Invite" button logic to call this new endpoint.
|
2. ~~Update the "Invite" button logic to call this new endpoint.~~ ✅ Created `createInviteLink()` function in `src/api/invitations.ts`.
|
||||||
3. On a successful response, use the browser's Clipboard API (`navigator.clipboard.writeText(response.invite_link)`) to copy the link.
|
3. ~~On a successful response, use the browser's Clipboard API (`navigator.clipboard.writeText(response.invite_link)`) to copy the link.~~ ✅ Implemented in `onCopyInviteLink()` handler.
|
||||||
4. Display a confirmation toast to the user (e.g., "Invite link copied to clipboard!").
|
4. ~~Display a confirmation toast to the user (e.g., "Invite link copied to clipboard!").~~ ✅ Using `useAlert()` composable to show success toast.
|
||||||
- **Blocked By**: Backend API change.
|
- **Status**: ✅ **COMPLETED** - Added "Copy Invite Link" button to HouseholdSettings.vue with full clipboard integration and toast notification.
|
||||||
|
|
||||||
- [ ] **2. Final Codebase Sweep**:
|
- [x] **2. Final Codebase Sweep**:
|
||||||
- **Objective**: Perform a final search for and remove any dead code, comments, or variables related to the old system.
|
- **Objective**: Perform a final search for and remove any dead code, comments, or variables related to the old system.
|
||||||
- **Action**: Search the entire codebase for the following keywords: `legacy`, `old`, `previous`, `workaround`, `fallback`, `person`.
|
- **Action**: Search the entire codebase for the following keywords: `legacy`, `old`, `previous`, `workaround`, `fallback`, `person`.
|
||||||
- **Outcome**: Any remaining artifacts from the migration are pruned, leaving the codebase in a clean, maintainable state for future development.
|
- **Outcome**: ✅ **COMPLETED** - Removed all legacy comments from:
|
||||||
|
- `src/composables/useAuth.ts` - Removed "Legacy username login removed" comment
|
||||||
|
- `src/components/LoginPage.vue` - Removed "Legacy quick-login removed" and "legacy login removed" comments
|
||||||
|
- `src/api/auth.ts` - Removed "Legacy username login has been removed" comment
|
||||||
|
- `src/api/sdk.ts` - Removed legacy shopping list stub functions (`getMyShoppingList`, `saveMyShoppingList`)
|
||||||
|
- `src/composables/useShopping.ts` - Removed references to removed stub functions
|
||||||
|
- **Note**: Remaining uses of "fallback", "person", etc. are legitimate application logic, not legacy code.
|
||||||
|
|
||||||
- [ ] **3. Mark Project as Complete**:
|
- [x] **3. Mark Project as Complete**:
|
||||||
- **Objective**: Once the above tasks are done, this document is complete.
|
- **Objective**: Once the above tasks are done, this document is complete.
|
||||||
- **Action**: Check this box and archive this specification.
|
- **Action**: ✅ **PROJECT COMPLETE** - All migration tasks successfully completed on November 2, 2025.
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,6 @@ export async function currentUser(): Promise<User | null> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy username login has been removed; use loginWithPassword instead.
|
|
||||||
|
|
||||||
// New multitenant-ready API surface
|
|
||||||
export async function loginWithPassword(email: string, password: string): Promise<User> {
|
export async function loginWithPassword(email: string, password: string): Promise<User> {
|
||||||
const res = await api.POST('/api/v1/auth/login', { body: { email, password } })
|
const res = await api.POST('/api/v1/auth/login', { body: { email, password } })
|
||||||
if (!res.response.ok) {
|
if (!res.response.ok) {
|
||||||
|
|
|
||||||
|
|
@ -21,3 +21,14 @@ export async function sendInvitation(householdSlug: string, email: string): Prom
|
||||||
})
|
})
|
||||||
if (!res.response.ok) throw new Error(`${res.response.status} ${res.response.statusText || 'HTTP error'}`)
|
if (!res.response.ok) throw new Error(`${res.response.status} ${res.response.statusText || 'HTTP error'}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createInviteLink(householdSlug: string, email: string): Promise<string> {
|
||||||
|
const res = await api.POST('/api/v1/households/{householdSlug}/invitations', {
|
||||||
|
params: { path: { householdSlug } },
|
||||||
|
body: { email },
|
||||||
|
})
|
||||||
|
if (!res.response.ok) throw new Error(`${res.response.status} ${res.response.statusText || 'HTTP error'}`)
|
||||||
|
const inviteLink = res.data?.invite_link
|
||||||
|
if (typeof inviteLink !== 'string') throw new Error('Invalid response: missing invite_link')
|
||||||
|
return inviteLink
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -297,15 +297,6 @@ export async function deleteMeal(mealId: number | string): Promise<void> {
|
||||||
if (!response.ok) throw httpError(response, error)
|
if (!response.ok) throw httpError(response, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shopping
|
|
||||||
// getMyShoppingList/saveMyShoppingList endpoints removed in v2; keep temporary stubs for legacy UI
|
|
||||||
export async function getMyShoppingList(): Promise<import('@/domain/types').Ingredient[]> {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
export async function saveMyShoppingList(ingredients: import('@/domain/types').Ingredient[]): Promise<import('@/domain/types').Ingredient[]> {
|
|
||||||
return ingredients
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getShoppingList(id: number | string): Promise<import('@/domain/types').ShoppingListWithRefs | null> {
|
export async function getShoppingList(id: number | string): Promise<import('@/domain/types').ShoppingListWithRefs | null> {
|
||||||
const householdSlug = requireSlug()
|
const householdSlug = requireSlug()
|
||||||
const { data, error, response } = await api.GET('/api/v1/households/{householdSlug}/shopping/{list_id}', { params: { path: { householdSlug, list_id: Number(id) } } })
|
const { data, error, response } = await api.GET('/api/v1/households/{householdSlug}/shopping/{list_id}', { params: { path: { householdSlug, list_id: Number(id) } } })
|
||||||
|
|
|
||||||
|
|
@ -570,15 +570,10 @@ export interface components {
|
||||||
/** Recipeid */
|
/** Recipeid */
|
||||||
recipeId?: number | null;
|
recipeId?: number | null;
|
||||||
};
|
};
|
||||||
/** InvitationResponse */
|
/** InviteLinkResponse */
|
||||||
InvitationResponse: {
|
InviteLinkResponse: {
|
||||||
/** Token */
|
/** Invite Link */
|
||||||
token: string;
|
invite_link: string;
|
||||||
/**
|
|
||||||
* Status
|
|
||||||
* @default pending
|
|
||||||
*/
|
|
||||||
status: string;
|
|
||||||
};
|
};
|
||||||
/** ListIngredientItem */
|
/** ListIngredientItem */
|
||||||
ListIngredientItem: {
|
ListIngredientItem: {
|
||||||
|
|
@ -1358,7 +1353,7 @@ export interface operations {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
content: {
|
content: {
|
||||||
"application/json": components["schemas"]["InvitationResponse"];
|
"application/json": components["schemas"]["InviteLinkResponse"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
403: components["responses"]["Problem403"];
|
403: components["responses"]["Problem403"];
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Legacy quick-login removed -->
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<p>This environment is configured without multi-tenancy enabled.</p>
|
<p>This environment is configured without multi-tenancy enabled.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -99,8 +98,6 @@ async function onGoogleLogin() {
|
||||||
alert(e instanceof Error ? e.message : 'Google login not available')
|
alert(e instanceof Error ? e.message : 'Google login not available')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// legacy login removed
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,6 @@ export async function loadUser() {
|
||||||
return user.value
|
return user.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy username login removed; use loginWithPassword instead.
|
|
||||||
|
|
||||||
export async function loginWithPassword(email: string, password: string) {
|
export async function loginWithPassword(email: string, password: string) {
|
||||||
user.value = await apiLoginWithPassword(email, password)
|
user.value = await apiLoginWithPassword(email, password)
|
||||||
return user.value
|
return user.value
|
||||||
|
|
|
||||||
|
|
@ -62,9 +62,6 @@ export function useShopping() {
|
||||||
unrequestMeal: sdk.unrequestMeal,
|
unrequestMeal: sdk.unrequestMeal,
|
||||||
requestIngredient: sdk.requestIngredient,
|
requestIngredient: sdk.requestIngredient,
|
||||||
unrequestIngredient: sdk.unrequestIngredient,
|
unrequestIngredient: sdk.unrequestIngredient,
|
||||||
getMyShoppingList: sdk.getMyShoppingList,
|
|
||||||
saveMyShoppingList: sdk.saveMyShoppingList,
|
|
||||||
// View-model helpers
|
|
||||||
groupsFrom,
|
groupsFrom,
|
||||||
mealsFrom,
|
mealsFrom,
|
||||||
async purchaseFromGroups(groups: Group[]) {
|
async purchaseFromGroups(groups: Group[]) {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,13 @@
|
||||||
>
|
>
|
||||||
Send Invitation
|
Send Invitation
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
:disabled="submitting"
|
||||||
|
@click="onCopyInviteLink"
|
||||||
|
>
|
||||||
|
Copy Invite Link
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<p
|
<p
|
||||||
v-if="message"
|
v-if="message"
|
||||||
|
|
@ -55,9 +62,10 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { sendInvitation } from '@/api/invitations'
|
import { sendInvitation, createInviteLink } from '@/api/invitations'
|
||||||
import { listMembers, type Member } from '@/api/households'
|
import { listMembers, type Member } from '@/api/households'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
import { useAlert } from '@/composables/useAlert'
|
||||||
|
|
||||||
const email = ref('')
|
const email = ref('')
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
|
|
@ -66,6 +74,7 @@ const error = ref('')
|
||||||
const members = ref<Member[]>([])
|
const members = ref<Member[]>([])
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const householdSlug = computed(() => (typeof route.params.householdSlug === 'string' ? route.params.householdSlug : null))
|
const householdSlug = computed(() => (typeof route.params.householdSlug === 'string' ? route.params.householdSlug : null))
|
||||||
|
const { show: showAlert, scheduleAutoDismiss } = useAlert()
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -91,6 +100,29 @@ async function onInvite() {
|
||||||
submitting.value = false
|
submitting.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function onCopyInviteLink() {
|
||||||
|
message.value = ''
|
||||||
|
error.value = ''
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
const slug = householdSlug.value
|
||||||
|
if (!slug) throw new Error('No household selected')
|
||||||
|
const emailValue = email.value.trim()
|
||||||
|
if (!emailValue) throw new Error('Please enter an email address')
|
||||||
|
|
||||||
|
const inviteLink = await createInviteLink(slug, emailValue)
|
||||||
|
await navigator.clipboard.writeText(inviteLink)
|
||||||
|
|
||||||
|
showAlert({ type: 'success', heading: 'Success', message: 'Invite link copied to clipboard!' })
|
||||||
|
scheduleAutoDismiss(3000)
|
||||||
|
email.value = ''
|
||||||
|
} catch (e) {
|
||||||
|
error.value = e instanceof Error ? e.message : 'Failed to copy invite link'
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue