mirror your GitHub repos to tangled.org automatically
1

Configure Feed

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

at main 6.4 kB View raw
1<script setup lang="ts"> 2import type { ConnectInfo } from '#server/api/connect/info.get' 3 4useSeoMeta({ 5 title: 'Connect · synchub.to', 6 description: 'Connect your tangled identity to your GitHub installation.', 7}) 8 9const route = useRoute() 10 11const rawId = route.query.installation_id 12const installationId = typeof rawId === 'string' && /^\d+$/.test(rawId) ? rawId : null 13const verified = route.query.verified === '1' 14const loginFromQuery = typeof route.query.login === 'string' ? route.query.login : null 15 16const { data: info } = await useAsyncData<ConnectInfo | null>( 17 `connect-info-${installationId}`, 18 () => installationId && !loginFromQuery 19 ? $fetch<ConnectInfo>('/api/connect/info', { query: { installationId } }).catch(() => null) 20 : Promise.resolve(null), 21) 22 23const login = loginFromQuery ?? info.value?.login ?? null 24const accountLabel = login ?? 'your GitHub account' 25</script> 26 27<template> 28 <div class="shell"> 29 <header class="nav-term"> 30 <nav class="nav-term__line" aria-label="primary"> 31 <span class="prompt" aria-hidden="true">&gt;</span> 32 <a class="nav-term__mark" href="/"><SynchubMark :wordmark="true" :size="18" /></a> 33 </nav> 34 </header> 35 36 <main class="connect"> 37 <div v-if="!installationId" class="panel"> 38 <h1 class="connect__title">no installation to connect</h1> 39 <p class="connect__body"> 40 start by installing the GitHub App on the repositories you'd like to 41 mirror. 42 </p> 43 <a class="btn btn--primary" href="https://github.com/apps/synchub-to/installations/new">install the GitHub App</a> 44 </div> 45 46 <div v-else-if="!verified" class="panel"> 47 <p class="connect__eyebrow">step 1 of 2 &middot; verify</p> 48 <h1 class="connect__title">installed on <span class="connect__login">{{ accountLabel }}</span></h1> 49 <p class="connect__body"> 50 confirm you administer <strong>{{ accountLabel }}</strong> on GitHub, 51 then you'll pick the tangled handle that mirrors it. we check this so 52 nobody else can bind your repositories to their identity. 53 </p> 54 <a class="btn btn--primary" :href="`/api/github/oauth/start?installationId=${installationId}`"> 55 verify with GitHub 56 </a> 57 </div> 58 59 <div v-else class="panel"> 60 <p class="connect__eyebrow">step 2 of 2 &middot; connect</p> 61 <h1 class="connect__title">connect a handle to <span class="connect__login">{{ accountLabel }}</span></h1> 62 <p class="connect__body"> 63 enter the tangled handle that should mirror 64 <strong>{{ accountLabel }}</strong>. one GitHub account maps to one 65 tangled identity, so connecting a new handle replaces any previous one. 66 </p> 67 <form class="signin" action="/api/atproto/login" method="get"> 68 <input type="hidden" name="installationId" :value="installationId"> 69 <label class="signin__label"> 70 <span>handle</span> 71 <span class="signin__field"> 72 <span class="signin__prompt" aria-hidden="true">@</span> 73 <input 74 type="text" 75 name="handle" 76 required 77 placeholder="alice.bsky.social" 78 autocomplete="username" 79 autocapitalize="none" 80 autocorrect="off" 81 spellcheck="false" 82 > 83 </span> 84 </label> 85 <button class="btn btn--primary" type="submit">connect</button> 86 </form> 87 </div> 88 </main> 89 </div> 90</template> 91 92<style scoped> 93.shell { 94 max-width: 52rem; 95 margin: 0 auto; 96 padding-inline: var(--page-gutter); 97} 98 99.nav-term { 100 padding: var(--space-md) 0; 101 border-bottom: var(--rule-hair) solid var(--color-rule); 102} 103 104.nav-term__line { 105 display: flex; 106 align-items: center; 107 gap: 0.6ch; 108 font-family: var(--font-mono); 109 font-size: var(--text-sm); 110 margin: 0; 111} 112 113.nav-term__line .prompt { color: var(--color-accent); } 114.nav-term__mark { display: inline-flex; text-decoration: none; } 115 116.connect { 117 padding-block: var(--space-2xl) var(--space-xl); 118} 119 120.panel { 121 max-width: 38rem; 122} 123 124.connect__eyebrow { 125 font-family: var(--font-mono); 126 font-size: var(--text-xs); 127 letter-spacing: 0.08em; 128 text-transform: uppercase; 129 color: var(--color-accent-dim); 130 margin: 0 0 var(--space-md); 131} 132 133.connect__title { 134 font-size: var(--text-xl); 135 line-height: 1.1; 136 margin: 0 0 var(--space-md); 137 overflow-wrap: anywhere; 138 min-width: 0; 139} 140 141.connect__login { color: var(--color-accent); } 142 143.connect__body { 144 max-width: var(--measure); 145 color: var(--color-muted); 146 margin: 0 0 var(--space-lg); 147} 148 149.signin { 150 display: flex; 151 flex-wrap: wrap; 152 align-items: end; 153 gap: var(--space-sm); 154} 155 156.signin__label { 157 display: flex; 158 flex-direction: column; 159 gap: var(--space-2xs); 160 font-family: var(--font-mono); 161 font-size: var(--text-xs); 162 letter-spacing: 0.06em; 163 text-transform: uppercase; 164 color: var(--color-neutral); 165} 166 167.signin__field { 168 display: flex; 169 align-items: center; 170 background: var(--color-paper-2); 171 border: var(--rule-hair) solid var(--color-rule-interactive); 172 border-radius: var(--radius-sm); 173} 174 175.signin__field:focus-within { 176 outline: 2px solid var(--color-focus); 177 outline-offset: 2px; 178 border-color: var(--color-accent); 179} 180 181.signin__prompt { 182 padding-inline: 0.75ch 0; 183 color: var(--color-accent); 184 font-family: var(--font-mono); 185} 186 187.signin input:focus-visible { outline: none; } 188 189.signin input { 190 flex: 1; 191 min-width: 14rem; 192 padding: 0.55rem 0.75rem 0.55rem 0.4ch; 193 border: 0; 194 background: transparent; 195 color: var(--color-ink); 196 font-family: var(--font-mono); 197 font-size: var(--text-base); 198} 199 200.signin input::placeholder { color: var(--color-neutral); } 201 202.btn { 203 display: inline-block; 204 padding: 0.55rem 1.1rem; 205 border: var(--rule-hair) solid var(--color-rule-interactive); 206 border-radius: var(--radius-sm); 207 background: var(--color-paper-2); 208 color: var(--color-ink); 209 font-family: var(--font-mono); 210 font-size: var(--text-sm); 211 text-decoration: none; 212 white-space: nowrap; 213 cursor: pointer; 214 transition: transform var(--dur-micro) var(--ease-out), border-color var(--dur-micro) var(--ease-out); 215} 216 217.btn:hover { border-color: var(--color-accent); transform: translateY(-1px); } 218.btn:active { transform: translateY(0); } 219.btn--primary { border-color: var(--color-accent); color: var(--color-accent); } 220</style>