From 45e8b970073b0cbbebfb65609b17e540c4e24962 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 23 Apr 2026 12:19:43 +0530 Subject: [PATCH 01/10] feat: add Keytrace profile composable and API integration Co-authored-by: Copilot --- app/components/AccountItem.vue | 368 +++++++++++++++++++++++ app/components/Compare/FacetSelector.vue | 32 +- app/components/LinkedAccounts.vue | 51 ++++ app/components/ProfileHeader.vue | 71 +++++ app/composables/useKeytraceProfile.ts | 54 ++++ app/pages/profile/[identity]/index.vue | 41 ++- i18n/locales/ar-EG.json | 5 +- i18n/locales/ar.json | 5 +- i18n/locales/az-AZ.json | 5 +- i18n/locales/bg-BG.json | 5 +- i18n/locales/bn-IN.json | 5 +- i18n/locales/cs-CZ.json | 5 +- i18n/locales/de-AT.json | 5 + i18n/locales/de-DE.json | 7 +- i18n/locales/de.json | 5 +- i18n/locales/en-GB.json | 5 + i18n/locales/en.json | 3 + i18n/locales/es-419.json | 5 +- i18n/locales/es.json | 5 +- i18n/locales/fr-FR.json | 5 +- i18n/locales/hi-IN.json | 5 +- i18n/locales/hu-HU.json | 5 +- i18n/locales/id-ID.json | 5 +- i18n/locales/it-IT.json | 5 +- i18n/locales/ja-JP.json | 5 +- i18n/locales/kn-IN.json | 5 +- i18n/locales/mr-IN.json | 5 +- i18n/locales/nb-NO.json | 5 +- i18n/locales/ne-NP.json | 5 +- i18n/locales/nl.json | 5 +- i18n/locales/pl-PL.json | 5 +- i18n/locales/pt-BR.json | 5 +- i18n/locales/ru-RU.json | 5 +- i18n/locales/sr-Latn-RS.json | 5 +- i18n/locales/ta-IN.json | 5 +- i18n/locales/te-IN.json | 5 +- i18n/locales/tr-TR.json | 5 +- i18n/locales/uk-UA.json | 5 +- i18n/locales/vi-VN.json | 5 +- i18n/locales/zh-CN.json | 5 +- i18n/locales/zh-TW.json | 5 +- i18n/schema.json | 9 + server/api/keytrace/[domain].ts | 125 ++++++++ server/api/keytrace/reverify.post.ts | 30 ++ shared/types/keytrace.ts | 50 +++ 45 files changed, 948 insertions(+), 58 deletions(-) create mode 100644 app/components/AccountItem.vue create mode 100644 app/components/LinkedAccounts.vue create mode 100644 app/components/ProfileHeader.vue create mode 100644 app/composables/useKeytraceProfile.ts create mode 100644 server/api/keytrace/[domain].ts create mode 100644 server/api/keytrace/reverify.post.ts create mode 100644 shared/types/keytrace.ts diff --git a/app/components/AccountItem.vue b/app/components/AccountItem.vue new file mode 100644 index 0000000000..25ac23b149 --- /dev/null +++ b/app/components/AccountItem.vue @@ -0,0 +1,368 @@ + + + diff --git a/app/components/Compare/FacetSelector.vue b/app/components/Compare/FacetSelector.vue index 7ce289779c..a66d6116e0 100644 --- a/app/components/Compare/FacetSelector.vue +++ b/app/components/Compare/FacetSelector.vue @@ -101,30 +101,30 @@ function deselectAllFacet(category: string) { :aria-labelledby="`facet-category-label-${category}`" data-facet-category-facets > - + + + diff --git a/app/components/LinkedAccounts.vue b/app/components/LinkedAccounts.vue new file mode 100644 index 0000000000..994be8d3d7 --- /dev/null +++ b/app/components/LinkedAccounts.vue @@ -0,0 +1,51 @@ + + + diff --git a/app/components/ProfileHeader.vue b/app/components/ProfileHeader.vue new file mode 100644 index 0000000000..921de318ed --- /dev/null +++ b/app/components/ProfileHeader.vue @@ -0,0 +1,71 @@ + + + diff --git a/app/composables/useKeytraceProfile.ts b/app/composables/useKeytraceProfile.ts new file mode 100644 index 0000000000..58787a1dab --- /dev/null +++ b/app/composables/useKeytraceProfile.ts @@ -0,0 +1,54 @@ +import type { KeytraceAccount, KeytraceResponse } from "#shared/types/keytrace"; + +const statusPriority: Record = { + verified: 0, + unverified: 1, + stale: 2, + failed: 3, +}; + +export function useKeytraceProfile(domain: MaybeRefOrGetter) { + const asyncData = useFetch( + () => `/api/keytrace/${encodeURIComponent(toValue(domain))}`, + { + default: () => ({ + profile: { + name: "", + avatar: "", + description: "", + }, + accounts: [], + }), + }, + ); + + const profile = computed(() => asyncData.data.value?.profile); + const accounts = computed(() => asyncData.data.value?.accounts ?? []); + + const sortedAccounts = computed(() => + [...accounts.value].sort((a, b) => { + const statusSort = statusPriority[a.status] - statusPriority[b.status]; + if (statusSort !== 0) { + return statusSort; + } + + return a.platform.localeCompare(b.platform); + }), + ); + + const verifiedAccounts = computed(() => + sortedAccounts.value.filter((account) => account.status === "verified"), + ); + + const nonVerifiedAccounts = computed(() => + sortedAccounts.value.filter((account) => account.status !== "verified"), + ); + + return { + profile, + accounts: sortedAccounts, + verifiedAccounts, + nonVerifiedAccounts, + loading: asyncData.pending, + }; +} diff --git a/app/pages/profile/[identity]/index.vue b/app/pages/profile/[identity]/index.vue index 61ca0f69eb..284a70e76a 100644 --- a/app/pages/profile/[identity]/index.vue +++ b/app/pages/profile/[identity]/index.vue @@ -2,6 +2,7 @@ import { updateProfile as updateProfileUtil } from '~/utils/atproto/profile' import type { CommandPaletteContextCommandInput } from '~/types/command-palette' import { getSafeHttpUrl } from '#shared/utils/url' +import { useKeytraceProfile } from '~/composables/useKeytraceProfile' const route = useRoute('profile-identity') const identity = computed(() => route.params.identity) @@ -97,6 +98,12 @@ const inviteUrl = computed(() => { }) const safeProfileWebsiteUrl = computed(() => getSafeHttpUrl(profile.value.website)) +const { + profile: keytraceProfile, + accounts: keytraceAccounts, + loading: keytraceLoading, +} = useKeytraceProfile(identity) + useCommandPaletteContextCommands( computed((): CommandPaletteContextCommandInput[] => { const commands: CommandPaletteContextCommandInput[] = [] @@ -232,20 +239,34 @@ defineOgImage( +
+ + +
+
-

- {{ $t('profile.likes') }} - ({{ likes.records?.length ?? 0 }}) -

+
+

+ {{ $t('profile.likes') }} + ({{ likes.records?.length ?? 0 }}) +

+

{{ $t('profile.public_interests_description') }}

+
-
-

{{ $t('common.error') }}

+
+

{{ $t('profile.likes_error') }}

+
+
+

{{ $t('profile.likes_empty') }}

diff --git a/i18n/locales/ar-EG.json b/i18n/locales/ar-EG.json index 18682843b5..83ce5e2dcb 100644 --- a/i18n/locales/ar-EG.json +++ b/i18n/locales/ar-EG.json @@ -113,7 +113,10 @@ "message": "انضم إليّ على npmx", "share_button": "دعوة صديق", "compose_text": "جرّب npmx، المتصفح السريع لحزم npm!" - } + }, + "public_interests_description": "اهتمامات الحزم العامة وإشارات النشاط.", + "likes_error": "تعذّر تحميل الحزم المُعجَب بها.", + "likes_empty": "لا توجد حزم مُعجَب بها بعد." }, "package": { "size_increase": { diff --git a/i18n/locales/ar.json b/i18n/locales/ar.json index 179ed6251f..ad72cb012e 100644 --- a/i18n/locales/ar.json +++ b/i18n/locales/ar.json @@ -155,7 +155,10 @@ } }, "profile": { - "invite": {} + "invite": {}, + "public_interests_description": "اهتمامات الحزم العامة وإشارات النشاط.", + "likes_error": "تعذر تحميل الحزم المفضلة.", + "likes_empty": "لا توجد حزم مفضلة بعد." }, "package": { "not_found": "لم يتم العثور على الحزمة", diff --git a/i18n/locales/az-AZ.json b/i18n/locales/az-AZ.json index 93c9a8117a..d21379292f 100644 --- a/i18n/locales/az-AZ.json +++ b/i18n/locales/az-AZ.json @@ -214,7 +214,10 @@ "message": "Deyəsən onlar hələ npmx istifadə etmirlər. Onlara bu barədə demək istəyirsiniz?", "share_button": "Bluesky-da paylaş", "compose_text": "Salam {'@'}{handle}! npmx.dev-i yoxlamısan? Bu, npm reyestri üçün sürətli, müasir və açıq mənbəli brauzerdir.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Açıq paket maraqları və fəaliyyət siqnalları.", + "likes_error": "Bəyənilən paketlər yüklənə bilmədi.", + "likes_empty": "Hələ bəyənilən paket yoxdur." }, "package": { "not_found": "Paket Tapılmadı", diff --git a/i18n/locales/bg-BG.json b/i18n/locales/bg-BG.json index f937b02e1e..daa106948f 100644 --- a/i18n/locales/bg-BG.json +++ b/i18n/locales/bg-BG.json @@ -177,7 +177,10 @@ "message": "Изглежда, че все още не използват npmx. Искате ли да им кажете за него?", "share_button": "Споделете в Bluesky", "compose_text": "Здравей {'@'}{handle}! Проверил ли си npmx.dev? Това е браузър за npm регистъра - бърз, модерен и с отворен код.\\nhttps://npmx.dev" - } + }, + "public_interests_description": "Публични интереси към пакети и сигнали за активност.", + "likes_error": "Неуспешно зареждане на харесани пакети.", + "likes_empty": "Все още няма харесани пакети." }, "package": { "not_found": "Пакетът не е намерен", diff --git a/i18n/locales/bn-IN.json b/i18n/locales/bn-IN.json index 3712252621..883027ec71 100644 --- a/i18n/locales/bn-IN.json +++ b/i18n/locales/bn-IN.json @@ -121,7 +121,10 @@ } }, "profile": { - "invite": {} + "invite": {}, + "public_interests_description": "পাবলিক প্যাকেজ আগ্রহ এবং কার্যকলাপ সংকেত।", + "likes_error": "পছন্দের প্যাকেজগুলি লোড করা যায়নি।", + "likes_empty": "এখনও কোনো পছন্দের প্যাকেজ নেই।" }, "package": { "not_found": "প্যাকেজ পাওয়া যায়নি", diff --git a/i18n/locales/cs-CZ.json b/i18n/locales/cs-CZ.json index b5ffca3a02..84cbc676be 100644 --- a/i18n/locales/cs-CZ.json +++ b/i18n/locales/cs-CZ.json @@ -359,7 +359,10 @@ "message": "Zdá se, že ještě nepoužívají npmx. Chcete jim o tom říct?", "share_button": "Sdílet na Bluesky", "compose_text": "Ahoj {'@'}{handle}! Viděl jsi už npmx.dev? Je to prohlížeč pro npm registr, který je rychlý, moderní a open-source.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Veřejné zájmy o balíčky a signály aktivity.", + "likes_error": "Nepodařilo se načíst oblíbené balíčky.", + "likes_empty": "Zatím žádné oblíbené balíčky." }, "package": { "not_found": "Balíček nenalezen", diff --git a/i18n/locales/de-AT.json b/i18n/locales/de-AT.json index 458cf38f23..daeddeec95 100644 --- a/i18n/locales/de-AT.json +++ b/i18n/locales/de-AT.json @@ -21,5 +21,10 @@ "instant_search_turn_on": "Schnellsuche aktivieren", "instant_search_turn_off": "Schnellsuche deaktivieren", "instant_search_advisory": "Die Schnellsuche sendet bei jedem Tastendruck eine Anfrage." + }, + "profile": { + "public_interests_description": "Öffentliche Paketinteressen und Aktivitätssignale.", + "likes_error": "Beliebte Pakete konnten nicht geladen werden.", + "likes_empty": "Noch keine beliebten Pakete vorhanden." } } diff --git a/i18n/locales/de-DE.json b/i18n/locales/de-DE.json index 0618235d76..f439792e96 100644 --- a/i18n/locales/de-DE.json +++ b/i18n/locales/de-DE.json @@ -1,3 +1,8 @@ { - "$schema": "../schema.json" + "$schema": "../schema.json", + "profile": { + "public_interests_description": "Öffentliche Paketinteressen und Aktivitätssignale.", + "likes_error": "Gelikte Pakete konnten nicht geladen werden.", + "likes_empty": "Noch keine gelikten Pakete." + } } diff --git a/i18n/locales/de.json b/i18n/locales/de.json index e805f32888..1a29179809 100644 --- a/i18n/locales/de.json +++ b/i18n/locales/de.json @@ -358,7 +358,10 @@ "message": "Es sieht nicht so aus, als ob sie npmx schon benutzen. Möchtest du ihnen davon erzählen?", "share_button": "Auf Bluesky teilen", "compose_text": "Hey {'@'}{handle}! Hast du schon npmx.dev ausprobiert? Es ist ein Browser für die npm Registry, der schnell, modern und Open-Source ist.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Öffentliche Paketinteressen und Aktivitätssignale.", + "likes_error": "Gelikte Pakete konnten nicht geladen werden.", + "likes_empty": "Noch keine gelikten Pakete." }, "package": { "not_found": "Paket nicht gefunden", diff --git a/i18n/locales/en-GB.json b/i18n/locales/en-GB.json index cd250a7f15..127ed8f8ef 100644 --- a/i18n/locales/en-GB.json +++ b/i18n/locales/en-GB.json @@ -45,5 +45,10 @@ "error": "Failed to load organisations", "empty": "No organisations found" } + }, + "profile": { + "public_interests_description": "Public package interests and activity signals.", + "likes_error": "Could not load liked packages.", + "likes_empty": "No liked packages yet." } } diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 9c132582da..5e31a60a21 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -355,6 +355,9 @@ "website": "Website", "website_placeholder": "https://example.com", "likes": "Likes", + "public_interests_description": "Public package interests and activity signals.", + "likes_error": "Could not load liked packages.", + "likes_empty": "No liked packages yet.", "seo_title": "{handle} - npmx", "seo_description": "npmx profile by {handle}", "not_found": "Profile Not Found", diff --git a/i18n/locales/es-419.json b/i18n/locales/es-419.json index aff49a7df8..ad0eae137c 100644 --- a/i18n/locales/es-419.json +++ b/i18n/locales/es-419.json @@ -43,7 +43,10 @@ "invite": { "message": "Parece que aún no usa npmx. ¿Quieres contarle?", "compose_text": "¡Hola {'@'}{handle}! ¿Has probado ya npmx.dev? Es un navegador para el registro de npm rápido, moderno y de código abierto.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Intereses de paquetes públicos y señales de actividad.", + "likes_error": "No se pudieron cargar los paquetes favoritos.", + "likes_empty": "Aún no hay paquetes favoritos." }, "package": { "readme": { diff --git a/i18n/locales/es.json b/i18n/locales/es.json index 9bcb7263fc..a723b3e57c 100644 --- a/i18n/locales/es.json +++ b/i18n/locales/es.json @@ -244,7 +244,10 @@ "message": "Parece que aún no usa npmx. ¿Quieres contárselo?", "share_button": "Compartir en Bluesky", "compose_text": "¡Hola {'@'}{handle}! ¿Has probado ya npmx.dev? Es un navegador para el registro de npm rápido, moderno y de código abierto.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Intereses públicos en paquetes y señales de actividad.", + "likes_error": "No se pudieron cargar los paquetes que te gustan.", + "likes_empty": "Aún no hay paquetes que te gusten." }, "package": { "not_found": "Paquete no encontrado", diff --git a/i18n/locales/fr-FR.json b/i18n/locales/fr-FR.json index 0e40fa415b..01c18381a0 100644 --- a/i18n/locales/fr-FR.json +++ b/i18n/locales/fr-FR.json @@ -360,7 +360,10 @@ "message": "Il semblerait qu'ils n'utilisent pas encore npmx. Vous voulez leur en parler ?", "share_button": "Partager sur Bluesky", "compose_text": "Salut {'@'}{handle} ! As-tu déjà testé npmx.dev ? C'est un navigateur pour le registre npm : rapide, moderne et open source.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Intérêts publics pour les paquets et signaux d'activité.", + "likes_error": "Impossible de charger les paquets aimés.", + "likes_empty": "Aucun paquet aimé pour le moment." }, "package": { "not_found": "Paquet introuvable", diff --git a/i18n/locales/hi-IN.json b/i18n/locales/hi-IN.json index ea702a7728..18391f03c2 100644 --- a/i18n/locales/hi-IN.json +++ b/i18n/locales/hi-IN.json @@ -229,7 +229,10 @@ "expand": "विस्तृत करें" }, "profile": { - "invite": {} + "invite": {}, + "public_interests_description": "सार्वजनिक पैकेज रुचियाँ और गतिविधि संकेत।", + "likes_error": "पसंद किए गए पैकेज लोड नहीं हो सके।", + "likes_empty": "अभी तक कोई पसंद किए गए पैकेज नहीं।" }, "package": { "not_found": "पैकेज नहीं मिला", diff --git a/i18n/locales/hu-HU.json b/i18n/locales/hu-HU.json index c14948a6e1..0b391fa6f2 100644 --- a/i18n/locales/hu-HU.json +++ b/i18n/locales/hu-HU.json @@ -177,7 +177,10 @@ "message": "Úgy tűnik, hogy még nem használja az npmx-et. Szeretnéd megtudatni vele?", "share_button": "Megosztás a Bluesky-on", "compose_text": "Halló {'@'}{handle}! Már próbáltad az npmx.dev-et? Egy gyors, modern és nyílt forráskódú böngésző az npm regiszterhez.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Nyilvános csomagérdeklődések és aktivitási jelek.", + "likes_error": "Nem sikerült betölteni a kedvelt csomagokat.", + "likes_empty": "Még nincsenek kedvelt csomagok." }, "package": { "not_found": "Csomag Nem Található", diff --git a/i18n/locales/id-ID.json b/i18n/locales/id-ID.json index 9646ca17b1..717b761a39 100644 --- a/i18n/locales/id-ID.json +++ b/i18n/locales/id-ID.json @@ -244,7 +244,10 @@ "message": "Sepertinya mereka belum menggunakan npmx. Ingin memberi tahu mereka?", "share_button": "Bagikan di Bluesky", "compose_text": "Hai {'@'}{handle}! Sudah pernah mencoba npmx.dev? Ini adalah browser untuk npm registry yang cepat, modern, dan open-source.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Minat paket publik dan sinyal aktivitas.", + "likes_error": "Gagal memuat paket yang disukai.", + "likes_empty": "Belum ada paket yang disukai." }, "package": { "not_found": "Paket Tidak Ditemukan", diff --git a/i18n/locales/it-IT.json b/i18n/locales/it-IT.json index b19efca8db..dfbb4c95d1 100644 --- a/i18n/locales/it-IT.json +++ b/i18n/locales/it-IT.json @@ -155,7 +155,10 @@ } }, "profile": { - "invite": {} + "invite": {}, + "public_interests_description": "Interessi dei pacchetti pubblici e segnali di attività.", + "likes_error": "Impossibile caricare i pacchetti preferiti.", + "likes_empty": "Nessun pacchetto preferito ancora." }, "package": { "not_found": "Pacchetto Non Trovato", diff --git a/i18n/locales/ja-JP.json b/i18n/locales/ja-JP.json index 72397e4907..63e51d154a 100644 --- a/i18n/locales/ja-JP.json +++ b/i18n/locales/ja-JP.json @@ -224,7 +224,10 @@ "message": "まだnpmxを利用していないようです。npmxを紹介しますか?", "share_button": "Blueskyで共有", "compose_text": "{'@'}{handle} さん、npmx.devはもうチェックしましたか? 高速でモダンなオープンソースのnpmレジストリブラウザです。\nhttps://npmx.dev" - } + }, + "public_interests_description": "公開パッケージの関心事とアクティビティシグナル。", + "likes_error": "いいねしたパッケージを読み込めませんでした。", + "likes_empty": "いいねしたパッケージはまだありません。" }, "package": { "not_found": "パッケージが見つかりません", diff --git a/i18n/locales/kn-IN.json b/i18n/locales/kn-IN.json index 2111c4dd23..d8d81eb9e7 100644 --- a/i18n/locales/kn-IN.json +++ b/i18n/locales/kn-IN.json @@ -122,7 +122,10 @@ } }, "profile": { - "invite": {} + "invite": {}, + "public_interests_description": "ಸಾರ್ವಜನಿಕ ಪ್ಯಾಕೇಜ್ ಆಸಕ್ತಿಗಳು ಮತ್ತು ಚಟುವಟಿಕೆ ಸಂಕೇತಗಳು.", + "likes_error": "ಇಷ್ಟಪಟ್ಟ ಪ್ಯಾಕೇಜುಗಳನ್ನು ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ.", + "likes_empty": "ಇನ್ನೂ ಇಷ್ಟಪಟ್ಟ ಪ್ಯಾಕೇಜುಗಳಿಲ್ಲ." }, "package": { "not_found": "ಪ್ಯಾಕೇಜ್ ಕಂಡುಬಂದಿಲ್ಲ", diff --git a/i18n/locales/mr-IN.json b/i18n/locales/mr-IN.json index 57c36430e0..7bee21b07b 100644 --- a/i18n/locales/mr-IN.json +++ b/i18n/locales/mr-IN.json @@ -213,7 +213,10 @@ "expand": "विस्तृत करा" }, "profile": { - "invite": {} + "invite": {}, + "public_interests_description": "सार्वजनिक पॅकेज आवडी आणि क्रियाकलाप संकेत.", + "likes_error": "आवडलेल्या पॅकेजेस लोड करता आल्या नाहीत.", + "likes_empty": "अद्याप आवडलेली कोणतीही पॅकेजेस नाहीत." }, "package": { "not_found": "पॅकेज सापडले नाही", diff --git a/i18n/locales/nb-NO.json b/i18n/locales/nb-NO.json index 1d6351607a..749c355749 100644 --- a/i18n/locales/nb-NO.json +++ b/i18n/locales/nb-NO.json @@ -358,7 +358,10 @@ "message": "Det ser ikke ut som de bruker npmx ennå. Vil du fortelle dem om det?", "share_button": "Del på Bluesky", "compose_text": "Hei {'@'}{handle}! Har du sjekket ut npmx.dev ennå? Det er en leser for npm-registeret som er rask, moderne og åpen kildekode.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Offentlige pakkeinteresser og aktivitetssignaler.", + "likes_error": "Kunne ikke laste likte pakker.", + "likes_empty": "Ingen likte pakker ennå." }, "package": { "not_found": "Pakke ikke funnet", diff --git a/i18n/locales/ne-NP.json b/i18n/locales/ne-NP.json index 60b7dec1f1..c348e2fe17 100644 --- a/i18n/locales/ne-NP.json +++ b/i18n/locales/ne-NP.json @@ -122,7 +122,10 @@ } }, "profile": { - "invite": {} + "invite": {}, + "public_interests_description": "सार्वजनिक प्याकेज रुचिहरू र गतिविधि संकेतहरू।", + "likes_error": "मनपरेका प्याकेजहरू लोड गर्न सकिएन।", + "likes_empty": "अहिले सम्म मनपरेका प्याकेजहरू छैनन्।" }, "package": { "not_found": "प्याकेज फेला परेन", diff --git a/i18n/locales/nl.json b/i18n/locales/nl.json index 85e5f13c1a..45634a2503 100644 --- a/i18n/locales/nl.json +++ b/i18n/locales/nl.json @@ -358,7 +358,10 @@ "message": "Het lijkt erop dat ze npmx nog niet gebruiken. Wilt u ze hierop wijzen", "share_button": "Deel op Bluesky", "compose_text": "Hallo {'@'}{handle}! Hebt u npmx.dev al bekeken? Het is een browser voor het npm register die snel, modern en open source is.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Openbare pakketinteresses en activiteitssignalen.", + "likes_error": "Kon gelikete pakketten niet laden.", + "likes_empty": "Nog geen gelikete pakketten." }, "package": { "not_found": "Pakket niet gevonden", diff --git a/i18n/locales/pl-PL.json b/i18n/locales/pl-PL.json index f4acd249ed..85cfd34c32 100644 --- a/i18n/locales/pl-PL.json +++ b/i18n/locales/pl-PL.json @@ -224,7 +224,10 @@ "message": "Wygląda na to, że jeszcze nie korzystają z npmx. Chcesz ich powiadomić?", "share_button": "Podziel się na Bluesky", "compose_text": "Hej {'@'}{handle}! Czy znasz już npmx.dev? To szybka, nowoczesna przeglądarka rejestru npm z otwartym kodem źródłowym.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Publiczne zainteresowania pakietami i sygnały aktywności.", + "likes_error": "Nie udało się wczytać polubionych pakietów.", + "likes_empty": "Brak polubionych pakietów." }, "package": { "not_found": "Nie znaleziono pakietu", diff --git a/i18n/locales/pt-BR.json b/i18n/locales/pt-BR.json index 935b61c5ec..a32f720e61 100644 --- a/i18n/locales/pt-BR.json +++ b/i18n/locales/pt-BR.json @@ -355,7 +355,10 @@ "message": "Parece que eles ainda não estão usando o npmx. Quer contar a eles sobre isso?", "share_button": "Compartilhar no Bluesky", "compose_text": "Olá, {'@'}{handle}! Você já conferiu npmx.dev? É um navegador para o registro npm rápido, moderno e de código aberto.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Interesses públicos de pacotes e sinais de atividade.", + "likes_error": "Não foi possível carregar os pacotes curtidos.", + "likes_empty": "Nenhum pacote curtido ainda." }, "package": { "not_found": "Pacote não encontrado", diff --git a/i18n/locales/ru-RU.json b/i18n/locales/ru-RU.json index 66dc858a5a..bc9adaa933 100644 --- a/i18n/locales/ru-RU.json +++ b/i18n/locales/ru-RU.json @@ -360,7 +360,10 @@ "message": "Похоже, этот пользователь ещё не пользуется npmx. Хотите рассказать ему о проекте?", "share_button": "Поделиться в Bluesky", "compose_text": "Привет, {'@'}{handle}! Уже смотрел npmx.dev? Это быстрый, современный и open-source браузер для реестра npm.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Публичные интересы к пакетам и сигналы активности.", + "likes_error": "Не удалось загрузить понравившиеся пакеты.", + "likes_empty": "Понравившихся пакетов пока нет." }, "package": { "not_found": "Пакет не найден", diff --git a/i18n/locales/sr-Latn-RS.json b/i18n/locales/sr-Latn-RS.json index c015089b8c..e7c98216c7 100644 --- a/i18n/locales/sr-Latn-RS.json +++ b/i18n/locales/sr-Latn-RS.json @@ -244,7 +244,10 @@ "message": "Izgleda da još uvek ne koriste npmx. Želite li da im kažete nešto više o tome?", "share_button": "Podelite na Bluesky-u", "compose_text": "Hej {'@'}{handle}! Da li ste već pogledali npmx.dev? To je pretraživač za npm registar koji je brz, moderan i otvorenog koda.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Javna interesovanja za pakete i signali aktivnosti.", + "likes_error": "Nije moguće učitati omiljene pakete.", + "likes_empty": "Još nema omiljenih paketa." }, "package": { "not_found": "Paket nije pronađen", diff --git a/i18n/locales/ta-IN.json b/i18n/locales/ta-IN.json index e8b657110c..fd8b7cdbf1 100644 --- a/i18n/locales/ta-IN.json +++ b/i18n/locales/ta-IN.json @@ -154,7 +154,10 @@ } }, "profile": { - "invite": {} + "invite": {}, + "public_interests_description": "பொதுப் தொகுப்பு விருப்பங்கள் மற்றும் செயல்பாட்டு சிக்னல்கள்.", + "likes_error": "விரும்பிய தொகுப்புகளை ஏற்ற முடியவில்லை.", + "likes_empty": "இன்னும் விரும்பிய தொகுப்புகள் இல்லை." }, "package": { "not_found": "தொகுப்பு கிடைக்கவில்லை", diff --git a/i18n/locales/te-IN.json b/i18n/locales/te-IN.json index 7e92347d2e..3b1fd100ae 100644 --- a/i18n/locales/te-IN.json +++ b/i18n/locales/te-IN.json @@ -122,7 +122,10 @@ } }, "profile": { - "invite": {} + "invite": {}, + "public_interests_description": "Public package interests and activity signals.", + "likes_error": "Could not load liked packages.", + "likes_empty": "No liked packages yet." }, "package": { "not_found": "ప్యాకేజ్ కనుగొనబడలేదు", diff --git a/i18n/locales/tr-TR.json b/i18n/locales/tr-TR.json index 963fed0027..959564eac2 100644 --- a/i18n/locales/tr-TR.json +++ b/i18n/locales/tr-TR.json @@ -214,7 +214,10 @@ "message": "npmx'i deneyin - npm için daha iyi bir paket tarayıcısı", "share_button": "Paylaş", "compose_text": "npmx'i deneyin - npm için daha iyi bir paket tarayıcısı: {url}" - } + }, + "public_interests_description": "Genel paket ilgi alanları ve aktivite sinyalleri.", + "likes_error": "Beğenilen paketler yüklenemedi.", + "likes_empty": "Henüz beğenilen paket yok." }, "package": { "not_found": "Paket Bulunamadı", diff --git a/i18n/locales/uk-UA.json b/i18n/locales/uk-UA.json index 336c409e1b..b229774241 100644 --- a/i18n/locales/uk-UA.json +++ b/i18n/locales/uk-UA.json @@ -244,7 +244,10 @@ "message": "Схоже, вони ще не користуються npmx. Хочете розповісти їм про нього?", "share_button": "Поділитися в Bluesky", "compose_text": "Привіт, {'@'}{handle}! Ти вже перевірив npmx.dev? Це швидкий сучасний браузер для реєстру npm з відкритим кодом.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Публічні інтереси до пакетів та сигнали активності.", + "likes_error": "Не вдалося завантажити вподобані пакети.", + "likes_empty": "Вподобаних пакетів поки немає." }, "package": { "not_found": "Пакет не знайдено", diff --git a/i18n/locales/vi-VN.json b/i18n/locales/vi-VN.json index d9a5698fa2..10721d5f92 100644 --- a/i18n/locales/vi-VN.json +++ b/i18n/locales/vi-VN.json @@ -244,7 +244,10 @@ "message": "Có vẻ họ chưa dùng npmx. Bạn có muốn giới thiệu cho họ không?", "share_button": "Chia sẻ trên Bluesky", "compose_text": "Chào {'@'}{handle}! Bạn đã thử npmx.dev chưa? Đây là trình duyệt cho npm registry, nhanh, hiện đại và mã nguồn mở.\nhttps://npmx.dev" - } + }, + "public_interests_description": "Sở thích package công khai và tín hiệu hoạt động.", + "likes_error": "Không thể tải các package đã thích.", + "likes_empty": "Chưa có package nào được thích." }, "package": { "not_found": "Không tìm thấy gói", diff --git a/i18n/locales/zh-CN.json b/i18n/locales/zh-CN.json index 559283c623..9b99aee730 100644 --- a/i18n/locales/zh-CN.json +++ b/i18n/locales/zh-CN.json @@ -359,7 +359,10 @@ "message": "看起来他们还没有使用 npmx,去邀请一下?", "share_button": "分享到 Bluesky", "compose_text": "嗨 {'@'}{handle}!您用过 npmx.dev 吗?它是一个快速、现代且开源的 npm registry 浏览器。\nhttps://npmx.dev" - } + }, + "public_interests_description": "公开的包兴趣和活动信号。", + "likes_error": "无法加载喜欢的包。", + "likes_empty": "还没有喜欢的包。" }, "package": { "not_found": "未找到包", diff --git a/i18n/locales/zh-TW.json b/i18n/locales/zh-TW.json index 623e3955fe..7843986523 100644 --- a/i18n/locales/zh-TW.json +++ b/i18n/locales/zh-TW.json @@ -357,7 +357,10 @@ "message": "看起來對方還沒在用 npmx。要不要跟他們分享一下?", "share_button": "分享到 Bluesky", "compose_text": "Hey {'@'}{handle}!你用過 npmx.dev 了嗎?它是 npm Registry 的瀏覽器,快速、現代,而且是開源的。\nhttps://npmx.dev" - } + }, + "public_interests_description": "公開套件興趣與活動訊號。", + "likes_error": "無法載入喜歡的套件。", + "likes_empty": "目前尚無喜歡的套件。" }, "package": { "not_found": "找不到套件", diff --git a/i18n/schema.json b/i18n/schema.json index f06f2f53a8..3f7d803d9a 100644 --- a/i18n/schema.json +++ b/i18n/schema.json @@ -1069,6 +1069,15 @@ "likes": { "type": "string" }, + "likes_error": { + "type": "string" + }, + "likes_empty": { + "type": "string" + }, + "public_interests_description": { + "type": "string" + }, "seo_title": { "type": "string" }, diff --git a/server/api/keytrace/[domain].ts b/server/api/keytrace/[domain].ts new file mode 100644 index 0000000000..b2781daa39 --- /dev/null +++ b/server/api/keytrace/[domain].ts @@ -0,0 +1,125 @@ +import type { KeytraceResponse } from "#shared/types/keytrace"; + +const MOCK_KEYTRACE_PROFILES: Record = { + "npmx.dev": { + profile: { + name: "npmx Team", + avatar: "https://api.dicebear.com/9.x/shapes/svg?seed=npmx", + banner: + "https://images.unsplash.com/photo-1515879218367-8466d910aaa4?auto=format&fit=crop&w=1400&q=80", + description: + "Open source developers building better tooling around package discovery.", + }, + accounts: [ + { + platform: "github", + username: "npmx-dev", + displayName: "npmx-dev", + avatar: "https://avatars.githubusercontent.com/u/178563400?v=4", + url: "https://github.com/npmx-dev", + status: "verified", + proofMethod: "github", + addedAt: "2026-03-10T12:00:00.000Z", + lastCheckedAt: "2026-04-20T09:30:00.000Z", + }, + { + platform: "npm", + username: "npmx", + displayName: "npmx", + avatar: "https://api.dicebear.com/9.x/identicon/svg?seed=npmx", + url: "https://www.npmjs.com/~npmx", + status: "stale", + proofMethod: "npm", + addedAt: "2026-02-18T15:20:00.000Z", + lastCheckedAt: "2026-03-05T08:00:00.000Z", + failureReason: "Proof has not been re-verified recently.", + }, + { + platform: "mastodon", + username: "@npmx@fosstodon.org", + displayName: "npmx", + avatar: + "https://api.dicebear.com/9.x/identicon/svg?seed=fosstodon-npmx", + url: "https://fosstodon.org/@npmx", + status: "failed", + proofMethod: "mastodon", + addedAt: "2026-01-22T11:40:00.000Z", + lastCheckedAt: "2026-04-19T22:15:00.000Z", + failureReason: + "Linked proof URL could not be resolved during verification.", + }, + ], + }, + "empty.dev": { + profile: { + name: "Empty Developer", + avatar: "https://api.dicebear.com/9.x/initials/svg?seed=empty.dev", + description: "A profile with no linked accounts yet.", + }, + accounts: [], + }, +}; + +function domainToDisplayName(domain: string): string { + const firstSegment = domain.split(".")[0] || domain; + return firstSegment.charAt(0).toUpperCase() + firstSegment.slice(1); +} + +function buildFallbackProfile(domain: string): KeytraceResponse { + return { + profile: { + name: `${domainToDisplayName(domain)} Developer`, + avatar: `https://api.dicebear.com/9.x/initials/svg?seed=${encodeURIComponent(domain)}`, + description: `Mock identity profile for ${domain}.`, + }, + accounts: [ + { + platform: "github", + username: domain, + displayName: domain, + avatar: `https://api.dicebear.com/9.x/identicon/svg?seed=github-${encodeURIComponent(domain)}`, + url: `https://github.com/${domain}`, + status: "verified", + proofMethod: "github", + addedAt: "2026-04-01T10:00:00.000Z", + lastCheckedAt: "2026-04-21T10:00:00.000Z", + }, + { + platform: "npm", + username: domain, + displayName: domain, + avatar: `https://api.dicebear.com/9.x/identicon/svg?seed=npm-${encodeURIComponent(domain)}`, + url: `https://www.npmjs.com/~${domain}`, + status: "unverified", + proofMethod: "npm", + addedAt: "2026-04-01T10:00:00.000Z", + lastCheckedAt: "2026-04-01T10:00:00.000Z", + failureReason: "Proof exists but has not been verified yet.", + }, + { + platform: "mastodon", + username: `@${domain}@mastodon.social`, + displayName: domain, + avatar: `https://api.dicebear.com/9.x/identicon/svg?seed=mastodon-${encodeURIComponent(domain)}`, + url: `https://mastodon.social/@${encodeURIComponent(domain)}`, + status: "stale", + proofMethod: "mastodon", + addedAt: "2026-04-01T10:00:00.000Z", + lastCheckedAt: "2026-04-08T10:00:00.000Z", + failureReason: "Verification check is out of date.", + }, + ], + }; +} + +export default defineEventHandler((event) => { + const domain = getRouterParam(event, "domain")?.trim().toLowerCase(); + if (!domain) { + throw createError({ + statusCode: 400, + message: "Domain is required", + }); + } + + return MOCK_KEYTRACE_PROFILES[domain] ?? buildFallbackProfile(domain); +}); diff --git a/server/api/keytrace/reverify.post.ts b/server/api/keytrace/reverify.post.ts new file mode 100644 index 0000000000..9030cd85b2 --- /dev/null +++ b/server/api/keytrace/reverify.post.ts @@ -0,0 +1,30 @@ +import type { + KeytraceReverifyRequest, + KeytraceReverifyResponse, +} from "#shared/types/keytrace"; + +export default defineEventHandler(async (event) => { + const body = await readBody(event); + + const platform = body?.platform?.trim().toLowerCase(); + const username = body?.username?.trim(); + + if (!platform || !username) { + throw createError({ + statusCode: 400, + message: "platform and username are required", + }); + } + + const lastCheckedAt = new Date().toISOString(); + + // TODO: Implement per-platform proof verification for KeytraceReverifyResponse + // construction here (including mastodon and npm short-username logic), ideally + // via a verifyProofForPlatform(platform, username, proofData) helper. + const response: KeytraceReverifyResponse = { + status: "unverified", + lastCheckedAt, + failureReason: "Proof verification is not implemented yet.", + }; + return response; +}); diff --git a/shared/types/keytrace.ts b/shared/types/keytrace.ts new file mode 100644 index 0000000000..859974ff42 --- /dev/null +++ b/shared/types/keytrace.ts @@ -0,0 +1,50 @@ +export type KeytraceProfile = { + name: string; + avatar: string; + banner?: string; + description: string; +}; + +export type KeytraceVerificationStatus = + | "verified" + | "unverified" + | "stale" + | "failed"; + +export type KeytraceProofMethod = + | "dns" + | "github" + | "npm" + | "mastodon" + | "pgp" + | "other"; + +export type KeytraceAccount = { + platform: string; + username: string; + displayName?: string; + avatar?: string; + url?: string; + status: KeytraceVerificationStatus; + proofMethod: KeytraceProofMethod; + addedAt: string; + lastCheckedAt: string; + failureReason?: string; +}; + +export type KeytraceResponse = { + profile: KeytraceProfile; + accounts: KeytraceAccount[]; +}; + +export type KeytraceReverifyRequest = { + platform: string; + username: string; + url?: string; +}; + +export type KeytraceReverifyResponse = { + status: KeytraceVerificationStatus; + lastCheckedAt: string; + failureReason?: string; +}; From 5043d74f9e028c6395e411f78b67b817bc8cf4ef Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 23 Apr 2026 12:22:05 +0530 Subject: [PATCH 02/10] fix: refactor code by formatting the components and types --- app/components/Compare/FacetSelector.vue | 6 +- app/composables/useKeytraceProfile.ts | 36 +++--- server/api/keytrace/[domain].ts | 135 +++++++++++------------ server/api/keytrace/reverify.post.ts | 29 +++-- shared/types/keytrace.ts | 68 +++++------- 5 files changed, 131 insertions(+), 143 deletions(-) diff --git a/app/components/Compare/FacetSelector.vue b/app/components/Compare/FacetSelector.vue index a66d6116e0..3a30a603c6 100644 --- a/app/components/Compare/FacetSelector.vue +++ b/app/components/Compare/FacetSelector.vue @@ -106,7 +106,11 @@ function deselectAllFacet(category: string) { :key="facet.id" :title="facet.comingSoon ? $t('compare.facets.coming_soon') : facet.description" class="inline-flex items-center gap-1 px-1.5 rounded border transition-colors text-fg-subtle bg-bg-subtle border-border-subtle hover:(text-fg-muted border-border) has-[:checked]:(text-fg-muted bg-fg/10 border-fg/20 hover:(bg-fg/20 text-fg/50)) has-[:focus-visible]:(outline outline-accent/70)" - :class="facet.comingSoon ? 'cursor-not-allowed text-fg-subtle/50 bg-bg-subtle border-border-subtle hover:(text-fg-subtle/50 border-border-subtle)' : 'cursor-pointer'" + :class=" + facet.comingSoon + ? 'cursor-not-allowed text-fg-subtle/50 bg-bg-subtle border-border-subtle hover:(text-fg-subtle/50 border-border-subtle)' + : 'cursor-pointer' + " > = { +const statusPriority: Record = { verified: 0, unverified: 1, stale: 2, failed: 3, -}; +} export function useKeytraceProfile(domain: MaybeRefOrGetter) { const asyncData = useFetch( @@ -13,36 +13,36 @@ export function useKeytraceProfile(domain: MaybeRefOrGetter) { { default: () => ({ profile: { - name: "", - avatar: "", - description: "", + name: '', + avatar: '', + description: '', }, accounts: [], }), }, - ); + ) - const profile = computed(() => asyncData.data.value?.profile); - const accounts = computed(() => asyncData.data.value?.accounts ?? []); + const profile = computed(() => asyncData.data.value?.profile) + const accounts = computed(() => asyncData.data.value?.accounts ?? []) const sortedAccounts = computed(() => [...accounts.value].sort((a, b) => { - const statusSort = statusPriority[a.status] - statusPriority[b.status]; + const statusSort = statusPriority[a.status] - statusPriority[b.status] if (statusSort !== 0) { - return statusSort; + return statusSort } - return a.platform.localeCompare(b.platform); + return a.platform.localeCompare(b.platform) }), - ); + ) const verifiedAccounts = computed(() => - sortedAccounts.value.filter((account) => account.status === "verified"), - ); + sortedAccounts.value.filter(account => account.status === 'verified'), + ) const nonVerifiedAccounts = computed(() => - sortedAccounts.value.filter((account) => account.status !== "verified"), - ); + sortedAccounts.value.filter(account => account.status !== 'verified'), + ) return { profile, @@ -50,5 +50,5 @@ export function useKeytraceProfile(domain: MaybeRefOrGetter) { verifiedAccounts, nonVerifiedAccounts, loading: asyncData.pending, - }; + } } diff --git a/server/api/keytrace/[domain].ts b/server/api/keytrace/[domain].ts index b2781daa39..2b2e20e478 100644 --- a/server/api/keytrace/[domain].ts +++ b/server/api/keytrace/[domain].ts @@ -1,68 +1,65 @@ -import type { KeytraceResponse } from "#shared/types/keytrace"; +import type { KeytraceResponse } from '#shared/types/keytrace' const MOCK_KEYTRACE_PROFILES: Record = { - "npmx.dev": { + 'npmx.dev': { profile: { - name: "npmx Team", - avatar: "https://api.dicebear.com/9.x/shapes/svg?seed=npmx", + name: 'npmx Team', + avatar: 'https://api.dicebear.com/9.x/shapes/svg?seed=npmx', banner: - "https://images.unsplash.com/photo-1515879218367-8466d910aaa4?auto=format&fit=crop&w=1400&q=80", - description: - "Open source developers building better tooling around package discovery.", + 'https://images.unsplash.com/photo-1515879218367-8466d910aaa4?auto=format&fit=crop&w=1400&q=80', + description: 'Open source developers building better tooling around package discovery.', }, accounts: [ { - platform: "github", - username: "npmx-dev", - displayName: "npmx-dev", - avatar: "https://avatars.githubusercontent.com/u/178563400?v=4", - url: "https://github.com/npmx-dev", - status: "verified", - proofMethod: "github", - addedAt: "2026-03-10T12:00:00.000Z", - lastCheckedAt: "2026-04-20T09:30:00.000Z", + platform: 'github', + username: 'npmx-dev', + displayName: 'npmx-dev', + avatar: 'https://avatars.githubusercontent.com/u/178563400?v=4', + url: 'https://github.com/npmx-dev', + status: 'verified', + proofMethod: 'github', + addedAt: '2026-03-10T12:00:00.000Z', + lastCheckedAt: '2026-04-20T09:30:00.000Z', }, { - platform: "npm", - username: "npmx", - displayName: "npmx", - avatar: "https://api.dicebear.com/9.x/identicon/svg?seed=npmx", - url: "https://www.npmjs.com/~npmx", - status: "stale", - proofMethod: "npm", - addedAt: "2026-02-18T15:20:00.000Z", - lastCheckedAt: "2026-03-05T08:00:00.000Z", - failureReason: "Proof has not been re-verified recently.", + platform: 'npm', + username: 'npmx', + displayName: 'npmx', + avatar: 'https://api.dicebear.com/9.x/identicon/svg?seed=npmx', + url: 'https://www.npmjs.com/~npmx', + status: 'stale', + proofMethod: 'npm', + addedAt: '2026-02-18T15:20:00.000Z', + lastCheckedAt: '2026-03-05T08:00:00.000Z', + failureReason: 'Proof has not been re-verified recently.', }, { - platform: "mastodon", - username: "@npmx@fosstodon.org", - displayName: "npmx", - avatar: - "https://api.dicebear.com/9.x/identicon/svg?seed=fosstodon-npmx", - url: "https://fosstodon.org/@npmx", - status: "failed", - proofMethod: "mastodon", - addedAt: "2026-01-22T11:40:00.000Z", - lastCheckedAt: "2026-04-19T22:15:00.000Z", - failureReason: - "Linked proof URL could not be resolved during verification.", + platform: 'mastodon', + username: '@npmx@fosstodon.org', + displayName: 'npmx', + avatar: 'https://api.dicebear.com/9.x/identicon/svg?seed=fosstodon-npmx', + url: 'https://fosstodon.org/@npmx', + status: 'failed', + proofMethod: 'mastodon', + addedAt: '2026-01-22T11:40:00.000Z', + lastCheckedAt: '2026-04-19T22:15:00.000Z', + failureReason: 'Linked proof URL could not be resolved during verification.', }, ], }, - "empty.dev": { + 'empty.dev': { profile: { - name: "Empty Developer", - avatar: "https://api.dicebear.com/9.x/initials/svg?seed=empty.dev", - description: "A profile with no linked accounts yet.", + name: 'Empty Developer', + avatar: 'https://api.dicebear.com/9.x/initials/svg?seed=empty.dev', + description: 'A profile with no linked accounts yet.', }, accounts: [], }, -}; +} function domainToDisplayName(domain: string): string { - const firstSegment = domain.split(".")[0] || domain; - return firstSegment.charAt(0).toUpperCase() + firstSegment.slice(1); + const firstSegment = domain.split('.')[0] || domain + return firstSegment.charAt(0).toUpperCase() + firstSegment.slice(1) } function buildFallbackProfile(domain: string): KeytraceResponse { @@ -74,52 +71,52 @@ function buildFallbackProfile(domain: string): KeytraceResponse { }, accounts: [ { - platform: "github", + platform: 'github', username: domain, displayName: domain, avatar: `https://api.dicebear.com/9.x/identicon/svg?seed=github-${encodeURIComponent(domain)}`, url: `https://github.com/${domain}`, - status: "verified", - proofMethod: "github", - addedAt: "2026-04-01T10:00:00.000Z", - lastCheckedAt: "2026-04-21T10:00:00.000Z", + status: 'verified', + proofMethod: 'github', + addedAt: '2026-04-01T10:00:00.000Z', + lastCheckedAt: '2026-04-21T10:00:00.000Z', }, { - platform: "npm", + platform: 'npm', username: domain, displayName: domain, avatar: `https://api.dicebear.com/9.x/identicon/svg?seed=npm-${encodeURIComponent(domain)}`, url: `https://www.npmjs.com/~${domain}`, - status: "unverified", - proofMethod: "npm", - addedAt: "2026-04-01T10:00:00.000Z", - lastCheckedAt: "2026-04-01T10:00:00.000Z", - failureReason: "Proof exists but has not been verified yet.", + status: 'unverified', + proofMethod: 'npm', + addedAt: '2026-04-01T10:00:00.000Z', + lastCheckedAt: '2026-04-01T10:00:00.000Z', + failureReason: 'Proof exists but has not been verified yet.', }, { - platform: "mastodon", + platform: 'mastodon', username: `@${domain}@mastodon.social`, displayName: domain, avatar: `https://api.dicebear.com/9.x/identicon/svg?seed=mastodon-${encodeURIComponent(domain)}`, url: `https://mastodon.social/@${encodeURIComponent(domain)}`, - status: "stale", - proofMethod: "mastodon", - addedAt: "2026-04-01T10:00:00.000Z", - lastCheckedAt: "2026-04-08T10:00:00.000Z", - failureReason: "Verification check is out of date.", + status: 'stale', + proofMethod: 'mastodon', + addedAt: '2026-04-01T10:00:00.000Z', + lastCheckedAt: '2026-04-08T10:00:00.000Z', + failureReason: 'Verification check is out of date.', }, ], - }; + } } -export default defineEventHandler((event) => { - const domain = getRouterParam(event, "domain")?.trim().toLowerCase(); +export default defineEventHandler(event => { + const domain = getRouterParam(event, 'domain')?.trim().toLowerCase() if (!domain) { throw createError({ statusCode: 400, - message: "Domain is required", - }); + message: 'Domain is required', + }) } - return MOCK_KEYTRACE_PROFILES[domain] ?? buildFallbackProfile(domain); -}); + return MOCK_KEYTRACE_PROFILES[domain] ?? buildFallbackProfile(domain) +}) diff --git a/server/api/keytrace/reverify.post.ts b/server/api/keytrace/reverify.post.ts index 9030cd85b2..a85341aa16 100644 --- a/server/api/keytrace/reverify.post.ts +++ b/server/api/keytrace/reverify.post.ts @@ -1,30 +1,27 @@ -import type { - KeytraceReverifyRequest, - KeytraceReverifyResponse, -} from "#shared/types/keytrace"; +import type { KeytraceReverifyRequest, KeytraceReverifyResponse } from '#shared/types/keytrace' -export default defineEventHandler(async (event) => { - const body = await readBody(event); +export default defineEventHandler(async event => { + const body = await readBody(event) - const platform = body?.platform?.trim().toLowerCase(); - const username = body?.username?.trim(); + const platform = body?.platform?.trim().toLowerCase() + const username = body?.username?.trim() if (!platform || !username) { throw createError({ statusCode: 400, - message: "platform and username are required", - }); + message: 'platform and username are required', + }) } - const lastCheckedAt = new Date().toISOString(); + const lastCheckedAt = new Date().toISOString() // TODO: Implement per-platform proof verification for KeytraceReverifyResponse // construction here (including mastodon and npm short-username logic), ideally // via a verifyProofForPlatform(platform, username, proofData) helper. const response: KeytraceReverifyResponse = { - status: "unverified", + status: 'unverified', lastCheckedAt, - failureReason: "Proof verification is not implemented yet.", - }; - return response; -}); + failureReason: 'Proof verification is not implemented yet.', + } + return response +}) diff --git a/shared/types/keytrace.ts b/shared/types/keytrace.ts index 859974ff42..2219e732ad 100644 --- a/shared/types/keytrace.ts +++ b/shared/types/keytrace.ts @@ -1,50 +1,40 @@ export type KeytraceProfile = { - name: string; - avatar: string; - banner?: string; - description: string; -}; + name: string + avatar: string + banner?: string + description: string +} -export type KeytraceVerificationStatus = - | "verified" - | "unverified" - | "stale" - | "failed"; +export type KeytraceVerificationStatus = 'verified' | 'unverified' | 'stale' | 'failed' -export type KeytraceProofMethod = - | "dns" - | "github" - | "npm" - | "mastodon" - | "pgp" - | "other"; +export type KeytraceProofMethod = 'dns' | 'github' | 'npm' | 'mastodon' | 'pgp' | 'other' export type KeytraceAccount = { - platform: string; - username: string; - displayName?: string; - avatar?: string; - url?: string; - status: KeytraceVerificationStatus; - proofMethod: KeytraceProofMethod; - addedAt: string; - lastCheckedAt: string; - failureReason?: string; -}; + platform: string + username: string + displayName?: string + avatar?: string + url?: string + status: KeytraceVerificationStatus + proofMethod: KeytraceProofMethod + addedAt: string + lastCheckedAt: string + failureReason?: string +} export type KeytraceResponse = { - profile: KeytraceProfile; - accounts: KeytraceAccount[]; -}; + profile: KeytraceProfile + accounts: KeytraceAccount[] +} export type KeytraceReverifyRequest = { - platform: string; - username: string; - url?: string; -}; + platform: string + username: string + url?: string +} export type KeytraceReverifyResponse = { - status: KeytraceVerificationStatus; - lastCheckedAt: string; - failureReason?: string; -}; + status: KeytraceVerificationStatus + lastCheckedAt: string + failureReason?: string +} From d44a19971e91c5b372f0466ec0aef1a9ab1c705a Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 23 Apr 2026 13:37:57 +0530 Subject: [PATCH 03/10] test: fix unit test --- test/nuxt/a11y.spec.ts | 63 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test/nuxt/a11y.spec.ts b/test/nuxt/a11y.spec.ts index aa16620760..e81af4c8d6 100644 --- a/test/nuxt/a11y.spec.ts +++ b/test/nuxt/a11y.spec.ts @@ -271,6 +271,9 @@ import FacetScatterChart from '~/components/Compare/FacetScatterChart.vue' import PackageLikeCard from '~/components/Package/LikeCard.vue' import SizeIncrease from '~/components/Package/SizeIncrease.vue' import Likes from '~/components/Package/Likes.vue' +import AccountItem from '~/components/AccountItem.vue' +import LinkedAccounts from '~/components/LinkedAccounts.vue' +import ProfileHeader from '~/components/ProfileHeader.vue' import type { VueUiXyDatasetItem } from 'vue-data-ui' describe('component accessibility audits', () => { @@ -904,6 +907,66 @@ describe('component accessibility audits', () => { }) }) + describe('ProfileHeader', () => { + it('should have no accessibility violations with profile data', async () => { + const component = await mountSuspended(ProfileHeader, { + props: { + profile: { + name: 'npmx Team', + avatar: 'https://api.dicebear.com/9.x/shapes/svg?seed=npmx', + description: 'Open source maintainers', + }, + }, + }) + const results = await runAxe(component) + expect(results.violations).toEqual([]) + }) + }) + + describe('LinkedAccounts', () => { + it('should have no accessibility violations with account list', async () => { + const component = await mountSuspended(LinkedAccounts, { + props: { + accounts: [ + { + platform: 'github', + username: 'npmx-dev', + displayName: 'npmx-dev', + status: 'verified', + proofMethod: 'github', + addedAt: '2026-04-01T10:00:00.000Z', + lastCheckedAt: '2026-04-21T10:00:00.000Z', + url: 'https://github.com/npmx-dev', + }, + ], + }, + }) + const results = await runAxe(component) + expect(results.violations).toEqual([]) + }) + }) + + describe('AccountItem', () => { + it('should have no accessibility violations', async () => { + const component = await mountSuspended(AccountItem, { + props: { + account: { + platform: 'github', + username: 'npmx-dev', + displayName: 'npmx-dev', + status: 'verified', + proofMethod: 'github', + addedAt: '2026-04-01T10:00:00.000Z', + lastCheckedAt: '2026-04-21T10:00:00.000Z', + url: 'https://github.com/npmx-dev', + }, + }, + }) + const results = await runAxe(component) + expect(results.violations).toEqual([]) + }) + }) + describe('PackageHeader', () => { it('should have no accessibility violations', async () => { const component = await mountSuspended(PackageHeader, { From 67f45f3d2b330380d72dedefc143ffe7e60f0c2e Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 23 Apr 2026 14:57:29 +0530 Subject: [PATCH 04/10] refactor: fix failed components test, remove error translations from locale files --- app/components/AccountItem.vue | 4 ++-- app/components/Compare/FacetSelector.vue | 17 +++++++---------- i18n/locales/ar-EG.json | 1 - i18n/locales/az-AZ.json | 1 - i18n/locales/bg-BG.json | 1 - i18n/locales/cs-CZ.json | 1 - i18n/locales/de.json | 1 - i18n/locales/en.json | 1 - i18n/locales/es.json | 1 - i18n/locales/fr-FR.json | 1 - i18n/locales/hi-IN.json | 1 - i18n/locales/hu-HU.json | 1 - i18n/locales/id-ID.json | 1 - i18n/locales/ja-JP.json | 1 - i18n/locales/mr-IN.json | 1 - i18n/locales/nb-NO.json | 1 - i18n/locales/nl.json | 1 - i18n/locales/pl-PL.json | 1 - i18n/locales/pt-BR.json | 1 - i18n/locales/ru-RU.json | 1 - i18n/locales/sr-Latn-RS.json | 1 - i18n/locales/tr-TR.json | 1 - i18n/locales/uk-UA.json | 1 - i18n/locales/vi-VN.json | 1 - i18n/locales/zh-CN.json | 1 - i18n/locales/zh-TW.json | 1 - 26 files changed, 9 insertions(+), 36 deletions(-) diff --git a/app/components/AccountItem.vue b/app/components/AccountItem.vue index 25ac23b149..5e151bbc83 100644 --- a/app/components/AccountItem.vue +++ b/app/components/AccountItem.vue @@ -229,7 +229,7 @@ function formatDate(value: string): string { diff --git a/app/components/Compare/FacetSelector.vue b/app/components/Compare/FacetSelector.vue index 3a30a603c6..da093c47f3 100644 --- a/app/components/Compare/FacetSelector.vue +++ b/app/components/Compare/FacetSelector.vue @@ -101,24 +101,21 @@ function deselectAllFacet(category: string) { :aria-labelledby="`facet-category-label-${category}`" data-facet-category-facets > - +
diff --git a/i18n/locales/ar-EG.json b/i18n/locales/ar-EG.json index 83ce5e2dcb..19a25e964e 100644 --- a/i18n/locales/ar-EG.json +++ b/i18n/locales/ar-EG.json @@ -82,7 +82,6 @@ "cancel": "إلغاء", "save": "حفظ", "edit": "تعديل", - "error": "خطأ", "view_on": { "gitlab": "عرض على GitLab", "bitbucket": "عرض على Bitbucket", diff --git a/i18n/locales/az-AZ.json b/i18n/locales/az-AZ.json index d21379292f..b2d7fc3897 100644 --- a/i18n/locales/az-AZ.json +++ b/i18n/locales/az-AZ.json @@ -193,7 +193,6 @@ "cancel": "Ləğv et", "save": "Saxla", "edit": "Redaktə et", - "error": "Xəta", "view_on": { "npm": "npm-də bax", "github": "GitHub-da bax" diff --git a/i18n/locales/bg-BG.json b/i18n/locales/bg-BG.json index daa106948f..03ec65790c 100644 --- a/i18n/locales/bg-BG.json +++ b/i18n/locales/bg-BG.json @@ -156,7 +156,6 @@ "cancel": "Отказ", "save": "Запазване", "edit": "Редактиране", - "error": "Грешка", "view_on": { "npm": "преглед в npm", "github": "Преглед в GitHub" diff --git a/i18n/locales/cs-CZ.json b/i18n/locales/cs-CZ.json index 84cbc676be..67bea763db 100644 --- a/i18n/locales/cs-CZ.json +++ b/i18n/locales/cs-CZ.json @@ -323,7 +323,6 @@ "cancel": "Zrušit", "save": "Uložit", "edit": "Upravit", - "error": "Chyba", "view_on": { "npm": "Zobrazit na npm", "github": "Zobrazit na GitHubu", diff --git a/i18n/locales/de.json b/i18n/locales/de.json index 1a29179809..e191cee94b 100644 --- a/i18n/locales/de.json +++ b/i18n/locales/de.json @@ -322,7 +322,6 @@ "cancel": "Abbrechen", "save": "Speichern", "edit": "Bearbeiten", - "error": "Fehler", "view_on": { "npm": "Auf npm ansehen", "github": "Auf GitHub ansehen", diff --git a/i18n/locales/en.json b/i18n/locales/en.json index 5e31a60a21..924e5eaa74 100644 --- a/i18n/locales/en.json +++ b/i18n/locales/en.json @@ -327,7 +327,6 @@ "cancel": "Cancel", "save": "Save", "edit": "Edit", - "error": "Error", "view_on": { "npm": "View on npm", "github": "View on GitHub", diff --git a/i18n/locales/es.json b/i18n/locales/es.json index a723b3e57c..ddce4c624b 100644 --- a/i18n/locales/es.json +++ b/i18n/locales/es.json @@ -211,7 +211,6 @@ "cancel": "Cancelar", "save": "Guardar", "edit": "Editar", - "error": "Error", "view_on": { "npm": "ver en npm", "github": "Ver en GitHub", diff --git a/i18n/locales/fr-FR.json b/i18n/locales/fr-FR.json index 01c18381a0..2c814d635a 100644 --- a/i18n/locales/fr-FR.json +++ b/i18n/locales/fr-FR.json @@ -324,7 +324,6 @@ "cancel": "Annuler", "save": "Enregistrer", "edit": "Modifier", - "error": "Erreur", "view_on": { "npm": "voir sur npm", "github": "Voir sur GitHub", diff --git a/i18n/locales/hi-IN.json b/i18n/locales/hi-IN.json index 18391f03c2..dff0bb6943 100644 --- a/i18n/locales/hi-IN.json +++ b/i18n/locales/hi-IN.json @@ -210,7 +210,6 @@ "cancel": "रद्द करें", "save": "सहेजें", "edit": "संपादित करें", - "error": "त्रुटि", "view_on": { "npm": "npm पर देखें", "github": "GitHub पर देखें", diff --git a/i18n/locales/hu-HU.json b/i18n/locales/hu-HU.json index 0b391fa6f2..f7eb646ef1 100644 --- a/i18n/locales/hu-HU.json +++ b/i18n/locales/hu-HU.json @@ -156,7 +156,6 @@ "cancel": "Mégse", "save": "Mentés", "edit": "Szerkesztés", - "error": "Hiba", "view_on": { "npm": "megtekintés npm-en", "github": "Megtekintés GitHubon" diff --git a/i18n/locales/id-ID.json b/i18n/locales/id-ID.json index 717b761a39..73dcae1fdf 100644 --- a/i18n/locales/id-ID.json +++ b/i18n/locales/id-ID.json @@ -211,7 +211,6 @@ "cancel": "Batal", "save": "Simpan", "edit": "Edit", - "error": "Kesalahan", "view_on": { "npm": "lihat di npm", "github": "Lihat di GitHub", diff --git a/i18n/locales/ja-JP.json b/i18n/locales/ja-JP.json index 63e51d154a..69ba897b2c 100644 --- a/i18n/locales/ja-JP.json +++ b/i18n/locales/ja-JP.json @@ -193,7 +193,6 @@ "cancel": "キャンセル", "save": "保存", "edit": "編集", - "error": "エラー", "view_on": { "npm": "npmで表示", "github": "GitHubで表示", diff --git a/i18n/locales/mr-IN.json b/i18n/locales/mr-IN.json index 7bee21b07b..ed80793975 100644 --- a/i18n/locales/mr-IN.json +++ b/i18n/locales/mr-IN.json @@ -194,7 +194,6 @@ "cancel": "रद्द करा", "save": "जतन करा", "edit": "संपादित करा", - "error": "त्रुटी", "view_on": { "npm": "npm वर पहा", "github": "GitHub वर पहा", diff --git a/i18n/locales/nb-NO.json b/i18n/locales/nb-NO.json index 749c355749..1b21b3d59f 100644 --- a/i18n/locales/nb-NO.json +++ b/i18n/locales/nb-NO.json @@ -322,7 +322,6 @@ "cancel": "Avbryt", "save": "Lagre", "edit": "Rediger", - "error": "Feil", "view_on": { "npm": "vis på npm", "github": "Vis på GitHub", diff --git a/i18n/locales/nl.json b/i18n/locales/nl.json index 45634a2503..4b820c60c9 100644 --- a/i18n/locales/nl.json +++ b/i18n/locales/nl.json @@ -322,7 +322,6 @@ "cancel": "Annuleer", "save": "Opslaan", "edit": "Wijzigen", - "error": "Fout", "view_on": { "npm": "Bekijk op npm", "github": "Bekijk op GitHub", diff --git a/i18n/locales/pl-PL.json b/i18n/locales/pl-PL.json index 85cfd34c32..a009daea5b 100644 --- a/i18n/locales/pl-PL.json +++ b/i18n/locales/pl-PL.json @@ -193,7 +193,6 @@ "cancel": "Anuluj", "save": "Zapisz", "edit": "Edytuj", - "error": "Błąd", "view_on": { "npm": "zobacz na npm", "github": "Zobacz w GitHub", diff --git a/i18n/locales/pt-BR.json b/i18n/locales/pt-BR.json index a32f720e61..6e0e05ec30 100644 --- a/i18n/locales/pt-BR.json +++ b/i18n/locales/pt-BR.json @@ -319,7 +319,6 @@ "cancel": "Cancelar", "save": "Salvar", "edit": "Editar", - "error": "Erro", "view_on": { "npm": "Ver no npm", "github": "Ver no GitHub", diff --git a/i18n/locales/ru-RU.json b/i18n/locales/ru-RU.json index bc9adaa933..55bbb22597 100644 --- a/i18n/locales/ru-RU.json +++ b/i18n/locales/ru-RU.json @@ -324,7 +324,6 @@ "cancel": "Отменить", "save": "Сохранить", "edit": "Изменить", - "error": "Ошибка", "view_on": { "npm": "Открыть на npm", "github": "Открыть на GitHub", diff --git a/i18n/locales/sr-Latn-RS.json b/i18n/locales/sr-Latn-RS.json index e7c98216c7..299f8e922d 100644 --- a/i18n/locales/sr-Latn-RS.json +++ b/i18n/locales/sr-Latn-RS.json @@ -211,7 +211,6 @@ "cancel": "Otkažite", "save": "Sačuvajte", "edit": "Uredite", - "error": "Greška", "view_on": { "npm": "pogledajte na npm-u", "github": "Pogledajte na GitHub-u", diff --git a/i18n/locales/tr-TR.json b/i18n/locales/tr-TR.json index 959564eac2..5508d468d3 100644 --- a/i18n/locales/tr-TR.json +++ b/i18n/locales/tr-TR.json @@ -193,7 +193,6 @@ "cancel": "İptal", "save": "Kaydet", "edit": "Düzenle", - "error": "Hata", "view_on": { "npm": "npm'de görüntüle", "github": "GitHub'da görüntüle" diff --git a/i18n/locales/uk-UA.json b/i18n/locales/uk-UA.json index b229774241..cf78807113 100644 --- a/i18n/locales/uk-UA.json +++ b/i18n/locales/uk-UA.json @@ -211,7 +211,6 @@ "cancel": "Скасувати", "save": "Зберегти", "edit": "Редагувати", - "error": "Помилка", "view_on": { "npm": "Переглянути на npm", "github": "Переглянути на GitHub", diff --git a/i18n/locales/vi-VN.json b/i18n/locales/vi-VN.json index 10721d5f92..73950927c7 100644 --- a/i18n/locales/vi-VN.json +++ b/i18n/locales/vi-VN.json @@ -211,7 +211,6 @@ "cancel": "Hủy", "save": "Lưu", "edit": "Chỉnh sửa", - "error": "Lỗi", "view_on": { "npm": "xem trên npm", "github": "Xem trên GitHub", diff --git a/i18n/locales/zh-CN.json b/i18n/locales/zh-CN.json index 9b99aee730..6854700b65 100644 --- a/i18n/locales/zh-CN.json +++ b/i18n/locales/zh-CN.json @@ -323,7 +323,6 @@ "cancel": "取消", "save": "保存", "edit": "编辑", - "error": "加载出错", "view_on": { "npm": "在 npm 上查看", "github": "在 GitHub 上查看", diff --git a/i18n/locales/zh-TW.json b/i18n/locales/zh-TW.json index 7843986523..4b17ac1cb4 100644 --- a/i18n/locales/zh-TW.json +++ b/i18n/locales/zh-TW.json @@ -322,7 +322,6 @@ "cancel": "取消", "save": "儲存", "edit": "編輯", - "error": "錯誤", "view_on": { "npm": "在 npm 上檢視", "github": "在 GitHub 上檢視", From e72233afe7c78a1b74b226798b66fa2b125e9e3d Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 23 Apr 2026 14:59:54 +0530 Subject: [PATCH 05/10] Update i18n schema after removing unused common.error key --- i18n/schema.json | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/i18n/schema.json b/i18n/schema.json index 3f7d803d9a..a3505b4897 100644 --- a/i18n/schema.json +++ b/i18n/schema.json @@ -985,9 +985,6 @@ "edit": { "type": "string" }, - "error": { - "type": "string" - }, "view_on": { "type": "object", "properties": { @@ -1069,13 +1066,13 @@ "likes": { "type": "string" }, - "likes_error": { + "public_interests_description": { "type": "string" }, - "likes_empty": { + "likes_error": { "type": "string" }, - "public_interests_description": { + "likes_empty": { "type": "string" }, "seo_title": { From 1e2c977de9648c79d114ac2fcbe6b449d5439278 Mon Sep 17 00:00:00 2001 From: Bittu kumar Date: Thu, 23 Apr 2026 15:09:23 +0530 Subject: [PATCH 06/10] Update i18n/locales/de-AT.json Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- i18n/locales/de-AT.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i18n/locales/de-AT.json b/i18n/locales/de-AT.json index daeddeec95..1e748e02cf 100644 --- a/i18n/locales/de-AT.json +++ b/i18n/locales/de-AT.json @@ -24,7 +24,7 @@ }, "profile": { "public_interests_description": "Öffentliche Paketinteressen und Aktivitätssignale.", - "likes_error": "Beliebte Pakete konnten nicht geladen werden.", - "likes_empty": "Noch keine beliebten Pakete vorhanden." + "likes_error": "Mit „Gefällt mir" markierte Pakete konnten nicht geladen werden.", + "likes_empty": "Noch keine mit „Gefällt mir" markierten Pakete." } } From eaf0536790415ed700f18a560fa65267c02d7006 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 23 Apr 2026 20:27:58 +0530 Subject: [PATCH 07/10] fix: failed e2e test --- app/components/AccountItem.vue | 8 ++--- app/components/LinkedAccounts.vue | 49 ++++++++++++++++++++++++------- app/components/ProfileHeader.vue | 4 +-- i18n/locales/en.json | 14 +++++++++ i18n/schema.json | 42 ++++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 16 deletions(-) diff --git a/app/components/AccountItem.vue b/app/components/AccountItem.vue index 5e151bbc83..6b747dac50 100644 --- a/app/components/AccountItem.vue +++ b/app/components/AccountItem.vue @@ -167,6 +167,9 @@ async function reverifyAccount() { body, }) + // Attach rejection handler to prevent unhandled promise rejection warnings + responsePromise.catch(() => {}) + await runStep(0) await runStep(1) await runStep(2) @@ -199,10 +202,7 @@ function getStepState(stepIndex: number): 'done' | 'active' | 'idle' { return 'done' } - if ( - currentVerificationStep.value === stepIndex && - (isReverifying.value || !!reverifyError.value) - ) { + if (currentVerificationStep.value === stepIndex && isReverifying.value && !reverifyError.value) { return 'active' } diff --git a/app/components/LinkedAccounts.vue b/app/components/LinkedAccounts.vue index 994be8d3d7..3d2165b2d8 100644 --- a/app/components/LinkedAccounts.vue +++ b/app/components/LinkedAccounts.vue @@ -1,12 +1,27 @@