Compare commits

..

4 commits

Author SHA1 Message Date
0e60183372
fix: Selection not working in country autocomplete
Some checks failed
Build / build (pull_request) Successful in 39s
Check formatting / check-formatting (pull_request) Failing after 16s
2025-07-12 15:16:04 +02:00
ed948d642a
chore: Update Vuetify 2025-07-12 15:15:47 +02:00
c923c2dbf4
feat: Install Vue devtools 2025-07-12 14:19:07 +02:00
b0650a5bf4
chore: Minor optimizations
- Optimize code, update a validation message and fix code inspection warning
2025-07-12 12:45:18 +02:00
6 changed files with 982 additions and 57 deletions

View file

@ -19,7 +19,7 @@
"openapi-fetch": "^0.14.0",
"roboto-fontface": "*",
"vue": "^3.5.13",
"vuetify": "^3.8.1"
"vuetify": "^3.9.0"
},
"devDependencies": {
"@eslint/js": "^9.24.0",
@ -42,6 +42,7 @@
"unplugin-vue-components": "^28.5.0",
"unplugin-vue-router": "^0.12.0",
"vite": "^6.2.6",
"vite-plugin-vue-devtools": "^7.7.7",
"vite-plugin-vuetify": "^2.1.1",
"vue-router": "^4.5.0",
"vue-tsc": "^2.2.8"

950
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref, useId, useTemplateRef } from "vue";
import { computed, ref, useId, useTemplateRef } from "vue";
import CountryListItem from "./CountryListItem.vue";
import { Country, getAvailableCountries } from "../utils/countries";
import { type Country, getAvailableCountries, userCountry } from "../utils/countries.ts";
const formModel = ref(false);
const formRef = useTemplateRef("form");
@ -16,16 +16,13 @@ const commonName = ref("");
const emailAddress = ref("");
const organization = ref("");
const organizationalUnit = ref("");
const country = ref("");
const country = ref(userCountry?.alpha2);
const countryName = ref("");
const state = ref("");
const locality = ref("");
// Countries data for autocomplete
const countries = ref<(Country | { divider: boolean; header: string })[]>([]);
// Initialize countries list
countries.value = getAvailableCountries();
const countries = ref(getAvailableCountries());
// Certificate settings
const keyLength = ref(4096);
@ -35,25 +32,25 @@ const noExpiry = ref(false);
// Validation rules
const domainRules = [
(value: string) => !!value || "A value must be provided to add the domain.",
(value: string) => new RegExp("^[a-z0-9][a-z0-9.]+$").test(value) || "Invalid domain characters provided. (To use some characters, convert the domain to xn-domain format.)",
]
(value: string) => /^[a-z0-9][a-z0-9.\-_]+$|^xn--[a-z0-9.\-_]+$/i.test(value) || "Invalid domain characters provided. (To use some special characters, convert the domain to xn-domain format.)"
];
const emailRules = [
(value: string) => !value || /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(value) || "Invalid email format.",
(value: string) => !value || /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(value) || "Invalid email format."
];
const keyLengthRules = [
(value: number) => value >= 3000 || "Key length must be at least 3000 bits.",
(value: number) => value <= 6000 || "Key length must not exceed 6000 bits.",
(value: number) => value <= 6000 || "Key length must not exceed 6000 bits."
];
const validityDaysRules = [
(value: number) => !value || value > 0 || "Validity period (in days) must be a positive integer.",
(value: number) => !value || value > 0 || "Validity period (in days) must be a positive integer."
];
function addDomain() {
if (formRef.value?.errors.some(({id}) => domainInputId === id)) {
if (formRef.value?.errors.some(({ id }) => domainInputId === id)) {
return;
}
@ -67,6 +64,7 @@ function addDomain() {
}
const isSubmitting = ref(false);
async function submitNewCertificate() {
if (!formRef.value?.validate()) {
return;
@ -75,7 +73,7 @@ async function submitNewCertificate() {
isSubmitting.value = true;
try {
// Extract country code if country is an object
const countryCode = typeof country.value === 'object' && country.value !== null
const countryCode = typeof country.value === "object" && country.value !== null
? (country.value as Country).alpha2
: country.value;
@ -91,10 +89,10 @@ async function submitNewCertificate() {
organizationalUnit: organizationalUnit.value,
country: countryCode,
state: state.value,
locality: locality.value,
locality: locality.value
},
extension: {
alternativeDnsNames: domains.value,
alternativeDnsNames: domains.value
}
});
} finally {
@ -147,8 +145,11 @@ async function submitNewCertificate() {
hint="Search and select your country"
class="mb-2"
>
<template #item="slotProps">
<CountryListItem :item="slotProps.item" :props="slotProps.props" />
<template #item="{ props, item }">
<CountryListItem
:item="item"
:item-props="props"
/>
</template>
</v-autocomplete>

View file

@ -1,45 +1,44 @@
<script setup lang="ts">
// Define props with proper TypeScript typing
import { computed } from "vue";
import type { CountryListItemType } from "@/utils/countries.ts";
interface Country {
country: string;
alpha2: string;
alpha3: string;
numeric: string;
type ListItem<T> = {
raw: T;
}
interface Divider {
divider: boolean;
header: string;
}
// Define a type that can be either a Country or a Divider
type CountryListItemType = Country | Divider;
// Define props with proper TypeScript typing
const props = defineProps<{
item: { raw: CountryListItemType };
props?: Record<string, unknown>;
item: ListItem<CountryListItemType>;
itemProps?: Record<string, unknown>;
}>();
// Helper functions to check the type of the item
const isDivider = computed(() => {
return 'divider' in props.item.raw && props.item.raw.divider;
return "divider" in props.item.raw && props.item.raw.divider;
});
const headerText = computed(() => {
return 'header' in props.item.raw ? props.item.raw.header : "";
return "header" in props.item.raw ? props.item.raw.header : "";
});
const countryItem = computed(() => {
return !isDivider.value && !headerText.value && 'country' in props.item.raw ? props.item.raw: undefined;
})
return !isDivider.value && !headerText.value && "country" in props.item.raw ? props.item.raw : undefined;
});
</script>
<template>
<v-divider v-if="isDivider" class="my-2" />
<v-list-subheader v-if="headerText">{{ headerText }}</v-list-subheader>
<v-list-item v-if="countryItem" v-bind="props" :title="countryItem.country" :value="countryItem.alpha2" />
<v-divider
v-if="isDivider"
class="my-2"
/>
<v-list-subheader v-if="headerText">
{{ headerText }}
</v-list-subheader>
<v-list-item
v-if="countryItem"
v-bind="itemProps"
:title="countryItem.country"
/>
</template>

View file

@ -1,6 +1,6 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"include": ["env.d.ts", "src/**/*", "src/**/*.ts", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,

View file

@ -2,6 +2,7 @@ import Components from 'unplugin-vue-components/vite'
import Vue from '@vitejs/plugin-vue'
import Vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
import ViteFonts from 'unplugin-fonts/vite'
import VueDevTools from 'vite-plugin-vue-devtools';
import VueRouter from 'unplugin-vue-router/vite'
import { defineConfig } from 'vite'
@ -32,6 +33,9 @@ export default defineConfig({
}],
},
}),
VueDevTools({
launchEditor: process.env.DEVTOOLS_LAUNCH_EDITOR ?? 'idea',
}),
],
define: { 'process.env': {} },
resolve: {