|
1 | 1 | <script lang="ts"> |
2 | 2 | import type { PageData } from './$types'; |
3 | 3 | import ThemeToggle from '$lib/components/ThemeToggle.svelte'; |
| 4 | + import { goto } from '$app/navigation'; |
4 | 5 |
|
5 | 6 | let { data }: { data: PageData } = $props(); |
6 | 7 |
|
7 | 8 | let activeTab = $state('overview'); |
8 | 9 | let copied = $state(false); |
9 | 10 | let showShareModal = $state(false); |
10 | 11 | let shareCopied = $state(false); |
| 12 | + let forking = $state(false); |
| 13 | + let forkError = $state(''); |
11 | 14 |
|
12 | 15 | function getInstallCommand() { |
13 | 16 | return `curl -fsSL https://openboot.dev/${data.configUser.username}/${data.config.slug} | bash`; |
@@ -285,6 +288,45 @@ ${rects} |
285 | 288 | img.src = url; |
286 | 289 | } |
287 | 290 |
|
| 291 | + async function forkConfig() { |
| 292 | + forking = true; |
| 293 | + forkError = ''; |
| 294 | +
|
| 295 | + try { |
| 296 | + const authCheck = await fetch('/api/user'); |
| 297 | + if (!authCheck.ok) { |
| 298 | + window.location.href = '/api/auth/login'; |
| 299 | + return; |
| 300 | + } |
| 301 | +
|
| 302 | + const forkResponse = await fetch('/api/configs', { |
| 303 | + method: 'POST', |
| 304 | + headers: { 'Content-Type': 'application/json' }, |
| 305 | + body: JSON.stringify({ |
| 306 | + name: `${data.config.name} (fork)`, |
| 307 | + description: `Forked from @${data.configUser.username}`, |
| 308 | + base_preset: data.config.base_preset, |
| 309 | + packages: data.config.packages, |
| 310 | + is_public: true, |
| 311 | + custom_script: data.config.custom_script || '', |
| 312 | + dotfiles_repo: data.config.dotfiles_repo || '' |
| 313 | + }) |
| 314 | + }); |
| 315 | +
|
| 316 | + if (!forkResponse.ok) { |
| 317 | + const error = await forkResponse.json(); |
| 318 | + forkError = error.error || 'Failed to fork config'; |
| 319 | + forking = false; |
| 320 | + return; |
| 321 | + } |
| 322 | +
|
| 323 | + goto('/dashboard'); |
| 324 | + } catch (err) { |
| 325 | + forkError = 'Network error. Please try again.'; |
| 326 | + forking = false; |
| 327 | + } |
| 328 | + } |
| 329 | +
|
288 | 330 | function getPackageUrl(name: string, type: 'formula' | 'cask' | 'npm'): string { |
289 | 331 | if (type === 'npm') return `https://www.npmjs.com/package/${name}`; |
290 | 332 | if (type === 'cask') return `https://formulae.brew.sh/cask/${name}`; |
@@ -363,6 +405,13 @@ ${rects} |
363 | 405 | {copied ? 'Copied!' : 'Copy'} |
364 | 406 | </button> |
365 | 407 | </div> |
| 408 | + <button class="fork-btn" onclick={forkConfig} disabled={forking}> |
| 409 | + <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><circle cx="18" cy="6" r="3"/><path d="M18 9v2c0 .6-.4 1-1 1H7c-.6 0-1-.4-1-1V9"/><path d="M12 12v3"/></svg> |
| 410 | + {forking ? 'Forking...' : 'Fork Config'} |
| 411 | + </button> |
| 412 | + {#if forkError} |
| 413 | + <div class="fork-error">{forkError}</div> |
| 414 | + {/if} |
366 | 415 | </div> |
367 | 416 |
|
368 | 417 | <div class="stats"> |
@@ -392,6 +441,10 @@ ${rects} |
392 | 441 | <span class="stat-label">Preferences</span> |
393 | 442 | </div> |
394 | 443 | {/if} |
| 444 | + <div class="stat"> |
| 445 | + <span class="stat-value">{data.config.install_count || 0}</span> |
| 446 | + <span class="stat-label">Installs</span> |
| 447 | + </div> |
395 | 448 | </div> |
396 | 449 | </section> |
397 | 450 |
|
@@ -796,6 +849,47 @@ ${rects} |
796 | 849 | background: var(--accent-hover); |
797 | 850 | } |
798 | 851 |
|
| 852 | + .fork-btn { |
| 853 | + display: flex; |
| 854 | + align-items: center; |
| 855 | + justify-content: center; |
| 856 | + gap: 8px; |
| 857 | + width: 100%; |
| 858 | + margin-top: 12px; |
| 859 | + padding: 10px 16px; |
| 860 | + background: var(--bg-tertiary); |
| 861 | + border: 1px solid var(--border); |
| 862 | + border-radius: 8px; |
| 863 | + color: var(--text-primary); |
| 864 | + font-size: 0.9rem; |
| 865 | + font-weight: 500; |
| 866 | + font-family: inherit; |
| 867 | + cursor: pointer; |
| 868 | + transition: all 0.2s; |
| 869 | + } |
| 870 | +
|
| 871 | + .fork-btn:hover:not(:disabled) { |
| 872 | + border-color: var(--accent); |
| 873 | + color: var(--accent); |
| 874 | + background: var(--bg-secondary); |
| 875 | + } |
| 876 | +
|
| 877 | + .fork-btn:disabled { |
| 878 | + opacity: 0.6; |
| 879 | + cursor: not-allowed; |
| 880 | + } |
| 881 | +
|
| 882 | + .fork-error { |
| 883 | + margin-top: 8px; |
| 884 | + padding: 8px 12px; |
| 885 | + background: rgba(239, 68, 68, 0.1); |
| 886 | + border: 1px solid rgba(239, 68, 68, 0.3); |
| 887 | + border-radius: 6px; |
| 888 | + color: #ef4444; |
| 889 | + font-size: 0.85rem; |
| 890 | + text-align: center; |
| 891 | + } |
| 892 | +
|
799 | 893 | .stats { |
800 | 894 | display: flex; |
801 | 895 | justify-content: center; |
|
0 commit comments