Files
vmall/apps/mall/pages/user/addresses.vue
T

221 lines
6.1 KiB
Vue

<script setup lang="ts">
import type { AddressBookEntry, AddressInput } from "@vmall/shared";
definePageMeta({ middleware: "auth" });
const { t } = useI18n();
const { $api } = useNuxtApp();
const rows = ref<AddressBookEntry[]>([]);
const loading = ref(true);
const open = ref(false);
const saving = ref(false);
const editingId = ref<string | null>(null);
interface AddressForm {
recipient: string;
phone: string;
country: string;
region: string;
city: string;
line1: string;
postal_code: string;
is_default: boolean;
}
const form = reactive<AddressForm>({
recipient: "",
phone: "",
country: "US",
region: "",
city: "",
line1: "",
postal_code: "",
is_default: false,
});
const errorKey = ref("");
const modalTitle = computed(() => (editingId.value ? t("user.editAddress") : t("user.addAddress")));
async function load(): Promise<void> {
loading.value = true;
errorKey.value = "";
try {
rows.value = await $api.listMyAddresses();
} catch {
errorKey.value = "user.loadFailed";
} finally {
loading.value = false;
}
}
onMounted(() => void load());
function resetForm(): void {
form.recipient = "";
form.phone = "";
form.country = "US";
form.region = "";
form.city = "";
form.line1 = "";
form.postal_code = "";
form.is_default = false;
errorKey.value = "";
}
function openAdd(): void {
editingId.value = null;
resetForm();
open.value = true;
}
function openEdit(row: AddressBookEntry): void {
editingId.value = row.id;
form.recipient = row.recipient;
form.phone = row.phone;
form.country = row.country;
form.region = row.region;
form.city = row.city;
form.line1 = row.line1;
form.postal_code = row.postal_code;
form.is_default = row.is_default;
errorKey.value = "";
open.value = true;
}
async function setDefault(id: string): Promise<void> {
try {
await $api.setDefaultAddress(id);
await load();
} catch {
errorKey.value = "user.saveFailed";
}
}
async function remove(row: AddressBookEntry): Promise<void> {
try {
rows.value = await $api.deleteAddress(row.id);
} catch {
errorKey.value = "user.saveFailed";
}
}
async function save(): Promise<void> {
if (
!form.recipient ||
!form.phone ||
!form.country ||
!form.region ||
!form.city ||
!form.line1 ||
!form.postal_code
) {
errorKey.value = "user.validationRequired";
return;
}
const input: AddressInput = {
recipient: form.recipient,
phone: form.phone,
country: form.country,
region: form.region,
city: form.city,
line1: form.line1,
postal_code: form.postal_code,
is_default: form.is_default,
};
saving.value = true;
errorKey.value = "";
try {
if (editingId.value) {
await $api.updateAddress(editingId.value, input);
} else {
await $api.createAddress(input);
}
open.value = false;
await load();
} catch {
errorKey.value = "user.saveFailed";
} finally {
saving.value = false;
}
}
</script>
<template>
<VCard class="min-h-[560px]">
<div class="mb-4 flex items-center justify-between gap-3">
<h1 class="text-text m-0 text-xl font-bold">{{ t("user.addresses") }}</h1>
<VBtn variant="primary" size="sm" type="button" @click="openAdd">{{
t("user.addAddress")
}}</VBtn>
</div>
<UiEmptyState v-if="!loading && rows.length === 0" :text="t('user.noAddresses')" />
<VTable v-else>
<thead>
<tr>
<th>{{ t("user.recipient") }}</th>
<th>{{ t("user.phone") }}</th>
<th>{{ t("user.country") }}</th>
<th>{{ t("user.region") }}</th>
<th>{{ t("user.city") }}</th>
<th>{{ t("user.line1") }}</th>
<th>{{ t("user.postalCode") }}</th>
<th>{{ t("common.status") }}</th>
<th>{{ t("common.actions") }}</th>
</tr>
</thead>
<tbody>
<tr v-for="row in rows" :key="row.id">
<td>{{ row.recipient }}</td>
<td>{{ row.phone }}</td>
<td>{{ row.country }}</td>
<td>{{ row.region }}</td>
<td>{{ row.city }}</td>
<td>{{ row.line1 }}</td>
<td>{{ row.postal_code }}</td>
<td>
<span v-if="row.is_default" class="text-primary text-xs">{{
t("user.defaultAddress")
}}</span>
<VBtn v-else size="sm" type="button" @click="setDefault(row.id)">{{
t("user.setDefault")
}}</VBtn>
</td>
<td class="space-x-2 whitespace-nowrap">
<VBtn size="sm" type="button" @click="openEdit(row)">{{ t("common.edit") }}</VBtn>
<VBtn size="sm" variant="danger" type="button" @click="remove(row)">{{
t("common.delete")
}}</VBtn>
</td>
</tr>
</tbody>
</VTable>
<UiModal :open="open" :title="modalTitle" @close="open = false">
<div class="grid gap-3 sm:grid-cols-2">
<VField :label="t('user.recipient')"><VInput v-model="form.recipient" /></VField>
<VField :label="t('user.phone')"><VInput v-model="form.phone" /></VField>
<VField :label="t('user.country')"><VInput v-model="form.country" /></VField>
<VField :label="t('user.region')"><VInput v-model="form.region" /></VField>
<VField :label="t('user.city')"><VInput v-model="form.city" /></VField>
<VField class="sm:col-span-2" :label="t('user.line1')"
><VInput v-model="form.line1"
/></VField>
<VField :label="t('user.postalCode')"><VInput v-model="form.postal_code" /></VField>
<label class="text-text flex items-center gap-2 self-end pb-3 text-sm"
><input v-model="form.is_default" class="accent-primary h-4 w-4" type="checkbox" />
<span>{{ t("user.defaultAddress") }}</span></label
>
</div>
<p v-if="errorKey" class="text-danger mt-2 text-sm">{{ t(errorKey) }}</p>
<template #footer>
<VBtn type="button" @click="open = false">{{ t("common.cancel") }}</VBtn>
<VBtn variant="primary" type="button" :disabled="saving" @click="save">{{
t("user.saveAddress")
}}</VBtn>
</template>
</UiModal>
</VCard>
</template>