Files
InvoiceShelf/resources/scripts/features/company/modules/views/CompanyModulesIndexView.vue
Darko Gjorgjijoski f3ab0f22fc chore(frontend): fix ESLint, add Pint+ESLint pre-commit hook, centralize v-html
Fix the broken ESLint setup: add vue-eslint-parser and @typescript-eslint/parser
and wire the TS parser into eslint.config.mjs so .ts and <script lang=ts> parse
(was failing outright). Clear the resulting backlog to a clean 0/0 baseline —
fix genuine issues, relax two intentional-pattern rules (multi-word-component-names,
no-required-prop-with-default).

Add a committed .githooks/pre-commit (enabled via core.hooksPath, auto-set by the
prepare script) that runs Pint on staged PHP and ESLint --max-warnings 0 on staged
resources/scripts JS/TS/Vue, blocking on failure. Add composer/npm lint scripts and
document the gate in CLAUDE.md.

Replace every scattered v-html with a single audited BaseSanitizedHtml component
that DOMPurify-sanitizes its input (new utils/markdown.ts sanitizeHtml), so
server/registry-provided HTML is actually sanitized and vue/no-v-html stays enabled
everywhere but one reviewed sink.
2026-06-11 11:11:12 +02:00

75 lines
2.0 KiB
Vue

<template>
<BaseSettingCard
:title="$t('modules.title')"
:description="$t('modules.index.description')"
>
<ModuleSettingsModal />
<!-- Loading skeleton -->
<div
v-if="store.isFetching && store.modules.length === 0"
class="grid mt-8 w-full grid-cols-1 items-start gap-6 lg:grid-cols-2"
>
<div v-for="n in 3" :key="n" class="h-32 bg-surface-tertiary rounded-lg animate-pulse" />
</div>
<!-- Empty state -->
<div
v-else-if="store.modules.length === 0"
class="mt-16 flex flex-col items-center justify-center text-center"
>
<div
class="
h-16 w-16 rounded-full bg-surface-tertiary
flex items-center justify-center mb-4
">
<BaseIcon name="PuzzlePieceIcon" class="h-8 w-8 text-subtle" />
</div>
<h3 class="text-lg font-medium text-heading">
{{ $t('modules.index.empty_title') }}
</h3>
<p class="text-sm text-muted mt-2 max-w-md">
{{ $t('modules.index.empty_description') }}
</p>
</div>
<!-- Module list -->
<div
v-else
class="grid mt-8 w-full grid-cols-1 items-start gap-6 lg:grid-cols-2"
>
<CompanyModuleCard
v-for="mod in store.modules"
:key="mod.slug"
:data="mod"
@open-settings="openSettingsModal"
/>
</div>
</BaseSettingCard>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { useModalStore } from '@/scripts/stores/modal.store'
import { useCompanyModulesStore } from '../store'
import type { CompanyModuleSummary } from '../store'
import CompanyModuleCard from '../components/CompanyModuleCard.vue'
import ModuleSettingsModal from '../components/ModuleSettingsModal.vue'
const store = useCompanyModulesStore()
const modalStore = useModalStore()
onMounted(() => {
store.fetchModules()
})
function openSettingsModal(module: CompanyModuleSummary): void {
modalStore.openModal({
componentName: 'ModuleSettingsModal',
title: module.display_name,
data: module,
size: 'lg',
})
}
</script>