[READ-ONLY] a fast, modern browser for the npm registry
at main 141 lines 4.7 kB view raw
1<script setup lang="ts"> 2import { useAtproto } from '~/composables/atproto/useAtproto' 3import { authRedirect } from '~/utils/atproto/helpers' 4import { isAtIdentifierString } from '@atproto/lex' 5 6const handleInput = shallowRef('') 7const errorMessage = shallowRef('') 8const route = useRoute() 9const { user, logout } = useAtproto() 10 11// https://atproto.com supports 4 locales as of 2026-02-07 12const { locale } = useI18n() 13const currentLang = locale.value.split('-')[0] ?? 'en' 14const localeSubPath = ['ko', 'pt', 'ja'].includes(currentLang) ? currentLang : '' 15const atprotoLink = `https://atproto.com/${localeSubPath}` 16 17async function handleBlueskySignIn() { 18 await authRedirect('https://bsky.social', { redirectTo: route.fullPath, locale: locale.value }) 19} 20 21async function handleCreateAccount() { 22 await authRedirect('https://npmx.social', { 23 create: true, 24 redirectTo: route.fullPath, 25 locale: locale.value, 26 }) 27} 28 29async function handleLogin() { 30 if (handleInput.value) { 31 // URLS to PDSs are valid for initiating oauth flows 32 if (handleInput.value.startsWith('https://') || isAtIdentifierString(handleInput.value)) { 33 await authRedirect(handleInput.value, { 34 redirectTo: route.fullPath, 35 locale: locale.value, 36 }) 37 } else { 38 errorMessage.value = $t('auth.modal.default_input_error') 39 } 40 } 41} 42 43watch(handleInput, newHandleInput => { 44 errorMessage.value = '' 45 if (!newHandleInput) return 46 47 const normalized = newHandleInput.trim().toLowerCase().replace(/@/g, '') 48 49 if (normalized !== newHandleInput) { 50 handleInput.value = normalized 51 } 52}) 53</script> 54 55<template> 56 <!-- Modal --> 57 <Modal :modalTitle="$t('auth.modal.title')" class="max-w-lg" id="auth-modal"> 58 <div v-if="user?.handle" class="space-y-4"> 59 <div class="flex items-center gap-3 p-4 bg-bg-subtle border border-border rounded-lg"> 60 <span class="w-3 h-3 rounded-full bg-green-500" aria-hidden="true" /> 61 <div> 62 <p class="font-mono text-xs text-fg-muted"> 63 {{ $t('auth.modal.connected_as', { handle: user.handle }) }} 64 </p> 65 </div> 66 </div> 67 <ButtonBase class="w-full" @click="logout"> 68 {{ $t('auth.modal.disconnect') }} 69 </ButtonBase> 70 </div> 71 72 <!-- Disconnected state --> 73 <form v-else class="space-y-4" @submit.prevent="handleLogin"> 74 <p class="text-sm text-fg-muted">{{ $t('auth.modal.connect_prompt') }}</p> 75 76 <div class="space-y-3"> 77 <div> 78 <label 79 for="handle-input" 80 class="block text-xs text-fg-subtle uppercase tracking-wider mb-1.5" 81 > 82 {{ $t('auth.modal.handle_label') }} 83 </label> 84 <InputBase 85 id="handle-input" 86 v-model="handleInput" 87 type="text" 88 name="handle" 89 :placeholder="$t('auth.modal.handle_placeholder')" 90 no-correct 91 class="w-full" 92 size="medium" 93 /> 94 <p v-if="errorMessage" class="text-red-500 text-xs mt-1" role="alert"> 95 {{ errorMessage }} 96 </p> 97 </div> 98 99 <details class="text-sm"> 100 <summary 101 class="text-fg-subtle hover:text-fg-muted transition-colors duration-200 focus-visible:(outline-2 outline-accent/70)" 102 > 103 {{ $t('auth.modal.what_is_atmosphere') }} 104 </summary> 105 <div class="mt-3"> 106 <i18n-t keypath="auth.modal.atmosphere_explanation" tag="p" scope="global"> 107 <template #npmx> 108 <span class="font-bold">npmx.dev</span> 109 </template> 110 <template #atproto> 111 <LinkBase :to="atprotoLink"> AT Protocol </LinkBase> 112 </template> 113 <template #bluesky> 114 <LinkBase to="https://bsky.app"> Bluesky </LinkBase> 115 </template> 116 <template #tangled> 117 <LinkBase to="https://tangled.org"> Tangled </LinkBase> 118 </template> 119 </i18n-t> 120 </div> 121 </details> 122 </div> 123 124 <ButtonBase type="submit" variant="primary" :disabled="!handleInput.trim()" class="w-full"> 125 {{ $t('auth.modal.connect') }} 126 </ButtonBase> 127 <ButtonBase type="button" class="w-full" @click="handleCreateAccount"> 128 {{ $t('auth.modal.create_account') }} 129 </ButtonBase> 130 <hr class="color-border" /> 131 <ButtonBase 132 type="button" 133 class="w-full" 134 @click="handleBlueskySignIn" 135 classicon="i-simple-icons:bluesky" 136 > 137 {{ $t('auth.modal.connect_bluesky') }} 138 </ButtonBase> 139 </form> 140 </Modal> 141</template>