|
1 | | -import { Cloud, Database } from 'lucide-react'; |
| 1 | +import { CheckCircle2, Cloud, Database, KeyRound } from 'lucide-react'; |
2 | 2 | import { useEffect, useState } from 'react'; |
3 | 3 | import { toast } from 'sonner'; |
4 | 4 |
|
@@ -121,87 +121,132 @@ function ApiConfigForm({ |
121 | 121 | ) : null} |
122 | 122 | </div> |
123 | 123 |
|
124 | | - <div> |
125 | | - <div className="mb-2 flex items-center justify-between"> |
126 | | - <label |
127 | | - htmlFor="beatapi-key" |
128 | | - className="text-[12px] font-medium text-white/45" |
129 | | - > |
130 | | - {t('keyLabel')} |
131 | | - </label> |
132 | | - {configured && !replacing ? ( |
133 | | - <span className="rounded-full border border-[#ff7a33]/30 bg-[#ff7a33]/10 px-2 py-0.5 text-[11px] font-medium text-[#ff9a62]"> |
134 | | - {t('keyConfigured')} |
| 124 | + {!loaded ? ( |
| 125 | + <div |
| 126 | + className="h-[148px] animate-pulse rounded-[16px] border border-white/[0.08] bg-white/[0.04] motion-reduce:animate-none" |
| 127 | + aria-label={t('loadingConnection')} |
| 128 | + /> |
| 129 | + ) : configured && !replacing ? ( |
| 130 | + <div |
| 131 | + className="rounded-[16px] border border-[#c7f36b]/20 bg-[#c7f36b]/[0.045] p-4" |
| 132 | + role="status" |
| 133 | + > |
| 134 | + <div className="flex items-start gap-3"> |
| 135 | + <span className="mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-full bg-[#c7f36b]/12 text-[#c7f36b]"> |
| 136 | + <CheckCircle2 className="size-4" aria-hidden="true" /> |
135 | 137 | </span> |
136 | | - ) : null} |
137 | | - </div> |
| 138 | + <div className="min-w-0"> |
| 139 | + <p className="text-[14px] font-semibold text-white"> |
| 140 | + {t('connectedTitle')} |
| 141 | + </p> |
| 142 | + <p className="mt-0.5 text-[12px] leading-5 text-white/48"> |
| 143 | + {t('connectedDescription')} |
| 144 | + </p> |
| 145 | + </div> |
| 146 | + </div> |
138 | 147 |
|
139 | | - {!loaded ? ( |
140 | | - <div |
141 | | - className="h-11 animate-pulse rounded-[12px] border border-white/[0.08] bg-white/[0.04] motion-reduce:animate-none" |
142 | | - aria-label={t('loadingConnection')} |
143 | | - /> |
144 | | - ) : showEditor ? ( |
145 | | - <input |
146 | | - id="beatapi-key" |
147 | | - type="password" |
148 | | - value={apiKey} |
149 | | - onChange={(event) => setApiKey(event.target.value)} |
150 | | - placeholder={t('keyPlaceholder')} |
151 | | - autoComplete="off" |
152 | | - className={inputClassName} |
153 | | - /> |
154 | | - ) : ( |
155 | | - <div className="flex h-11 items-center rounded-[12px] border border-white/[0.12] bg-white/[0.04] px-3.5 font-mono text-[13px] text-white/80"> |
156 | | - {state?.apiKeyPreview} |
| 148 | + <div className="mt-4 flex items-center justify-between gap-4 rounded-[11px] border border-white/[0.08] bg-black/15 px-3 py-2.5"> |
| 149 | + <span className="text-[11px] font-medium uppercase tracking-[0.08em] text-white/38"> |
| 150 | + {t('savedKeyLabel')} |
| 151 | + </span> |
| 152 | + <code className="truncate font-mono text-[12px] text-white/72"> |
| 153 | + {state?.apiKeyPreview} |
| 154 | + </code> |
157 | 155 | </div> |
158 | | - )} |
159 | 156 |
|
160 | | - <p className="mt-2 text-[12px] leading-5 text-white/40"> |
161 | | - {t('connectHint')} |
162 | | - </p> |
163 | | - <div className="mt-2 flex flex-wrap items-center gap-x-4 gap-y-1"> |
164 | | - {configured && !replacing ? ( |
| 157 | + <div className="mt-3 flex flex-wrap items-center gap-x-4 gap-y-2"> |
165 | 158 | <button |
166 | 159 | type="button" |
167 | 160 | onClick={() => setReplacing(true)} |
168 | | - className="text-[13px] font-semibold text-white/55 transition hover:text-white" |
| 161 | + className="rounded-[9px] border border-white/[0.12] bg-white/[0.04] px-3 py-2 text-[12px] font-semibold text-white/72 transition hover:border-white/20 hover:bg-white/[0.07] hover:text-white" |
169 | 162 | > |
170 | | - {t('replaceKey')} |
| 163 | + {t('changeKey')} |
171 | 164 | </button> |
172 | | - ) : null} |
173 | | - {replacing ? ( |
174 | | - <button |
175 | | - type="button" |
176 | | - onClick={() => { |
177 | | - setReplacing(false); |
178 | | - setApiKey(''); |
179 | | - }} |
180 | | - className="text-[13px] font-semibold text-white/55 transition hover:text-white" |
| 165 | + <a |
| 166 | + href="https://beatapi.io/dashboard/apikeys" |
| 167 | + target="_blank" |
| 168 | + rel="noreferrer" |
| 169 | + className="inline-flex text-[12px] font-semibold text-[#ff8b4d] transition hover:text-[#ffa26b]" |
181 | 170 | > |
182 | | - {t('cancelReplace')} |
183 | | - </button> |
184 | | - ) : null} |
185 | | - <a |
186 | | - href="https://beatapi.io/dashboard/apikeys" |
187 | | - target="_blank" |
188 | | - rel="noreferrer" |
189 | | - className="inline-flex text-[13px] font-semibold text-[#ff8b4d] transition hover:text-[#ffa26b]" |
190 | | - > |
191 | | - {t('getKey')} |
192 | | - </a> |
| 171 | + {t('getKey')} |
| 172 | + </a> |
| 173 | + </div> |
193 | 174 | </div> |
194 | | - </div> |
| 175 | + ) : showEditor ? ( |
| 176 | + <div className="space-y-4"> |
| 177 | + <div |
| 178 | + className="flex items-start gap-3 rounded-[14px] border border-white/[0.09] bg-white/[0.025] p-3.5" |
| 179 | + role="status" |
| 180 | + > |
| 181 | + <span className="mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-full bg-[#ff7a33]/10 text-[#ff9258]"> |
| 182 | + <KeyRound className="size-4" aria-hidden="true" /> |
| 183 | + </span> |
| 184 | + <div className="min-w-0"> |
| 185 | + <p className="text-[13px] font-semibold text-white"> |
| 186 | + {replacing ? t('changeKeyTitle') : t('notConnectedTitle')} |
| 187 | + </p> |
| 188 | + <p className="mt-0.5 text-[12px] leading-5 text-white/45"> |
| 189 | + {replacing |
| 190 | + ? t('changeKeyDescription') |
| 191 | + : t('notConnectedDescription')} |
| 192 | + </p> |
| 193 | + </div> |
| 194 | + </div> |
195 | 195 |
|
196 | | - {showEditor ? ( |
197 | | - <button |
198 | | - type="button" |
199 | | - disabled={saving || !apiKey.trim()} |
200 | | - onClick={() => void save()} |
201 | | - className={saveButtonClassName} |
202 | | - > |
203 | | - {saving ? t('saving') : t('save')} |
204 | | - </button> |
| 196 | + <div> |
| 197 | + <label |
| 198 | + htmlFor="beatapi-key" |
| 199 | + className="mb-2 block text-[12px] font-medium text-white/45" |
| 200 | + > |
| 201 | + {t('keyLabel')} |
| 202 | + </label> |
| 203 | + <input |
| 204 | + id="beatapi-key" |
| 205 | + type="password" |
| 206 | + value={apiKey} |
| 207 | + onChange={(event) => setApiKey(event.target.value)} |
| 208 | + placeholder={t('keyPlaceholder')} |
| 209 | + autoComplete="off" |
| 210 | + className={inputClassName} |
| 211 | + /> |
| 212 | + </div> |
| 213 | + |
| 214 | + <button |
| 215 | + type="button" |
| 216 | + disabled={saving || !apiKey.trim()} |
| 217 | + onClick={() => void save()} |
| 218 | + className={saveButtonClassName} |
| 219 | + > |
| 220 | + {saving |
| 221 | + ? t('saving') |
| 222 | + : replacing |
| 223 | + ? t('saveNewKey') |
| 224 | + : t('connect')} |
| 225 | + </button> |
| 226 | + |
| 227 | + <div className="flex flex-wrap items-center gap-x-4 gap-y-2"> |
| 228 | + {replacing ? ( |
| 229 | + <button |
| 230 | + type="button" |
| 231 | + onClick={() => { |
| 232 | + setReplacing(false); |
| 233 | + setApiKey(''); |
| 234 | + }} |
| 235 | + className="text-[12px] font-semibold text-white/55 transition hover:text-white" |
| 236 | + > |
| 237 | + {t('cancelReplace')} |
| 238 | + </button> |
| 239 | + ) : null} |
| 240 | + <a |
| 241 | + href="https://beatapi.io/dashboard/apikeys" |
| 242 | + target="_blank" |
| 243 | + rel="noreferrer" |
| 244 | + className="inline-flex text-[12px] font-semibold text-[#ff8b4d] transition hover:text-[#ffa26b]" |
| 245 | + > |
| 246 | + {t('getKey')} |
| 247 | + </a> |
| 248 | + </div> |
| 249 | + </div> |
205 | 250 | ) : null} |
206 | 251 | </div> |
207 | 252 | ); |
|
0 commit comments