Skip to content

Commit 05f552b

Browse files
committed
Refactor: Simplify the process to delete access url
Integrate notification composable. Replace hardcoded URL generation with Symfony router. Update accessurlService methods for improved readability.
1 parent b69704d commit 05f552b

4 files changed

Lines changed: 80 additions & 101 deletions

File tree

assets/vue/services/accessurlService.js

Lines changed: 17 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -11,53 +11,27 @@ export async function findUserActivePortals(userIri) {
1111
return items
1212
}
1313

14-
async function getUrl(id) {
15-
const apiUrl = `/api/access_urls/${encodeURIComponent(id)}`;
16-
try {
17-
const resp = await fetch(apiUrl, {
18-
credentials: "same-origin",
19-
headers: { Accept: "application/json" },
20-
});
21-
if (resp.ok) {
22-
const contentType = resp.headers.get("content-type") || "";
23-
return contentType.includes("application/json") ? resp.json() : { url: await resp.text() };
24-
}
25-
} catch (err) {
26-
}
27-
}
28-
29-
async function deleteAccessUrl(id, confirmValue, secToken = "") {
30-
const url = `/api/access_urls/${encodeURIComponent(id)}`
31-
const resp = await fetch(url, {
32-
method: "DELETE",
33-
credentials: "same-origin",
34-
headers: {
35-
Accept: "application/json",
36-
"Content-Type": "application/json",
37-
...(secToken ? { "X-CSRF-Token": secToken } : {}),
38-
},
39-
body: JSON.stringify({ confirm_value: String(confirmValue) }),
40-
})
41-
42-
if (!resp.ok) {
43-
const txt = await resp.text()
44-
throw new Error(txt || "Delete failed")
45-
}
14+
export async function findAll() {
15+
const { items } = await baseService.getCollection("/api/access_urls")
4616

47-
const contentType = resp.headers.get("content-type") || ""
48-
if (contentType.includes("application/json")) {
49-
return resp.json()
50-
}
51-
return { message: await resp.text(), redirectUrl: "/main/admin/access_urls.php" }
17+
return items
5218
}
5319

54-
export default {
55-
deleteAccessUrl,
56-
getUrl,
20+
/**
21+
* @param {number} id
22+
* @returns {Promise<Object>}
23+
*/
24+
export async function findById(id) {
25+
return await baseService.get(`/api/access_urls/${id}`)
5726
}
5827

59-
export async function findAll() {
60-
const { items } = await baseService.getCollection("/api/access_urls")
28+
/**
29+
* @param {number} id
30+
* @param {string} [secToken]
31+
* @returns {Promise<Object>}
32+
*/
33+
export async function deleteById(id, secToken = "") {
34+
await baseService.delete(`/api/access_urls/${id}`)
6135

62-
return items
36+
return { redirectUrl: "/main/admin/access_urls.php" }
6337
}

assets/vue/views/accessurl/DeleteAccessUrl.vue

Lines changed: 50 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,69 @@
11
<template>
22
<div class="space-y-6">
3-
<Message severity="warn" class="inline-block max-w-max">{{ t("Danger zone: deleting an access URL is permanent.")}}</Message>
4-
<section class="p-0">
5-
<h3 class="mb-2 text-sm font-semibold text-rose-900">{{ t("Confirm deletion") }}</h3>
6-
<p class="mb-3 text-sm text-rose-800">
7-
{{ t("Type the full URL to confirm. The URL and its relations will be permanently removed.") }}
3+
<SectionHeader :title="t('Confirm deletion')" />
4+
<Message
5+
severity="warn"
6+
class="inline-block max-w-max"
7+
>
8+
{{ t("Danger zone: deleting an access URL is permanent.") }}
9+
</Message>
10+
<section>
11+
<p class="mb-3">
12+
{{ t("The value must match exactly: %s", [urlText]) }}
813
</p>
9-
<div class="grid grid-cols-1 gap-4 md:grid-cols-3 items-center">
10-
<div class="md:col-span-2">
11-
<label class="mb-1 block text-xs font-medium text-rose-900">{{ t("URL") }}</label>
12-
<BaseInputText
13-
v-model="confirmText"
14-
class="w-full"
15-
:placeholder="urlText || 'https://example.com'"
16-
/>
17-
<p v-if="confirmText && !canDelete" class="mt-1 text-xs text-rose-700">
18-
{{ t("The value must match exactly:") }} <strong>{{ urlText }}</strong>
19-
</p>
20-
</div>
21-
<div class="flex items-center md:justify-end md:col-span-1 md:pr-8">
22-
<div class="hidden md:block">
23-
<BaseButton
24-
:label="t('Delete URL')"
25-
icon="delete"
26-
type="danger"
27-
:disabled="loading || !canDelete"
28-
:isLoading="loading"
29-
@click="openConfirmDialog"
30-
/>
31-
</div>
32-
</div>
33-
</div>
14+
15+
<BaseInputText
16+
id="confirm-text"
17+
v-model="confirmText"
18+
:label="t('URL')"
19+
:placeholder="urlText || 'https://example.com'"
20+
class="w-full"
21+
:help-text="t('Type the full URL to confirm. The URL and its relations will be permanently removed.')"
22+
/>
23+
24+
<BaseButton
25+
:disabled="loading || !canDelete"
26+
:is-loading="loading"
27+
:label="t('Delete URL')"
28+
icon="delete"
29+
type="danger"
30+
@click="confirmDialogVisible = true"
31+
/>
3432
</section>
33+
3534
<BaseDialogConfirmCancel
3635
v-model:isVisible="confirmDialogVisible"
36+
:cancel-label="t('Cancel')"
37+
:confirm-label="t('Delete')"
3738
:title="t('Confirm deletion')"
38-
:confirmLabel="t('Delete')"
39-
:cancelLabel="t('Cancel')"
40-
@confirmClicked="confirmSubmit"
39+
@confirm-clicked="confirmSubmit"
40+
@cancel-clicked="confirmDialogVisible = false"
4141
/>
4242
</div>
4343
</template>
4444

45-
<script setup >
45+
<script setup>
4646
import { ref, computed, onMounted } from "vue"
4747
import { useI18n } from "vue-i18n"
48-
import { useRoute, useRouter } from "vue-router"
49-
import svc from "../../services/accessurlService"
48+
import { useRoute } from "vue-router"
49+
import { findById, deleteById } from "../../services/accessurlService"
5050
import Message from "primevue/message"
5151
5252
import BaseInputText from "../../components/basecomponents/BaseInputText.vue"
5353
import BaseButton from "../../components/basecomponents/BaseButton.vue"
5454
import BaseDialogConfirmCancel from "../../components/basecomponents/BaseDialogConfirmCancel.vue"
55+
import SectionHeader from "../../components/layout/SectionHeader.vue"
56+
import { useNotification } from "../../composables/notification"
5557
5658
const { t } = useI18n()
5759
const route = useRoute()
58-
const router = useRouter()
60+
61+
const notification = useNotification()
62+
5963
const urlId = Number(route.params.id || 0)
6064
const urlText = ref(route.query.url || "")
6165
const confirmText = ref("")
6266
const loading = ref(false)
63-
const error = ref("")
6467
const notice = ref("")
6568
const confirmDialogVisible = ref(false)
6669
@@ -69,34 +72,27 @@ const canDelete = computed(() => !!confirmText.value && confirmText.value === ur
6972
onMounted(async () => {
7073
if (!urlText.value && urlId) {
7174
try {
72-
const data = await svc.getUrl(urlId)
75+
const data = await findById(urlId)
7376
urlText.value = data.url || ""
7477
} catch (e) {
75-
error.value = t("Unable to load URL data.")
78+
notification.showErrorNotification(e)
7679
}
7780
}
7881
})
7982
80-
function openConfirmDialog() {
81-
confirmDialogVisible.value = true
82-
}
83-
8483
async function confirmSubmit() {
8584
confirmDialogVisible.value = false
86-
error.value = ""
8785
notice.value = ""
8886
try {
8987
loading.value = true
90-
const secToken = (window.SEC_TOKEN || route.query.sec_token || "")
91-
const res = await svc.deleteAccessUrl(urlId, confirmText.value, secToken)
92-
notice.value = res.message || t("URL deleted successfully.")
93-
if (res.redirectUrl) {
94-
window.location.href = res.redirectUrl
95-
} else {
96-
router.push({ name: "AccessUrlsList" })
97-
}
88+
const secToken = window.SEC_TOKEN || route.query.sec_token || ""
89+
const res = await deleteById(urlId, secToken)
90+
91+
notification.showSuccessNotification(t("URL deleted successfully."))
92+
93+
window.location.href = res.redirectUrl
9894
} catch (e) {
99-
error.value = e?.message || (e?.response?.data?.error) || t("Failed to delete URL.")
95+
notification.showErrorNotification(e)
10096
} finally {
10197
loading.value = false
10298
}

public/main/admin/access_urls.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Chamilo\CoreBundle\Enums\ActionIcon;
1414
use Chamilo\CoreBundle\Enums\StateIcon;
1515
use Chamilo\CoreBundle\Framework\Container;
16+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
1617

1718
$cidReset = true;
1819
require_once __DIR__.'/../inc/global.inc.php';
@@ -214,10 +215,17 @@
214215

215216
if ($u->getId() !== 1) {
216217
// build a link to the Vue route that will open DeleteAccessUrl.vue
217-
$urlEncoded = rawurlencode($u->getUrl());
218-
$secTokenEncoded = rawurlencode($parameters['sec_token']);
219-
$vueHref = api_get_path(WEB_PATH) . 'resources/accessurl/' . $u->getId() . '/delete?url=' . $urlEncoded . '&sec_token=' . $secTokenEncoded;
220-
$rowActions .= '<a href="' . $vueHref . '">' .
218+
$vueHref = Container::getRouter()->generate(
219+
'access_url_delete',
220+
[
221+
'id' => $u->getId(),
222+
'url' => $u->getUrl(),
223+
'sec_token' => $parameters['sec_token'],
224+
],
225+
UrlGeneratorInterface::ABSOLUTE_URL
226+
);
227+
228+
$rowActions .= '<a href="'.$vueHref.'">' .
221229
Display::getMdiIcon('delete', 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Delete')) .
222230
'</a>';
223231
}

src/CoreBundle/Controller/IndexController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class IndexController extends BaseController
2727
#[Route('/catalogue/{slug}', name: 'catalogue', options: ['expose' => true], methods: ['GET', 'POST'])]
2828
#[Route('/resources/ccalendarevent', name: 'resources_ccalendarevent', methods: ['GET'])]
2929
#[Route('/resources/document/{nodeId}/manager', name: 'resources_filemanager', methods: ['GET'])]
30+
#[Route('/resources/accessurl/{id}/delete', name: 'access_url_delete', methods: ['GET'])]
3031
#[Route('/account/home', name: 'chamilo_core_account_home', options: ['expose' => true])]
3132
#[Route('/social', name: 'chamilo_core_socialnetwork', options: ['expose' => true])]
3233
#[Route('/social/{vueRouting}', name: 'chamilo_core_socialnetwork_vue_entrypoint', requirements: ['vueRouting' => '.+'], options: ['expose' => true])]

0 commit comments

Comments
 (0)