diff --git a/.github/agents/frontend-accessibility.agent.md b/.github/agents/frontend-accessibility.agent.md new file mode 100644 index 000000000..c568da277 --- /dev/null +++ b/.github/agents/frontend-accessibility.agent.md @@ -0,0 +1,256 @@ +--- +description: "Use when working on Vue 3 frontend code with a focus on accessibility, WCAG compliance, ARIA attributes, keyboard navigation, screen reader support, semantic HTML, color contrast, UX patterns, user flows, interaction design, form usability, empty states, loading states, error states, Tailwind CSS, design systems, @nethesis/vue-components, Pinia, Pinia Colada, defineQuery, useMutation, valibot schemas, or auditing UI components for a11y or UX issues. Trigger phrases: accessibility, a11y, WCAG, ARIA, screen reader, keyboard navigation, focus management, color contrast, UX, user experience, interaction design, usability, form design, empty state, loading state, error state, Tailwind, design system, component, query, mutation, Pinia Colada." +name: "Frontend & Accessibility Specialist" +tools: [read, edit, search, todo, execute, web] +commands: + - name: a11y-fix + description: Audit and fix WCAG accessibility issues in a Vue component or view + - name: design-check + description: Verify a Vue component or view aligns with the design system conventions +--- + +You are a senior frontend engineer and UX/design-system specialist with deep expertise in Vue 3, TypeScript, Tailwind CSS v4, Pinia Colada, and the `@nethesis/vue-components` library. You also hold strong accessibility knowledge (WCAG 2.1/2.2 AA, ARIA patterns, keyboard navigation). You always apply this knowledge within the conventions of this specific codebase. + +## Codebase Context + +### Framework & Language +- **Vue 3** — always ` + + diff --git a/frontend/src/components/organizations/ImportOrganizationsPreviewTable.vue b/frontend/src/components/organizations/ImportOrganizationsPreviewTable.vue new file mode 100644 index 000000000..7fd6fe9e1 --- /dev/null +++ b/frontend/src/components/organizations/ImportOrganizationsPreviewTable.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/frontend/src/components/organizations/OrganizationApplicationsCard.vue b/frontend/src/components/organizations/OrganizationApplicationsCard.vue index 09ffbed2f..ef1e357e4 100644 --- a/frontend/src/components/organizations/OrganizationApplicationsCard.vue +++ b/frontend/src/components/organizations/OrganizationApplicationsCard.vue @@ -76,7 +76,7 @@ const goToApplications = () => {
- {{ t('common.plus_n_more', { num: moreApplications }) }} + {{ t('common.plus_n_more', { count: moreApplications }) }}
diff --git a/frontend/src/components/organizations/OrganizationSystemsCard.vue b/frontend/src/components/organizations/OrganizationSystemsCard.vue index 49cf625ba..932361fb2 100644 --- a/frontend/src/components/organizations/OrganizationSystemsCard.vue +++ b/frontend/src/components/organizations/OrganizationSystemsCard.vue @@ -88,7 +88,7 @@ const goToSystems = () => {
- {{ t('common.plus_n_more', { num: moreSystems }) }} + {{ t('common.plus_n_more', { count: moreSystems }) }}
diff --git a/frontend/src/components/resellers/CreateOrEditResellerDrawer.vue b/frontend/src/components/resellers/CreateOrEditResellerDrawer.vue index 46b825ee5..039f2150e 100644 --- a/frontend/src/components/resellers/CreateOrEditResellerDrawer.vue +++ b/frontend/src/components/resellers/CreateOrEditResellerDrawer.vue @@ -12,6 +12,7 @@ import { NeInlineNotification, NeTextArea, NeCombobox, + NeFormItemLabel, type NeComboboxOption, getPreference, } from '@nethesis/vue-components' @@ -36,6 +37,7 @@ import type { AxiosError } from 'axios' import { getCommonLanguagesOptions } from '@/lib/locale' import { getBrowserLocale } from '@/i18n' import { useLoginStore } from '@/stores/login' +import { combinePhoneParts, countryCodeComboOptions, parsePhoneForForm } from '@/lib/phone' const { isShown = false, currentReseller = undefined } = defineProps<{ isShown: boolean @@ -123,6 +125,7 @@ const mainContact = ref('') const mainContactRef = useTemplateRef('mainContactRef') const email = ref('') const emailRef = useTemplateRef('emailRef') +const countryCode = ref('') const phone = ref('') const phoneRef = useTemplateRef('phoneRef') const language = ref('it') @@ -168,7 +171,17 @@ function onShow() { city.value = currentReseller.custom_data?.city || '' mainContact.value = currentReseller.custom_data?.main_contact || '' email.value = currentReseller.custom_data?.email || '' - phone.value = currentReseller.custom_data?.phone || '' + + // Parse phone number to extract country code and local part + if (currentReseller.custom_data?.phone) { + const parsed = parsePhoneForForm(currentReseller.custom_data.phone) + countryCode.value = parsed.countryCode + phone.value = parsed.phone + } else { + countryCode.value = 'it' + phone.value = '' + } + language.value = currentReseller.custom_data?.language || '' notes.value = currentReseller.custom_data?.notes || '' } else { @@ -179,6 +192,7 @@ function onShow() { city.value = '' mainContact.value = '' email.value = '' + countryCode.value = 'it' phone.value = '' language.value = 'it' notes.value = '' @@ -268,7 +282,7 @@ async function saveReseller() { city: city.value, main_contact: mainContact.value, email: email.value, - phone: phone.value, + phone: combinePhoneParts(countryCode.value, phone.value), language: language.value, notes: notes.value, }, @@ -387,18 +401,41 @@ async function saveReseller() { :optional-label="t('common.optional')" /> - +
+
+ {{ $t('organizations.phone_number') }} + {{ $t('common.optional') }} +
+
+ + + + +
+
{{ resellerDetail.data.custom_data.email }} @@ -129,7 +131,7 @@ function getKebabMenuItems() { v-if="resellerDetail.data.custom_data.phone" :href="`tel:${resellerDetail.data.custom_data.phone}`" > - {{ resellerDetail.data.custom_data.phone }} + {{ formatPhoneForDisplay(resellerDetail.data.custom_data.phone) }} diff --git a/frontend/src/components/shell/SideMenu.vue b/frontend/src/components/shell/SideMenu.vue index cffaa3a37..9bf6b7c7e 100644 --- a/frontend/src/components/shell/SideMenu.vue +++ b/frontend/src/components/shell/SideMenu.vue @@ -177,7 +177,7 @@ function loadMenuItemsExpanded() { :class="[ isCurrentRoute(item.to) ? 'border-primary-700 dark:border-primary-500 bg-gray-100 text-gray-900 dark:bg-gray-800 dark:text-gray-50' - : 'border-transparent text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-gray-50', + : 'text-tertiary-neutral dark:text-tertiary-neutral border-transparent hover:text-gray-900 dark:hover:text-gray-50', 'group flex cursor-pointer items-center gap-x-3 rounded-md border-l-4 px-3 py-2 text-sm leading-6 font-semibold hover:bg-gray-100 dark:hover:bg-gray-800', ]" > @@ -195,7 +195,7 @@ function loadMenuItemsExpanded() { :class="[ isCurrentRoute(item.to) ? 'text-gray-900 dark:text-gray-50' - : 'text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-gray-50', + : 'text-tertiary-neutral dark:text-tertiary-neutral hover:text-gray-900 dark:hover:text-gray-50', 'group flex cursor-pointer items-center justify-between rounded-md border-l-4 border-transparent px-3 py-2 text-sm leading-6 font-semibold hover:bg-gray-100 dark:hover:bg-gray-800', ]" @click="toggleExpand(item)" @@ -225,7 +225,7 @@ function loadMenuItemsExpanded() { :class="[ isCurrentRoute(child.to) ? 'border-primary-700 dark:border-primary-500 border-l-4 bg-gray-100 text-gray-900 dark:bg-gray-800 dark:text-gray-50' - : 'text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-gray-50', + : 'text-tertiary-neutral dark:text-tertiary-neutral hover:text-gray-900 dark:hover:text-gray-50', 'group flex cursor-pointer items-center gap-x-3 rounded-md px-3 py-1 text-sm leading-6 font-semibold hover:bg-gray-100 dark:hover:bg-gray-800', ]" > diff --git a/frontend/src/components/shell/TopBar.vue b/frontend/src/components/shell/TopBar.vue index 3ce788d42..c157d061a 100644 --- a/frontend/src/components/shell/TopBar.vue +++ b/frontend/src/components/shell/TopBar.vue @@ -84,7 +84,7 @@ function openNotificationsDrawer() { >