[READ-ONLY] a fast, modern browser for the npm registry
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at eac8537f4da5f46fc166b0e041d492d8091dfbca 151 lines 5.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 <button 68 class="w-full px-4 py-2 font-mono text-sm text-fg-muted bg-bg-subtle border border-border rounded-md transition-colors duration-200 hover:text-fg hover:border-border-hover focus-visible:outline-accent/70" 69 @click="logout" 70 > 71 {{ $t('auth.modal.disconnect') }} 72 </button> 73 </div> 74 75 <!-- Disconnected state --> 76 <form v-else class="space-y-4" @submit.prevent="handleLogin"> 77 <p class="text-sm text-fg-muted">{{ $t('auth.modal.connect_prompt') }}</p> 78 79 <div class="space-y-3"> 80 <div> 81 <label 82 for="handle-input" 83 class="block text-xs text-fg-subtle uppercase tracking-wider mb-1.5" 84 > 85 {{ $t('auth.modal.handle_label') }} 86 </label> 87 <InputBase 88 id="handle-input" 89 v-model="handleInput" 90 type="text" 91 name="handle" 92 :placeholder="$t('auth.modal.handle_placeholder')" 93 no-correct 94 class="w-full" 95 size="medium" 96 /> 97 <p v-if="errorMessage" class="text-red-500 text-xs mt-1" role="alert"> 98 {{ errorMessage }} 99 </p> 100 </div> 101 102 <details class="text-sm"> 103 <summary 104 class="text-fg-subtle hover:text-fg-muted transition-colors duration-200 focus-visible:(outline-2 outline-accent/70)" 105 > 106 {{ $t('auth.modal.what_is_atmosphere') }} 107 </summary> 108 <div class="mt-3"> 109 <i18n-t keypath="auth.modal.atmosphere_explanation" tag="p" scope="global"> 110 <template #npmx> 111 <span class="font-bold">npmx.dev</span> 112 </template> 113 <template #atproto> 114 <a :href="atprotoLink" target="_blank" class="text-blue-400 hover:underline"> 115 AT Protocol 116 </a> 117 </template> 118 <template #bluesky> 119 <a href="https://bsky.app" target="_blank" class="text-blue-400 hover:underline"> 120 Bluesky 121 </a> 122 </template> 123 <template #tangled> 124 <a href="https://tangled.org" target="_blank" class="text-blue-400 hover:underline"> 125 Tangled 126 </a> 127 </template> 128 </i18n-t> 129 </div> 130 </details> 131 </div> 132 133 <ButtonBase type="submit" variant="primary" :disabled="!handleInput.trim()" class="w-full"> 134 {{ $t('auth.modal.connect') }} 135 </ButtonBase> 136 <ButtonBase type="button" variant="primary" class="w-full" @click="handleCreateAccount"> 137 {{ $t('auth.modal.create_account') }} 138 </ButtonBase> 139 <hr class="color-border" /> 140 <ButtonBase type="button" variant="primary" class="w-full" @click="handleBlueskySignIn" block> 141 {{ $t('auth.modal.connect_bluesky') }} 142 <svg fill="none" viewBox="0 0 64 57" width="20" style="width: 20px"> 143 <path 144 fill="#0F73FF" 145 d="M13.873 3.805C21.21 9.332 29.103 20.537 32 26.55v15.882c0-.338-.13.044-.41.867-1.512 4.456-7.418 21.847-20.923 7.944-7.111-7.32-3.819-14.64 9.125-16.85-7.405 1.264-15.73-.825-18.014-9.015C1.12 23.022 0 8.51 0 6.55 0-3.268 8.579-.182 13.873 3.805ZM50.127 3.805C42.79 9.332 34.897 20.537 32 26.55v15.882c0-.338.13.044.41.867 1.512 4.456 7.418 21.847 20.923 7.944 7.111-7.32 3.819-14.64-9.125-16.85 7.405 1.264 15.73-.825 18.014-9.015C62.88 23.022 64 8.51 64 6.55c0-9.818-8.578-6.732-13.873-2.745Z" 146 ></path> 147 </svg> 148 </ButtonBase> 149 </form> 150 </Modal> 151</template>