Skip to content

Commit f18ebe6

Browse files
consolidate DTO types
1 parent 2624b53 commit f18ebe6

File tree

2 files changed

+102
-167
lines changed

2 files changed

+102
-167
lines changed

frontend/src/components/authority/GroupEditCreate.vue

Lines changed: 34 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<!-- Edit/Create page -->
88
<div v-else>
9-
<BreadcrumbNav v-if="props.mode === 'EDIT'" :crumbs="[ { label: t('nav.groups'), to: '/app/groups' }, { label: name, to:'/app/groups/' + props.id }, { label: t('common.edit') } ]"/>
9+
<BreadcrumbNav v-if="props.mode === 'EDIT'" :crumbs="[ { label: t('nav.groups'), to: '/app/groups' }, { label: data.name, to:'/app/groups/' + props.id }, { label: t('common.edit') } ]"/>
1010
<BreadcrumbNav v-else :crumbs="[ { label: t('nav.groups'), to: '/app/groups' }, { label: t('common.create') } ]"/>
1111
<div class="-my-2 -mx-4 sm:-mx-6 lg:-mx-8 overflow-hidden">
1212
<div class="py-2 align-middle inline-block min-w-full px-4 sm:px-6 lg:px-8">
@@ -21,7 +21,7 @@
2121
<!-- Profile Picture Preview -->
2222
<div class="flex flex-col items-center gap-4 mb-8">
2323
<div class="relative w-32 h-32">
24-
<img v-if="isValidImageUrl" :src="pictureUrl" class="w-full h-full rounded-full object-cover border border-gray-300" :alt="t('groupEditCreate.profilePicture')" />
24+
<img v-if="isValidImageUrl" :src="data.pictureUrl" class="w-full h-full rounded-full object-cover border border-gray-300" :alt="t('groupEditCreate.profilePicture')" />
2525
<div v-else class="w-full h-full rounded-full bg-gray-100 flex items-center justify-center text-gray-400">
2626
<UserGroupIcon class="w-12 h-12" />
2727
</div>
@@ -37,8 +37,8 @@
3737
</label>
3838
<div class="mt-1 md:mt-0 md:col-span-2 lg:col-span-1">
3939
<div class="relative">
40-
<input id="pictureUrl" v-model="pictureUrl" type="url" :class="[errors.pictureUrl ? 'border-red-300 focus:border-red-500 focus:ring-red-500' : 'border-gray-300 focus:ring-primary focus:border-primary', 'block w-full max-w-md shadow-sm sm:text-sm rounded-md pr-10']"/>
41-
<button v-if="pictureUrl" type="button" class="absolute inset-y-0 right-0 flex items-center px-3 text-gray-400 hover:text-gray-600 focus:outline-none" :aria-label="t('groupEditCreate.removePicture')" @click="removePicture">
40+
<input id="pictureUrl" v-model="data.pictureUrl" type="url" :class="[errors.pictureUrl ? 'border-red-300 focus:border-red-500 focus:ring-red-500' : 'border-gray-300 focus:ring-primary focus:border-primary', 'block w-full max-w-md shadow-sm sm:text-sm rounded-md pr-10']"/>
41+
<button v-if="data.pictureUrl" type="button" class="absolute inset-y-0 right-0 flex items-center px-3 text-gray-400 hover:text-gray-600 focus:outline-none" :aria-label="t('groupEditCreate.removePicture')" @click="removePicture">
4242
<TrashIcon class="w-5 h-5 text-gray-600" />
4343
</button>
4444
</div>
@@ -52,7 +52,7 @@
5252
{{ t('groupEditCreate.name') }}
5353
</label>
5454
<div class="mt-1 md:mt-0 md:col-span-2 lg:col-span-1">
55-
<input id="name" v-model="name" type="text" :class="[errors.name ? 'border-red-300 focus:border-red-500 focus:ring-red-500' : 'border-gray-300 focus:ring-primary focus:border-primary', 'block w-full max-w-md shadow-sm sm:text-sm rounded-md']" required />
55+
<input id="name" v-model="data.name" type="text" :class="[errors.name ? 'border-red-300 focus:border-red-500 focus:ring-red-500' : 'border-gray-300 focus:ring-primary focus:border-primary', 'block w-full max-w-md shadow-sm sm:text-sm rounded-md']" required />
5656
<p v-if="errors.name" class="mt-1 text-sm text-red-600">{{ errors.name }}</p>
5757
</div>
5858
</div>
@@ -91,10 +91,10 @@
9191

