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.
|
||||
|
||||
- [ ] **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.
|
||||
- **File**: `src/views/HouseholdMembers.vue`
|
||||
- **File**: `src/views/HouseholdSettings.vue`
|
||||
- **Action**:
|
||||
1. The backend team will provide a new endpoint that, when called, returns a JSON object with an `invite_link`.
|
||||
2. Update the "Invite" button logic to call this new endpoint.
|
||||
3. On a successful response, use the browser's Clipboard API (`navigator.clipboard.writeText(response.invite_link)`) to copy the link.
|
||||
4. Display a confirmation toast to the user (e.g., "Invite link copied to clipboard!").
|
||||
- **Blocked By**: Backend API change.
|
||||
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.~~ ✅ 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.~~ ✅ Implemented in `onCopyInviteLink()` handler.
|
||||
4. ~~Display a confirmation toast to the user (e.g., "Invite link copied to clipboard!").~~ ✅ Using `useAlert()` composable to show success toast.
|
||||
- **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.
|
||||
- **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.
|
||||
- **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> {
|
||||
const res = await api.POST('/api/v1/auth/login', { body: { email, password } })
|
||||
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'}`)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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> {
|
||||
const householdSlug = requireSlug()
|
||||
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?: number | null;
|
||||
};
|
||||
/** InvitationResponse */
|
||||
InvitationResponse: {
|
||||
/** Token */
|
||||
token: string;
|
||||
/**
|
||||
* Status
|
||||
* @default pending
|
||||
*/
|
||||
status: string;
|
||||
/** InviteLinkResponse */
|
||||
InviteLinkResponse: {
|
||||
/** Invite Link */
|
||||
invite_link: string;
|
||||
};
|
||||
/** ListIngredientItem */
|
||||
ListIngredientItem: {
|
||||
|
|
@ -1358,7 +1353,7 @@ export interface operations {
|
|||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["InvitationResponse"];
|
||||
"application/json": components["schemas"]["InviteLinkResponse"];
|
||||
};
|
||||
};
|
||||
403: components["responses"]["Problem403"];
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@
|
|||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Legacy quick-login removed -->
|
||||
<div v-else>
|
||||
<p>This environment is configured without multi-tenancy enabled.</p>
|
||||
</div>
|
||||
|
|
@ -99,8 +98,6 @@ async function onGoogleLogin() {
|
|||
alert(e instanceof Error ? e.message : 'Google login not available')
|
||||
}
|
||||
}
|
||||
|
||||
// legacy login removed
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ export async function loadUser() {
|
|||
return user.value
|
||||
}
|
||||
|
||||
// Legacy username login removed; use loginWithPassword instead.
|
||||
|
||||
export async function loginWithPassword(email: string, password: string) {
|
||||
user.value = await apiLoginWithPassword(email, password)
|
||||
return user.value
|
||||
|
|
|
|||
|
|
@ -62,9 +62,6 @@ export function useShopping() {
|
|||
unrequestMeal: sdk.unrequestMeal,
|
||||
requestIngredient: sdk.requestIngredient,
|
||||
unrequestIngredient: sdk.unrequestIngredient,
|
||||
getMyShoppingList: sdk.getMyShoppingList,
|
||||
saveMyShoppingList: sdk.saveMyShoppingList,
|
||||
// View-model helpers
|
||||
groupsFrom,
|
||||
mealsFrom,
|
||||
async purchaseFromGroups(groups: Group[]) {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,13 @@
|
|||
>
|
||||
Send Invitation
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:disabled="submitting"
|
||||
@click="onCopyInviteLink"
|
||||
>
|
||||
Copy Invite Link
|
||||
</button>
|
||||
</form>
|
||||
<p
|
||||
v-if="message"
|
||||
|
|
@ -55,9 +62,10 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
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 { useRoute } from 'vue-router'
|
||||
import { useAlert } from '@/composables/useAlert'
|
||||
|
||||
const email = ref('')
|
||||
const submitting = ref(false)
|
||||
|
|
@ -66,6 +74,7 @@ const error = ref('')
|
|||
const members = ref<Member[]>([])
|
||||
const route = useRoute()
|
||||
const householdSlug = computed(() => (typeof route.params.householdSlug === 'string' ? route.params.householdSlug : null))
|
||||
const { show: showAlert, scheduleAutoDismiss } = useAlert()
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
|
|
@ -91,6 +100,29 @@ async function onInvite() {
|
|||
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>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
Loading…
Reference in a new issue