diff --git a/src/components/FormModel.vue b/src/components/FormModel.vue
index 115145f37..a0f6bf479 100644
--- a/src/components/FormModel.vue
+++ b/src/components/FormModel.vue
@@ -100,7 +100,6 @@ const isResetting = ref(false);
 const hasChanges = ref(!$props.observeFormChanges);
 const originalData = ref({});
 const formData = computed(() => state.get(modelValue));
-const formUrl = computed(() => $props.url);
 const defaultButtons = computed(() => ({
     save: {
         color: 'primary',
@@ -148,11 +147,14 @@ if (!$props.url)
         (val) => updateAndEmit('onFetch', val)
     );
 
-watch(formUrl, async () => {
-    originalData.value = null;
-    reset();
-    await fetch();
-});
+watch(
+    () => [$props.url, $props.filter],
+    async () => {
+        originalData.value = null;
+        reset();
+        await fetch();
+    }
+);
 
 onBeforeRouteLeave((to, from, next) => {
     if (hasChanges.value && $props.observeFormChanges)
diff --git a/src/pages/Customer/Card/CustomerWebAccess.vue b/src/pages/Customer/Card/CustomerWebAccess.vue
index 33659dd77..8d025a365 100644
--- a/src/pages/Customer/Card/CustomerWebAccess.vue
+++ b/src/pages/Customer/Card/CustomerWebAccess.vue
@@ -2,164 +2,81 @@
 import { computed, ref } from 'vue';
 import { useI18n } from 'vue-i18n';
 import { useRoute } from 'vue-router';
-
 import axios from 'axios';
 import { useQuasar } from 'quasar';
 
-import { useValidator } from 'src/composables/useValidator';
-import useNotify from 'src/composables/useNotify';
-import { useStateStore } from 'stores/useStateStore';
-
-import FetchData from 'components/FetchData.vue';
 import VnInput from 'src/components/common/VnInput.vue';
 import CustomerChangePassword from 'src/pages/Customer/components/CustomerChangePassword.vue';
+import FormModel from 'components/FormModel.vue';
 
-const { notify } = useNotify();
 const { t } = useI18n();
-const { validate } = useValidator();
 const quasar = useQuasar();
 const route = useRoute();
-const stateStore = useStateStore();
-
-const active = ref(false);
 const canChangePassword = ref(0);
-const email = ref(null);
-const isLoading = ref(false);
-const name = ref(null);
-const usersPreviewRef = ref(null);
-const user = ref([]);
 
-const dataChanges = computed(() => {
-    return (
-        user.value.active !== active.value ||
-        user.value.email !== email.value ||
-        user.value.name !== name.value
-    );
+const filter = computed(() => {
+    return {
+        fields: ['active', 'email', 'name'],
+        where: { id: route.params.id },
+    };
 });
 
-const filter = { where: { id: `${route.params.id}` } };
-
 const showChangePasswordDialog = () => {
     quasar.dialog({
         component: CustomerChangePassword,
         componentProps: {
             id: route.params.id,
-            promise: usersPreviewRef.value.fetch(),
         },
     });
 };
 
-const setInitialData = () => {
-    if (user.value.length) {
-        active.value = user.value[0].active;
-        email.value = user.value[0].email;
-        name.value = user.value[0].name;
-    }
-};
-
-const onSubmit = async () => {
-    isLoading.value = true;
-
-    const payload = {
-        active: active.value,
-        email: email.value,
-        name: name.value,
-    };
-    try {
-        await axios.patch(`Clients/${route.params.id}/updateUser`, payload);
-        notify('globals.dataSaved', 'positive');
-        if (usersPreviewRef.value) usersPreviewRef.value.fetch();
-    } catch (error) {
-        notify('errors.create', 'negative');
-    } finally {
-        isLoading.value = false;
-    }
-};
+async function hasCustomerRole() {
+    const { data } = await axios(`Clients/${route.params.id}/hasCustomerRole`);
+    canChangePassword.value = data;
+}
 </script>
 
 <template>
-    <FetchData
+    <FormModel
+        url="VnUsers/preview"
+        :url-update="`Clients/${route.params.id}/updateUser`"
         :filter="filter"
-        @on-fetch="
-            (data) => {
-                user = data;
-                setInitialData();
+        model="webAccess"
+        :mapper="
+            ({ active, name, email }) => {
+                return {
+                    active,
+                    name,
+                    email,
+                };
             }
         "
+        @on-fetch="hasCustomerRole()"
         auto-load
-        ref="usersPreviewRef"
-        url="VnUsers/preview"
-    />
-    <FetchData
-        :url="`Clients/${route.params.id}/hasCustomerRole`"
-        @on-fetch="(data) => (canChangePassword = data)"
-        auto-load
-    />
-
-    <Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
-        <QBtnGroup push class="q-gutter-x-sm">
-            <QBtn
-                :disabled="isLoading"
-                :label="t('globals.reset')"
-                :loading="isLoading"
-                @click="setInitialData"
-                color="primary"
-                flat
-                icon="restart_alt"
-                type="reset"
+    >
+        <template #form="{ data, validate }">
+            <QCheckbox :label="t('Enable web access')" v-model="data.active" />
+            <VnInput :label="t('User')" clearable v-model="data.name" />
+            <VnInput
+                :label="t('Recovery email')"
+                :rules="validate('client.email')"
+                clearable
+                type="email"
+                v-model="data.email"
+                class="q-mt-sm"
+                :info="t('This email is used for user to regain access their account')"
             />
+        </template>
+        <template #moreActions>
             <QBtn
-                :disabled="isLoading"
                 :label="t('Change password')"
-                :loading="isLoading"
-                @click.stop="showChangePasswordDialog()"
                 color="primary"
-                flat
                 icon="edit"
-                v-if="canChangePassword"
+                :disable="!canChangePassword"
+                @click="showChangePasswordDialog()"
             />
-            <QBtn
-                :disabled="isLoading || !dataChanges"
-                :label="t('globals.save')"
-                :loading="isLoading"
-                @click="onSubmit"
-                color="primary"
-                icon="save"
-            />
-        </QBtnGroup>
-    </Teleport>
-
-    <div class="full-width flex justify-center">
-        <QCard class="card-width q-pa-lg">
-            <QCardSection>
-                <QForm>
-                    <QCheckbox :label="t('Enable web access')" v-model="active" />
-
-                    <div class="q-px-sm">
-                        <VnInput :label="t('User')" clearable v-model="name" />
-                        <VnInput
-                            :label="t('Recovery email')"
-                            :rules="validate('client.email')"
-                            clearable
-                            type="email"
-                            v-model="email"
-                            class="q-mt-sm"
-                        >
-                            <template #append>
-                                <QIcon name="info" class="cursor-pointer">
-                                    <QTooltip>{{
-                                        t(
-                                            'This email is used for user to regain access their account'
-                                        )
-                                    }}</QTooltip>
-                                </QIcon>
-                            </template>
-                        </VnInput>
-                    </div>
-                </QForm>
-            </QCardSection>
-        </QCard>
-    </div>
+        </template>
+    </FormModel>
 </template>
 
 <i18n>