wip bsky client for the web & android
bbell.vt3e.cat
1<script lang="ts" setup>
2import { computed, onMounted, ref } from 'vue'
3
4import { useAuthStore } from '@/stores/auth'
5import { getProviderList, type HydratedProvider } from '@/utils/pds'
6
7import Button from '../../UI/BaseButton.vue'
8import Toggle from '../../UI/BaseCheckbox.vue'
9import Modal from '../../UI/BaseModal.vue'
10import PdsList from '../../PdsSelector.vue'
11
12const auth = useAuthStore()
13
14const providers = ref<HydratedProvider[]>([])
15const selectedProvider = ref<HydratedProvider>()
16
17const selectedHasHandlePolicy = computed(() => !!selectedProvider.value?.handlePolicy)
18const hasAgreedToHandlePolicy = ref(false)
19
20const canProgress = computed(() => {
21 const selected = selectedProvider.value
22 if (!selected) return false
23 if (selectedHasHandlePolicy.value && hasAgreedToHandlePolicy.value) return true
24 return !selected.handlePolicy
25})
26
27const pdsError = ref('')
28const pdsInput = ref('')
29
30async function submitPds() {
31 const val =
32 pdsInput.value.trim() === '' ? selectedProvider.value?.url.toString() : pdsInput.value.trim()
33 if (!val) {
34 pdsError.value = 'Enter a PDS URL or handle'
35 return
36 }
37 await auth.login(val)
38}
39
40onMounted(async () => {
41 providers.value = await getProviderList()
42 if (providers.value.length) {
43 selectedProvider.value = providers.value[0]
44 } else {
45 pdsError.value = 'No providers available'
46 }
47})
48
49function selectProvider(provider: HydratedProvider) {
50 hasAgreedToHandlePolicy.value = false
51 selectedProvider.value = provider
52}
53</script>
54
55<template>
56 <Modal title="Create an account" width="600px">
57 <div class="modal-body">
58 <p>
59 Choose a provider to host your account. You can migrate to a different provider later if
60 needed.
61 </p>
62
63 <PdsList @select="selectProvider" />
64
65 <div v-if="selectedProvider" class="provider-profile">
66 <div class="profile-main">
67 <div class="pds-info">
68 <p class="pds-name">{{ selectedProvider.name }}</p>
69 <p class="pds-location">{{ selectedProvider.location }}</p>
70 </div>
71 </div>
72
73 <div
74 class="policy-transition-wrapper"
75 :class="{ open: selectedHasHandlePolicy }"
76 :inert="!selectedHasHandlePolicy"
77 >
78 <div class="policy-inner">
79 <div class="policy-section">
80 <div class="policy-notice">
81 <p>
82 This provider has restrictions on who can use what handles.
83 <a :href="selectedProvider.handlePolicy?.toString() || '#'" class="policy-link">
84 View terms here
85 </a>
86 </p>
87 </div>
88 <label class="policy-checkbox" :class="{ checked: hasAgreedToHandlePolicy }">
89 <Toggle v-model="hasAgreedToHandlePolicy" id="policy-agree" />
90 <span>I've read and accept these terms</span>
91 </label>
92 </div>
93 </div>
94 </div>
95 </div>
96 </div>
97
98 <template #footer>
99 <Button variant="ghost" type="button" @click="$emit('close')">Cancel</Button>
100 <Button
101 variant="primary"
102 :loading="auth.isLoading"
103 @click="submitPds"
104 type="button"
105 :disabled="!canProgress"
106 >
107 Create account
108 </Button>
109 </template>
110 </Modal>
111</template>
112
113<style lang="scss" scoped>
114.modal-body {
115 display: flex;
116 flex-direction: column;
117 gap: 1.5rem;
118}
119
120.provider-list {
121 margin-top: -0.75rem;
122}
123
124.provider-profile {
125 background: hsla(var(--surface0) / 0.4);
126 margin-left: -0.75rem;
127 width: calc(100% + 1.5rem);
128 border-radius: 1rem;
129 padding: 1.25rem;
130 display: flex;
131 flex-direction: column;
132 gap: 0;
133
134 .profile-main {
135 display: flex;
136 align-items: center;
137 gap: 1rem;
138
139 .pds-info {
140 display: flex;
141 flex-direction: row;
142 align-items: baseline;
143 gap: 0.5rem;
144
145 .pds-name {
146 font-weight: 600;
147 font-size: 1rem;
148 color: hsla(var(--text));
149 display: block;
150 }
151
152 .pds-location {
153 font-size: 0.8rem;
154 color: hsla(var(--text) / 0.6);
155 display: block;
156 }
157 }
158 }
159
160 .policy-transition-wrapper {
161 display: grid;
162 grid-template-rows: 0fr;
163 opacity: 0;
164
165 &.open {
166 grid-template-rows: 1fr;
167 opacity: 1;
168 }
169 }
170
171 .policy-inner {
172 overflow: hidden;
173 min-height: 0;
174 }
175
176 .policy-section {
177 border-top: 1px dashed hsla(var(--accent) / 0.5);
178 margin-top: 0.5rem;
179 padding-top: 0.5rem;
180
181 display: flex;
182 flex-direction: column;
183 gap: 0.25rem;
184
185 .policy-notice {
186 font-size: 0.85rem;
187 color: hsl(var(--subtext1));
188
189 .policy-link {
190 color: hsla(var(--accent));
191 text-decoration: none;
192 font-weight: 700;
193
194 &:hover {
195 text-decoration: underline;
196 }
197 }
198 }
199
200 .policy-checkbox {
201 display: flex;
202 align-items: center;
203 gap: 0.75rem;
204
205 span {
206 font-size: 0.85rem;
207 user-select: none;
208 }
209 }
210 }
211}
212
213.fade-enter-from,
214.fade-leave-to {
215 opacity: 0;
216}
217</style>