1 Commits

Author SHA1 Message Date
Sebastian e9bb013381 Update dependency vite to v8 2026-05-15 12:50:12 +00:00
22 changed files with 491 additions and 3030 deletions
-42
View File
@@ -1,42 +0,0 @@
name: Build Test
on:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Retrieve Server Install Action
uses: actions/checkout@v6.0.2
with:
repository: Nodarx/action-server-install
ref: main
path: action-server-install
github-server-url: https://git.ktrix.dev
- name: Install Server
uses: ./action-server-install
with:
install-php: 'false'
install-node: 'true'
php-version: '8.5'
node-version: '24'
server-path: './server'
- name: Checkout Pull Request
uses: actions/checkout@v6.0.2
with:
repository: ${{ github.repository }}
ref: ${{ github.event.pull_request.head.sha }}
path: server/modules/user_profile
github-server-url: https://git.ktrix.dev
- name: Install dependencies
run: npm ci
working-directory: server/modules/user_profile
- name: Build
run: npm run build
working-directory: server/modules/user_profile
-42
View File
@@ -1,42 +0,0 @@
name: JS Unit Tests
on:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Retrieve Server Install Action
uses: actions/checkout@v6.0.2
with:
repository: Nodarx/action-server-install
ref: main
path: action-server-install
github-server-url: https://git.ktrix.dev
- name: Install Server
uses: ./action-server-install
with:
install-php: 'false'
install-node: 'true'
php-version: '8.5'
node-version: '24'
server-path: './server'
- name: Checkout Pull Request
uses: actions/checkout@v6.0.2
with:
repository: ${{ github.repository }}
ref: ${{ github.event.pull_request.head.sha }}
path: server/modules/user_profile
github-server-url: https://git.ktrix.dev
- name: Install dependencies
run: npm ci
working-directory: server/modules/user_profile
- name: Run tests
run: npm run test:unit
working-directory: server/modules/user_profile
+2 -8
View File
@@ -25,17 +25,11 @@ jobs:
tools: composer:v2
- name: Install Renovate
run: |
npm install --global --no-audit --fund=false \
--prefix "${{ runner.temp }}/renovate-npm" \
--cache "${{ runner.temp }}/renovate-npm-cache" \
renovate
"${{ runner.temp }}/renovate-npm/bin/renovate" --version
run: npm install -g renovate
- name: Run Renovate
env:
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
RENOVATE_PLATFORM: gitea
RENOVATE_ENDPOINT: https://git.ktrix.dev/api/v1
run: |
"${{ runner.temp }}/renovate-npm/bin/renovate" ${{ gitea.repository }}
run: renovate ${{ gitea.repository }}
+4 -2
View File
@@ -12,10 +12,12 @@ node_modules/
/static/
# Backend development
/vendor/
/lib/vendor/
coverage/
*.cache
phpunit.xml.cache
.phpunit.result.cache
.php-cs-fixer.cache
.phpstan.cache
.phpactor/
# Editors
Generated
-18
View File
@@ -1,18 +0,0 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "71f6c55e6bc13ae680f9f4bb06eae7ce",
"packages": [],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {},
"platform-dev": {},
"plugin-api-version": "2.6.0"
}
-84
View File
@@ -1,84 +0,0 @@
{
"account": {
"address": "Adresse",
"addressPlaceholder": "Musterstraße 123",
"city": "Stadt",
"cityPlaceholder": "Berlin",
"countries": {
"australia": "Australien",
"brazil": "Brasilien",
"canada": "Kanada",
"china": "China",
"france": "Frankreich",
"germany": "Deutschland",
"india": "Indien",
"japan": "Japan",
"mexico": "Mexiko",
"southAfrica": "Südafrika",
"unitedKingdom": "Vereinigtes Königreich",
"unitedStates": "Vereinigte Staaten"
},
"country": "Land",
"email": "E-Mail",
"firstName": "Vorname",
"firstNamePlaceholder": "Max",
"lastName": "Nachname",
"lastNamePlaceholder": "Mustermann",
"managedFieldHint": "Wird vom Authentifizierungsanbieter verwaltet",
"phoneNumber": "Telefonnummer",
"photoRequirements": "Zulässig sind JPG, GIF oder PNG. Maximale Größe: 800 KB",
"selectCountry": "Land auswählen",
"stateOrProvince": "Bundesland/Provinz",
"statePlaceholder": "Bundesland",
"title": "Kontodetails",
"uploadPhoto": "Neues Foto hochladen",
"zipCode": "Postleitzahl"
},
"actions": {
"reset": "Zurücksetzen",
"saveChanges": "Änderungen speichern"
},
"nav": {
"preferences": "Einstellungen",
"profile": "Profil",
"profileSettings": "Profil & Einstellungen",
"security": "Sicherheit"
},
"notifications": {
"columns": {
"app": "App",
"browser": "BROWSER",
"email": "E-MAIL",
"type": "Typ"
},
"permissionDescription": "Wir benötigen die Berechtigung Ihres Browsers, um Benachrichtigungen anzuzeigen.",
"requestPermission": "Berechtigung anfordern",
"timing": {
"anytime": "Jederzeit",
"online": "Nur wenn ich online bin"
},
"timingQuestion": "Wann sollen wir Ihnen Benachrichtigungen senden?",
"title": "Benachrichtigungen",
"types": {
"accountActivity": "Kontoaktivität",
"newBrowserSignIn": "Anmeldung über einen neuen Browser",
"newDeviceLinked": "Ein neues Gerät wurde verknüpft",
"newForYou": "Neu für Sie"
}
},
"preferences": {
"languageHint": "Wird sofort angewendet und in Ihrem Konto gespeichert",
"languageLabel": "Anzeigesprache",
"languageTitle": "Sprache"
},
"security": {
"emptyDescription": "Derzeit sind keine Sicherheitsmodule aktiviert oder installiert.",
"emptyTitle": "Keine Sicherheitseinstellungen verfügbar"
},
"tabs": {
"account": "Konto",
"notifications": "Benachrichtigungen",
"preferences": "Einstellungen",
"security": "Sicherheit"
}
}
-84
View File
@@ -1,84 +0,0 @@
{
"account": {
"address": "Address",
"addressPlaceholder": "123 Main St",
"city": "City",
"cityPlaceholder": "New York",
"countries": {
"australia": "Australia",
"brazil": "Brazil",
"canada": "Canada",
"china": "China",
"france": "France",
"germany": "Germany",
"india": "India",
"japan": "Japan",
"mexico": "Mexico",
"southAfrica": "South Africa",
"unitedKingdom": "United Kingdom",
"unitedStates": "United States"
},
"country": "Country",
"email": "E-mail",
"firstName": "First Name",
"firstNamePlaceholder": "John",
"lastName": "Last Name",
"lastNamePlaceholder": "Doe",
"managedFieldHint": "Managed by authentication provider",
"phoneNumber": "Phone Number",
"photoRequirements": "Allowed JPG, GIF or PNG. Max size of 800K",
"selectCountry": "Select Country",
"stateOrProvince": "State/Province",
"statePlaceholder": "State",
"title": "Account Details",
"uploadPhoto": "Upload new photo",
"zipCode": "Zip Code"
},
"actions": {
"reset": "Reset",
"saveChanges": "Save changes"
},
"nav": {
"preferences": "Preferences",
"profile": "Profile",
"profileSettings": "Profile & Settings",
"security": "Security"
},
"notifications": {
"columns": {
"app": "App",
"browser": "BROWSER",
"email": "EMAIL",
"type": "Type"
},
"permissionDescription": "We need permission from your browser to show notifications.",
"requestPermission": "Request Permission",
"timing": {
"anytime": "Anytime",
"online": "Only when I'm online"
},
"timingQuestion": "When should we send you notifications?",
"title": "Notifications",
"types": {
"accountActivity": "Account activity",
"newBrowserSignIn": "A new browser used to sign in",
"newDeviceLinked": "A new device is linked",
"newForYou": "New for you"
}
},
"preferences": {
"languageHint": "Applies immediately and is saved to your account",
"languageLabel": "Display language",
"languageTitle": "Language"
},
"security": {
"emptyDescription": "No security modules are currently enabled or installed.",
"emptyTitle": "No Security Settings Available"
},
"tabs": {
"account": "Account",
"notifications": "Notifications",
"preferences": "Preferences",
"security": "Security"
}
}
-8
View File
@@ -1,8 +0,0 @@
{
"nav": {
"preferences": "Preferences",
"profile": "Profile",
"profileSettings": "Profile & Settings",
"security": "Security"
}
}
+379 -2486
View File
File diff suppressed because it is too large Load Diff
+4 -12
View File
@@ -11,17 +11,13 @@
"dev": "vite build --mode development --config vite.config.ts",
"watch": "vite build --mode development --watch --config vite.config.ts",
"typecheck": "vue-tsc --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"test": "vitest run --config tests/js/vitest.config.ts",
"test:unit": "vitest run --config tests/js/vitest.config.ts",
"test:watch": "vitest watch --config tests/js/vitest.config.ts",
"test:coverage": "vitest run --coverage --config tests/js/vitest.config.ts"
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"@mdi/js": "^7.4.47",
"pinia": "^4.0.0",
"pinia": "^3.0.0",
"vue": "^3.5.18",
"vue-router": "^5.0.0",
"vue-router": "^4.5.1",
"vuetify": "^4.0.0"
},
"devDependencies": {
@@ -29,10 +25,6 @@
"@vue/tsconfig": "^0.9.0",
"typescript": "~6.0.0",
"vite": "^8.0.0",
"vue-tsc": "^3.0.5",
"@vitest/coverage-v8": "^4.1.6",
"@vue/test-utils": "^2.4.10",
"jsdom": "^30.0.0",
"vitest": "^4.1.6"
"vue-tsc": "^3.0.5"
}
}
+39 -41
View File
@@ -2,10 +2,8 @@
import { ref, computed, onMounted } from 'vue'
import { useUserStore } from '@KTXC/stores/userStore'
import { userService } from '@KTXC/services/user/userService'
import { useL10n } from '@KTXC'
const userStore = useUserStore()
const { t } = useL10n('user_profile')
const refInputEl = ref<HTMLElement>()
const isSaving = ref(false)
@@ -136,27 +134,27 @@ const resetAvatar = () => {
}
}
const countries = computed(() => [
{ value: 'United States', title: t('account.countries.unitedStates', 'United States') },
{ value: 'United Kingdom', title: t('account.countries.unitedKingdom', 'United Kingdom') },
{ value: 'Canada', title: t('account.countries.canada', 'Canada') },
{ value: 'Australia', title: t('account.countries.australia', 'Australia') },
{ value: 'Germany', title: t('account.countries.germany', 'Germany') },
{ value: 'France', title: t('account.countries.france', 'France') },
{ value: 'India', title: t('account.countries.india', 'India') },
{ value: 'China', title: t('account.countries.china', 'China') },
{ value: 'Japan', title: t('account.countries.japan', 'Japan') },
{ value: 'Brazil', title: t('account.countries.brazil', 'Brazil') },
{ value: 'Mexico', title: t('account.countries.mexico', 'Mexico') },
{ value: 'South Africa', title: t('account.countries.southAfrica', 'South Africa') },
])
const countries = [
'United States',
'United Kingdom',
'Canada',
'Australia',
'Germany',
'France',
'India',
'China',
'Japan',
'Brazil',
'Mexico',
'South Africa',
]
</script>
<template>
<VRow>
<VCol cols="12">
<VCard :title="t('account.title', 'Account Details')">
<VCard title="Account Details">
<VCardText class="d-flex">
<!-- 👉 Avatar -->
<VAvatar
@@ -174,7 +172,7 @@ const countries = computed(() => [
@click="refInputEl?.click()"
>
<VIcon icon="mdi-cloud-upload" class="d-sm-none" />
<span class="d-none d-sm-block">{{ t('account.uploadPhoto', 'Upload new photo') }}</span>
<span class="d-none d-sm-block">Upload new photo</span>
</VBtn>
<input
@@ -192,13 +190,13 @@ const countries = computed(() => [
variant="outlined"
@click="resetAvatar"
>
<span class="d-none d-sm-block">{{ t('actions.reset', 'Reset') }}</span>
<span class="d-none d-sm-block">Reset</span>
<VIcon icon="mdi-refresh" class="d-sm-none" />
</VBtn>
</div>
<p class="text-body-1 mb-0">
{{ t('account.photoRequirements', 'Allowed JPG, GIF or PNG. Max size of 800K') }}
Allowed JPG, GIF or PNG. Max size of 800K
</p>
</form>
</VCardText>
@@ -216,10 +214,10 @@ const countries = computed(() => [
>
<VTextField
v-model="accountDataLocal.name_given"
:placeholder="t('account.firstNamePlaceholder', 'John')"
:label="t('account.firstName', 'First Name')"
placeholder="John"
label="First Name"
:readonly="!isFieldEditable('name_given')"
:hint="!isFieldEditable('name_given') ? t('account.managedFieldHint', 'Managed by authentication provider') : ''"
:hint="!isFieldEditable('name_given') ? 'Managed by authentication provider' : ''"
persistent-hint
/>
</VCol>
@@ -231,10 +229,10 @@ const countries = computed(() => [
>
<VTextField
v-model="accountDataLocal.name_family"
:placeholder="t('account.lastNamePlaceholder', 'Doe')"
:label="t('account.lastName', 'Last Name')"
placeholder="Doe"
label="Last Name"
:readonly="!isFieldEditable('name_family')"
:hint="!isFieldEditable('name_family') ? t('account.managedFieldHint', 'Managed by authentication provider') : ''"
:hint="!isFieldEditable('name_family') ? 'Managed by authentication provider' : ''"
persistent-hint
/>
</VCol>
@@ -246,11 +244,11 @@ const countries = computed(() => [
>
<VTextField
v-model="accountDataLocal.email"
:label="t('account.email', 'E-mail')"
label="E-mail"
placeholder="john.doe@example.com"
type="email"
:readonly="!isFieldEditable('email')"
:hint="!isFieldEditable('email') ? t('account.managedFieldHint', 'Managed by authentication provider') : ''"
:hint="!isFieldEditable('email') ? 'Managed by authentication provider' : ''"
persistent-hint
/>
</VCol>
@@ -262,10 +260,10 @@ const countries = computed(() => [
>
<VTextField
v-model="accountDataLocal.phone"
:label="t('account.phoneNumber', 'Phone Number')"
label="Phone Number"
placeholder="+1 (555) 123-4567"
:readonly="!isFieldEditable('phone')"
:hint="!isFieldEditable('phone') ? t('account.managedFieldHint', 'Managed by authentication provider') : ''"
:hint="!isFieldEditable('phone') ? 'Managed by authentication provider' : ''"
persistent-hint
/>
</VCol>
@@ -277,8 +275,8 @@ const countries = computed(() => [
>
<VTextField
v-model="accountDataLocal.address"
:label="t('account.address', 'Address')"
:placeholder="t('account.addressPlaceholder', '123 Main St')"
label="Address"
placeholder="123 Main St"
/>
</VCol>
@@ -289,8 +287,8 @@ const countries = computed(() => [
>
<VTextField
v-model="accountDataLocal.city"
:label="t('account.city', 'City')"
:placeholder="t('account.cityPlaceholder', 'New York')"
label="City"
placeholder="New York"
/>
</VCol>
@@ -301,8 +299,8 @@ const countries = computed(() => [
>
<VTextField
v-model="accountDataLocal.state"
:label="t('account.stateOrProvince', 'State/Province')"
:placeholder="t('account.statePlaceholder', 'State')"
label="State/Province"
placeholder="State"
/>
</VCol>
@@ -313,7 +311,7 @@ const countries = computed(() => [
>
<VTextField
v-model="accountDataLocal.zip"
:label="t('account.zipCode', 'Zip Code')"
label="Zip Code"
placeholder="10001"
/>
</VCol>
@@ -325,9 +323,9 @@ const countries = computed(() => [
>
<VSelect
v-model="accountDataLocal.country"
:label="t('account.country', 'Country')"
label="Country"
:items="countries"
:placeholder="t('account.selectCountry', 'Select Country')"
placeholder="Select Country"
/>
</VCol>
@@ -341,7 +339,7 @@ const countries = computed(() => [
:loading="isSaving"
color="primary"
>
{{ t('actions.saveChanges', 'Save changes') }}
Save changes
</VBtn>
<VBtn
@@ -350,7 +348,7 @@ const countries = computed(() => [
type="reset"
@click.prevent="resetForm"
>
{{ t('actions.reset', 'Reset') }}
Reset
</VBtn>
</VCol>
</VRow>
+43 -36
View File
@@ -1,62 +1,69 @@
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { useL10n } from '@KTXC'
import { ref } from 'vue'
const { t } = useL10n('user_profile')
const recentDevices = ref(
[
{
type: 'New for you',
email: true,
browser: true,
app: true,
},
{
type: 'Account activity',
email: true,
browser: true,
app: true,
},
{
type: 'A new browser used to sign in',
email: true,
browser: true,
app: false,
},
{
type: 'A new device is linked',
email: true,
browser: false,
app: false,
},
],
)
const recentDevices = ref([
{ id: 'newForYou', email: true, browser: true, app: true },
{ id: 'accountActivity', email: true, browser: true, app: true },
{ id: 'newBrowserSignIn', email: true, browser: true, app: false },
{ id: 'newDeviceLinked', email: true, browser: false, app: false },
])
const notificationTypeLabels = computed<Record<string, string>>(() => ({
newForYou: t('notifications.types.newForYou', 'New for you'),
accountActivity: t('notifications.types.accountActivity', 'Account activity'),
newBrowserSignIn: t('notifications.types.newBrowserSignIn', 'A new browser used to sign in'),
newDeviceLinked: t('notifications.types.newDeviceLinked', 'A new device is linked'),
}))
const notificationTimingOptions = computed(() => [
{ value: 'online', title: t('notifications.timing.online', 'Only when I\'m online') },
{ value: 'anytime', title: t('notifications.timing.anytime', 'Anytime') },
])
const selectedNotification = ref('online')
const selectedNotification = ref('Only when I\'m online')
</script>
<template>
<VCard :title="t('notifications.title', 'Notifications')">
<VCard title="Notifications">
<VCardText>
{{ t('notifications.permissionDescription', 'We need permission from your browser to show notifications.') }}
<a href="javascript:void(0)">{{ t('notifications.requestPermission', 'Request Permission') }}</a>
We need permission from your browser to show notifications.
<a href="javascript:void(0)">Request Permission</a>
</VCardText>
<VTable class="text-no-wrap">
<thead>
<tr>
<th scope="col">
{{ t('notifications.columns.type', 'Type') }}
Type
</th>
<th scope="col">
{{ t('notifications.columns.email', 'EMAIL') }}
EMAIL
</th>
<th scope="col">
{{ t('notifications.columns.browser', 'BROWSER') }}
BROWSER
</th>
<th scope="col">
{{ t('notifications.columns.app', 'App') }}
App
</th>
</tr>
</thead>
<tbody>
<tr
v-for="device in recentDevices"
:key="device.id"
:key="device.type"
>
<td>
{{ notificationTypeLabels[device.id] }}
{{ device.type }}
</td>
<td>
<VCheckbox v-model="device.email" />
@@ -75,7 +82,7 @@ const selectedNotification = ref('online')
<VCardText>
<VForm @submit.prevent="() => {}">
<p class="text-base font-weight-medium">
{{ t('notifications.timingQuestion', 'When should we send you notifications?') }}
When should we send you notifications?
</p>
<VRow>
@@ -86,21 +93,21 @@ const selectedNotification = ref('online')
<VSelect
v-model="selectedNotification"
mandatory
:items="notificationTimingOptions"
:items="['Only when I\'m online', 'Anytime']"
/>
</VCol>
</VRow>
<div class="d-flex flex-wrap gap-4 mt-4">
<VBtn type="submit">
{{ t('actions.saveChanges', 'Save changes') }}
Save Changes
</VBtn>
<VBtn
color="secondary"
variant="outlined"
type="reset"
>
{{ t('actions.reset', 'Reset') }}
Reset
</VBtn>
</div>
</VForm>
-45
View File
@@ -1,45 +0,0 @@
<script lang="ts" setup>
import { computed } from 'vue'
import { useL10nStore, useL10n } from '@KTXC'
const l10nStore = useL10nStore()
const { t } = useL10n('user_profile')
// Offer each available locale under its own native name
const languageOptions = computed(() =>
l10nStore.available.map((locale: string) => {
let title = locale
try {
title = new Intl.DisplayNames([locale], { type: 'language' }).of(locale) ?? locale
} catch {
// unknown tag: fall back to the raw locale code
}
return { value: locale, title }
})
)
const selectLanguage = (locale: string | null) => {
if (locale && locale !== l10nStore.locale) {
l10nStore.setLocale(locale)
}
}
</script>
<template>
<VRow>
<VCol cols="12" md="6">
<VCard :title="t('preferences.languageTitle', 'Language')">
<VCardText>
<VSelect
:model-value="l10nStore.locale"
:items="languageOptions"
:label="t('preferences.languageLabel', 'Display language')"
:hint="t('preferences.languageHint', 'Applies immediately and is saved to your account')"
persistent-hint
@update:model-value="selectLanguage"
/>
</VCardText>
</VCard>
</VCol>
</VRow>
</template>
+2 -6
View File
@@ -1,10 +1,8 @@
<script lang="ts" setup>
import { ref, computed, onMounted } from 'vue'
import { useIntegrationStore } from '@KTXC/stores/integrationStore'
import { useL10n } from '@KTXC'
const integrationStore = useIntegrationStore()
const { t } = useL10n('user_profile')
// Get all registered security panel components from modules
const securityPanels = computed(() => {
@@ -61,11 +59,9 @@ onMounted(async () => {
class="mb-4"
color="grey"
/>
<p class="text-h6 mb-2">
{{ t('security.emptyTitle', 'No Security Settings Available') }}
</p>
<p class="text-h6 mb-2">No Security Settings Available</p>
<p class="text-body-2 text-medium-emphasis">
{{ t('security.emptyDescription', 'No security modules are currently enabled or installed.') }}
No security modules are currently enabled or installed.
</p>
</VCardText>
</VCard>
+1 -5
View File
@@ -5,19 +5,17 @@ const integrations: ModuleIntegrations = {
{
id: 'user_profile',
label: 'Profile & Settings',
l10n: 'nav.profileSettings',
path: '/me',
icon: 'mdi-account-outline',
priority: 90,
},
],
// User personal settings
user_settings_menu: [
{
id: 'profile',
label: 'Profile',
l10n: 'nav.profile',
path: '/me/profile',
icon: 'mdi-account-circle',
priority: 10,
@@ -25,7 +23,6 @@ const integrations: ModuleIntegrations = {
{
id: 'preferences',
label: 'Preferences',
l10n: 'nav.preferences',
path: '/me/preferences',
icon: 'mdi-tune',
priority: 20,
@@ -33,7 +30,6 @@ const integrations: ModuleIntegrations = {
{
id: 'security',
label: 'Security',
l10n: 'nav.security',
path: '/me/security',
icon: 'mdi-shield-lock',
priority: 30,
-3
View File
@@ -1,7 +1,4 @@
import routes from '@/routes'
import integrations from '@/integrations'
// Module ships translation catalogs at static/l10n/{locale}.json
export const l10n = true
export { routes, integrations }
+2 -2
View File
@@ -2,7 +2,7 @@ const routes = [
{
name: 'user_profile',
path: '/me',
component: () => import('@/pages/Main.vue'),
component: () => import('@/views/ProfilePage.vue'),
meta: {
requiresAuth: true
}
@@ -10,7 +10,7 @@ const routes = [
{
name: 'user_profile-tab',
path: '/me/:tab',
component: () => import('@/pages/Main.vue'),
component: () => import('@/views/ProfilePage.vue'),
meta: {
requiresAuth: true
}
@@ -1,29 +1,27 @@
<script lang="ts" setup>
import { computed, ref } from 'vue'
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import ProfileAccount from '@/components/ProfileAccount.vue'
import ProfileNotifications from '@/components/ProfileNotifications.vue'
import ProfilePreferences from '@/components/ProfilePreferences.vue'
import ProfileSecurity from '@/components/ProfileSecurity.vue'
import { useL10n } from '@KTXC'
const route = useRoute()
const { t } = useL10n('user_profile')
const activeTab = ref(route.params.tab || 'account')
// tabs
const tabs = computed(() => [
{ title: t('tabs.account', 'Account'), icon: 'mdi-account-outline', tab: 'account' },
{ title: t('tabs.security', 'Security'), icon: 'mdi-lock-outline', tab: 'security' },
{ title: t('tabs.preferences', 'Preferences'), icon: 'mdi-tune', tab: 'preferences' },
{ title: t('tabs.notifications', 'Notifications'), icon: 'mdi-bell-outline', tab: 'notifications' },
])
const tabs = [
{ title: 'Account', icon: 'mdi-account-outline', tab: 'account' },
{ title: 'Security', icon: 'mdi-lock-outline', tab: 'security' },
{ title: 'Notifications', icon: 'mdi-bell-outline', tab: 'notifications' },
]
</script>
<template>
<div
<PerfectScrollbar
class="pa-4"
style="height: calc(100vh - 64px);"
:options="{ wheelPropagation: false }"
>
<VTabs
v-model="activeTab"
@@ -63,11 +61,6 @@ const tabs = computed(() => [
<VWindowItem value="notifications">
<ProfileNotifications />
</VWindowItem>
<!-- Preferences -->
<VWindowItem value="preferences">
<ProfilePreferences />
</VWindowItem>
</VWindow>
</div>
</PerfectScrollbar>
</template>
-30
View File
@@ -1,30 +0,0 @@
import { describe, it, expect } from 'vitest'
describe('Basic Tests', () => {
it('should perform basic assertion', () => {
expect(true).toBe(true)
})
it('should test array operations', () => {
const array = ['foo', 'bar', 'baz']
expect(array).toHaveLength(3)
expect(array).toContain('bar')
expect(array[0]).toBe('foo')
})
it('should test string operations', () => {
const string = 'Hello, World!'
expect(string).toContain('World')
expect(string.length).toBe(13)
})
it('should test object operations', () => {
const obj = { foo: 'bar', count: 42 }
expect(obj).toHaveProperty('foo')
expect(obj.foo).toBe('bar')
expect(obj.count).toBeGreaterThan(40)
})
})
-33
View File
@@ -1,33 +0,0 @@
import { fileURLToPath } from 'node:url'
import { defineConfig, configDefaults } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import path from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, '../../src'),
'@KTXC': path.resolve(__dirname, '../../../../core/src'),
},
},
test: {
environment: 'jsdom',
exclude: [...configDefaults.exclude, 'e2e/**'],
root: fileURLToPath(new URL('../../', import.meta.url)),
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: [
'node_modules/',
'tests/',
'**/*.d.ts',
'**/*.config.*',
'**/dist/**',
],
},
},
})
+2
View File
@@ -5,6 +5,8 @@
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@KTXC/*": ["../../core/src/*"]
+3 -26
View File
@@ -1,30 +1,10 @@
import { defineConfig, type PluginOption } from 'vite'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
import fs from 'fs'
// Copy translation catalogs (l10n/*.json, except the extractor-only
// en.manual.json) into static/l10n/ so the core shell can fetch them at
// /modules/{handle}/static/l10n/{locale}.json
const copyL10nCatalogs = (): PluginOption => ({
name: 'copy-l10n-catalogs',
apply: 'build',
closeBundle() {
const sourceDir = path.resolve(__dirname, 'l10n')
const outDir = path.resolve(__dirname, 'static/l10n')
if (!fs.existsSync(sourceDir)) return
fs.mkdirSync(outDir, { recursive: true })
for (const file of fs.readdirSync(sourceDir)) {
if (file.endsWith('.json') && !file.endsWith('.manual.json')) {
fs.copyFileSync(path.join(sourceDir, file), path.join(outDir, file))
}
}
},
})
// https://vite.dev/config/
export default defineConfig({
plugins: [vue(), copyL10nCatalogs()],
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
@@ -40,10 +20,7 @@ export default defineConfig({
fileName: () => 'module.mjs',
},
rollupOptions: {
external: ['vue', 'vue-router', 'pinia', '@KTXC'],
output: {
paths: (id) => (id === '@KTXC' ? '/js/ktxc.mjs' : id),
},
external: ['vue', 'vue-router', 'pinia'],
},
},
})