9292
<script setup lang="ts">
9393
import { ExclamationTriangleIcon, TrashIcon, UserGroupIcon } from '@heroicons/vue/24/outline';
94-
import { computed, onMounted, ref, watch } from 'vue';
94+
import { computed, onMounted, reactive, ref, shallowRef, watch } from 'vue';
9595
import { useI18n } from 'vue-i18n';
9696
import { useRouter } from 'vue-router';
97-
import backend, { isAxiosError } from '../../common/backend';
97+
import backend, { GroupDto, isAxiosError } from '../../common/backend';
9898
import { FormValidator } from '../../common/formvalidator';
9999
import { debounce } from '../../common/util';
100100
import BreadcrumbNav from '../BreadcrumbNav.vue';
@@ -107,77 +107,65 @@ const props = defineProps<{
107107
mode: 'EDIT',
108108
}>();
109109
110-
interface GroupData {
111-
id?: string;
112-
name: string;
113-
pictureUrl?: string;
114-
}
115-
116-
const initialGroupData = ref<GroupData>({ name: '', pictureUrl: '' });
110+
type EditableGroupData = Pick<GroupDto, 'name' | 'pictureUrl'>;
111+
const initialData = shallowRef<EditableGroupData>({ name: '', pictureUrl: '' });
112+
const data = reactive<EditableGroupData>(initialData.value);
117113
118114
const groupDataHasUnsavedChanges = computed(() => {
119-
return (
120-
initialGroupData.value.name !== name.value ||
121-
initialGroupData.value.pictureUrl !== pictureUrl.value
122-
);
115+
return data.name !== initialData.value.name
116+
|| data.pictureUrl !== initialData.value.pictureUrl;
123117
});
124118
125119
function resetGroupData() {
126-
name.value = initialGroupData.value.name;
127-
pictureUrl.value = initialGroupData.value.pictureUrl ?? '';
120+
data.name = initialData.value.name;
121+
data.pictureUrl = initialData.value.pictureUrl;
128122
}
129123
130124
const { t } = useI18n({ useScope: 'global' });
131125
const router = useRouter();
132-
133126
const loading = ref(true);
134-
const name = ref<string>('');
135-
136127
const errors = ref<Record<string, string>>({});
137128
const processing = ref(false);
138129
const groupSaved = ref(false);
139130
const onSaveError = ref<Error | null>(null);
140131
const debouncedGroupSaved = debounce(() => groupSaved.value = false, 2000);
141-
142-
const pictureUrl = ref<string>('');
143132
const isValidImageUrl = ref<boolean>(false);
144133
145-
watch(pictureUrl,
134+
watch(() => data.pictureUrl,
146135
async (newUrl) => {
147136
isValidImageUrl.value = await FormValidator.validateImageUrl(newUrl);
148137
},
149138
{ immediate: true }
150139
);
151140
152141
onMounted(async () => {
142+
loading.value = true;
153143
if (props.mode === 'EDIT') {
154144
try {
155-
const group = await backend.groups.getGroup(props.id);
156-
name.value = group.name;
157-
pictureUrl.value = group.pictureUrl ?? '';
158-
initialGroupData.value = {
159-
id: group.id,
160-
name: group.name,
161-
pictureUrl: group.pictureUrl ?? ''
162-
};
145+
initialData.value = await backend.groups.getGroup(props.id);
163146
} catch (error) {
164147
console.error('Failed to fetch group:', error);
148+
} finally {
149+
loading.value = false;
165150
}
166151
} else {
167-
name.value = '';
168-
pictureUrl.value = '';
152+
initialData.value = {
153+
name: '',
154+
pictureUrl: ''
155+
};
156+
loading.value = false;
169157
}
170-
loading.value = false;
158+
resetGroupData();
171159
});
172160
173161
function removePicture() {
174-
pictureUrl.value = '';
162+
data.pictureUrl = undefined;
175163
}
176164
177165
function validateForm() {
178166
const result = FormValidator.validateGroup({
179-
name: name.value,
180-
pictureUrl: pictureUrl.value,
167+
name: data.name,
168+
pictureUrl: data.pictureUrl,
181169
isValidImageUrl: isValidImageUrl.value
182170
});
183171
@@ -193,27 +181,20 @@ async function onSubmit() {
193181
processing.value = true;
194182
onSaveError.value = null;
195183
196-
const trimmedName = name.value.trim();
184+
data.name = data.name.trim();
197185
198186
try {
199187
if (props.mode === 'EDIT') {
200-
await backend.groups.updateGroup(props.id, {
201-
name: trimmedName,
202-
pictureUrl: pictureUrl.value || undefined
203-
});
188+
await backend.groups.updateGroup(props.id, data);
204189
} else {
205-
await backend.groups.createGroup({
206-
name: trimmedName,
207-
pictureUrl: pictureUrl.value || undefined
208-
});
190+
await backend.groups.createGroup(data);
209191
}
210192
211193
// Update initial data to match saved state
212-
initialGroupData.value = {
213-
name: trimmedName,
214-
pictureUrl: pictureUrl.value
194+
initialData.value = {
195+
name: data.name,
196+
pictureUrl: data.pictureUrl
215197
};
216-
name.value = trimmedName;
217198
218199
// Show saved success state
219200
groupSaved.value = true;

0 commit comments

Comments
 (0)