create-sia-app 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +179 -0
  3. package/package.json +29 -0
  4. package/template/CLAUDE.md +160 -0
  5. package/template/README.md +102 -0
  6. package/template/_gitignore +5 -0
  7. package/template/biome.json +40 -0
  8. package/template/index.html +13 -0
  9. package/template/package.json +30 -0
  10. package/template/rust/README.md +16 -0
  11. package/template/rust/sia-sdk-rs/.changeset/added_cancel_function_to_cancel_inflight_packed_uploads.md +6 -0
  12. package/template/rust/sia-sdk-rs/.changeset/check_if_we_have_enough_hosts_prior_to_encoding_in_upload_slabs.md +16 -0
  13. package/template/rust/sia-sdk-rs/.changeset/fix_slab_length_in_packed_object.md +5 -0
  14. package/template/rust/sia-sdk-rs/.changeset/fix_upload_racing_race_conditon.md +13 -0
  15. package/template/rust/sia-sdk-rs/.changeset/improved_parallelism_of_packed_uploads.md +5 -0
  16. package/template/rust/sia-sdk-rs/.changeset/progress_callback_will_now_be_called_as_expected_for_packed_uploads.md +5 -0
  17. package/template/rust/sia-sdk-rs/.github/dependabot.yml +10 -0
  18. package/template/rust/sia-sdk-rs/.github/workflows/main.yml +36 -0
  19. package/template/rust/sia-sdk-rs/.github/workflows/prepare-release.yml +34 -0
  20. package/template/rust/sia-sdk-rs/.github/workflows/release.yml +30 -0
  21. package/template/rust/sia-sdk-rs/.rustfmt.toml +4 -0
  22. package/template/rust/sia-sdk-rs/Cargo.lock +4127 -0
  23. package/template/rust/sia-sdk-rs/Cargo.toml +3 -0
  24. package/template/rust/sia-sdk-rs/LICENSE +21 -0
  25. package/template/rust/sia-sdk-rs/README.md +30 -0
  26. package/template/rust/sia-sdk-rs/indexd/CHANGELOG.md +79 -0
  27. package/template/rust/sia-sdk-rs/indexd/Cargo.toml +79 -0
  28. package/template/rust/sia-sdk-rs/indexd/benches/upload.rs +258 -0
  29. package/template/rust/sia-sdk-rs/indexd/src/app_client.rs +1710 -0
  30. package/template/rust/sia-sdk-rs/indexd/src/builder.rs +354 -0
  31. package/template/rust/sia-sdk-rs/indexd/src/download.rs +379 -0
  32. package/template/rust/sia-sdk-rs/indexd/src/hosts.rs +659 -0
  33. package/template/rust/sia-sdk-rs/indexd/src/lib.rs +827 -0
  34. package/template/rust/sia-sdk-rs/indexd/src/mock.rs +162 -0
  35. package/template/rust/sia-sdk-rs/indexd/src/object_encryption.rs +125 -0
  36. package/template/rust/sia-sdk-rs/indexd/src/quic.rs +575 -0
  37. package/template/rust/sia-sdk-rs/indexd/src/rhp4.rs +52 -0
  38. package/template/rust/sia-sdk-rs/indexd/src/slabs.rs +497 -0
  39. package/template/rust/sia-sdk-rs/indexd/src/upload.rs +629 -0
  40. package/template/rust/sia-sdk-rs/indexd/src/wasm_time.rs +41 -0
  41. package/template/rust/sia-sdk-rs/indexd/src/web_transport.rs +398 -0
  42. package/template/rust/sia-sdk-rs/indexd_ffi/CHANGELOG.md +76 -0
  43. package/template/rust/sia-sdk-rs/indexd_ffi/Cargo.toml +47 -0
  44. package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/README.md +10 -0
  45. package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/example.py +130 -0
  46. package/template/rust/sia-sdk-rs/indexd_ffi/src/bin/uniffi-bindgen.rs +3 -0
  47. package/template/rust/sia-sdk-rs/indexd_ffi/src/builder.rs +377 -0
  48. package/template/rust/sia-sdk-rs/indexd_ffi/src/io.rs +155 -0
  49. package/template/rust/sia-sdk-rs/indexd_ffi/src/lib.rs +1039 -0
  50. package/template/rust/sia-sdk-rs/indexd_ffi/src/logging.rs +58 -0
  51. package/template/rust/sia-sdk-rs/indexd_ffi/src/tls.rs +23 -0
  52. package/template/rust/sia-sdk-rs/indexd_wasm/Cargo.toml +33 -0
  53. package/template/rust/sia-sdk-rs/indexd_wasm/src/lib.rs +818 -0
  54. package/template/rust/sia-sdk-rs/knope.toml +54 -0
  55. package/template/rust/sia-sdk-rs/sia_derive/CHANGELOG.md +38 -0
  56. package/template/rust/sia-sdk-rs/sia_derive/Cargo.toml +19 -0
  57. package/template/rust/sia-sdk-rs/sia_derive/src/lib.rs +278 -0
  58. package/template/rust/sia-sdk-rs/sia_sdk/CHANGELOG.md +91 -0
  59. package/template/rust/sia-sdk-rs/sia_sdk/Cargo.toml +59 -0
  60. package/template/rust/sia-sdk-rs/sia_sdk/benches/merkle_root.rs +12 -0
  61. package/template/rust/sia-sdk-rs/sia_sdk/src/blake2.rs +22 -0
  62. package/template/rust/sia-sdk-rs/sia_sdk/src/consensus.rs +767 -0
  63. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v1.rs +257 -0
  64. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v2.rs +291 -0
  65. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding.rs +26 -0
  66. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async/v2.rs +367 -0
  67. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async.rs +6 -0
  68. package/template/rust/sia-sdk-rs/sia_sdk/src/encryption.rs +303 -0
  69. package/template/rust/sia-sdk-rs/sia_sdk/src/erasure_coding.rs +347 -0
  70. package/template/rust/sia-sdk-rs/sia_sdk/src/lib.rs +15 -0
  71. package/template/rust/sia-sdk-rs/sia_sdk/src/macros.rs +435 -0
  72. package/template/rust/sia-sdk-rs/sia_sdk/src/merkle.rs +112 -0
  73. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/merkle.rs +357 -0
  74. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/rpc.rs +1507 -0
  75. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/types.rs +146 -0
  76. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp.rs +7 -0
  77. package/template/rust/sia-sdk-rs/sia_sdk/src/seed.rs +278 -0
  78. package/template/rust/sia-sdk-rs/sia_sdk/src/signing.rs +236 -0
  79. package/template/rust/sia-sdk-rs/sia_sdk/src/types/common.rs +677 -0
  80. package/template/rust/sia-sdk-rs/sia_sdk/src/types/currency.rs +450 -0
  81. package/template/rust/sia-sdk-rs/sia_sdk/src/types/specifier.rs +110 -0
  82. package/template/rust/sia-sdk-rs/sia_sdk/src/types/spendpolicy.rs +778 -0
  83. package/template/rust/sia-sdk-rs/sia_sdk/src/types/utils.rs +117 -0
  84. package/template/rust/sia-sdk-rs/sia_sdk/src/types/v1.rs +1737 -0
  85. package/template/rust/sia-sdk-rs/sia_sdk/src/types/v2.rs +1726 -0
  86. package/template/rust/sia-sdk-rs/sia_sdk/src/types/work.rs +59 -0
  87. package/template/rust/sia-sdk-rs/sia_sdk/src/types.rs +16 -0
  88. package/template/scripts/setup-rust.js +29 -0
  89. package/template/src/App.tsx +13 -0
  90. package/template/src/components/DevNote.tsx +21 -0
  91. package/template/src/components/auth/ApproveScreen.tsx +84 -0
  92. package/template/src/components/auth/AuthFlow.tsx +77 -0
  93. package/template/src/components/auth/ConnectScreen.tsx +214 -0
  94. package/template/src/components/auth/LoadingScreen.tsx +8 -0
  95. package/template/src/components/auth/RecoveryScreen.tsx +182 -0
  96. package/template/src/components/upload/UploadZone.tsx +314 -0
  97. package/template/src/index.css +9 -0
  98. package/template/src/lib/constants.ts +8 -0
  99. package/template/src/lib/format.ts +35 -0
  100. package/template/src/lib/hex.ts +13 -0
  101. package/template/src/lib/sdk.ts +25 -0
  102. package/template/src/lib/wasm-env.ts +5 -0
  103. package/template/src/main.tsx +12 -0
  104. package/template/src/stores/auth.ts +86 -0
  105. package/template/tsconfig.app.json +31 -0
  106. package/template/tsconfig.json +7 -0
  107. package/template/tsconfig.node.json +26 -0
  108. package/template/vite.config.ts +18 -0
  109. package/template/wasm/indexd_wasm/indexd_wasm.d.ts +309 -0
  110. package/template/wasm/indexd_wasm/indexd_wasm.js +1507 -0
  111. package/template/wasm/indexd_wasm/indexd_wasm_bg.wasm +0 -0
  112. package/template/wasm/indexd_wasm/package.json +31 -0
@@ -0,0 +1,59 @@
1
+ use serde::{Deserialize, Serialize};
2
+ use uint::construct_uint;
3
+
4
+ use crate::encoding::{self, SiaDecodable, SiaEncodable, V1SiaDecodable, V1SiaEncodable};
5
+
6
+ construct_uint! {
7
+ /// Work is a 256-bit unsigned integer.
8
+ pub struct Work(4);
9
+ }
10
+
11
+ impl From<&[u8; 32]> for Work {
12
+ fn from(bytes: &[u8; 32]) -> Self {
13
+ Work::from_big_endian(bytes)
14
+ }
15
+ }
16
+
17
+ impl SiaEncodable for Work {
18
+ fn encode<W: std::io::Write>(&self, w: &mut W) -> encoding::Result<()> {
19
+ self.to_big_endian().encode(w)
20
+ }
21
+ }
22
+
23
+ impl SiaDecodable for Work {
24
+ fn decode<R: std::io::Read>(r: &mut R) -> encoding::Result<Self> {
25
+ Ok(Work::from_big_endian(&<[u8; 32]>::decode(r).map_err(
26
+ |_| encoding::Error::Custom("invalid work".to_string()),
27
+ )?))
28
+ }
29
+ }
30
+
31
+ impl V1SiaEncodable for Work {
32
+ fn encode_v1<W: std::io::Write>(&self, w: &mut W) -> encoding::Result<()> {
33
+ self.to_big_endian().encode_v1(w)
34
+ }
35
+ }
36
+
37
+ impl V1SiaDecodable for Work {
38
+ fn decode_v1<R: std::io::Read>(r: &mut R) -> encoding::Result<Self> {
39
+ Ok(Work::from_big_endian(&<[u8; 32]>::decode_v1(r)?))
40
+ }
41
+ }
42
+
43
+ impl Serialize for Work {
44
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
45
+ where
46
+ S: serde::Serializer,
47
+ {
48
+ serializer.serialize_str(&self.to_string())
49
+ }
50
+ }
51
+
52
+ impl<'de> Deserialize<'de> for Work {
53
+ fn deserialize<D>(deserializer: D) -> Result<Work, D::Error>
54
+ where
55
+ D: serde::Deserializer<'de>,
56
+ {
57
+ Work::from_dec_str(&String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
58
+ }
59
+ }
@@ -0,0 +1,16 @@
1
+ mod common;
2
+ mod currency;
3
+ mod specifier;
4
+ mod spendpolicy; // exposed in v2 types
5
+
6
+ #[allow(clippy::manual_div_ceil)]
7
+ mod work;
8
+
9
+ pub use common::*;
10
+ pub use currency::*;
11
+ pub use specifier::*;
12
+ pub use work::*;
13
+
14
+ pub(crate) mod utils;
15
+ pub mod v1;
16
+ pub mod v2;
@@ -0,0 +1,29 @@
1
+ import { existsSync } from 'fs'
2
+ import { execSync } from 'child_process'
3
+ import { resolve, dirname } from 'path'
4
+ import { fileURLToPath } from 'url'
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url))
7
+ const targetDir = resolve(__dirname, '../rust/sia-sdk-rs')
8
+
9
+ if (existsSync(targetDir)) {
10
+ console.log('rust/sia-sdk-rs already exists, skipping clone.')
11
+ process.exit(0)
12
+ }
13
+
14
+ const repo = 'https://github.com/alexfreska/sia-sdk-rs.git'
15
+ const branch = 'alex/wasm-experimental'
16
+
17
+ console.log(`Cloning ${repo} (branch: ${branch})...`)
18
+
19
+ try {
20
+ execSync(
21
+ `git clone --branch ${branch} --single-branch --depth 1 ${repo} ${targetDir}`,
22
+ { stdio: 'inherit' },
23
+ )
24
+ console.log('Rust SDK source cloned successfully.')
25
+ } catch {
26
+ console.warn(
27
+ '\nWarning: Failed to clone Rust SDK source. This is optional — the pre-built WASM binary in wasm/ is what the app uses at runtime. The Rust source is only needed if you want to rebuild the WASM module.\n',
28
+ )
29
+ }
@@ -0,0 +1,13 @@
1
+ import { AuthFlow } from './components/auth/AuthFlow'
2
+ import { UploadZone } from './components/upload/UploadZone'
3
+ import { useAuthStore } from './stores/auth'
4
+
5
+ export default function App() {
6
+ const step = useAuthStore((s) => s.step)
7
+
8
+ if (step === 'connected') {
9
+ return <UploadZone />
10
+ }
11
+
12
+ return <AuthFlow />
13
+ }
@@ -0,0 +1,21 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ export function DevNote({
4
+ title,
5
+ children,
6
+ }: {
7
+ title: string
8
+ children: ReactNode
9
+ }) {
10
+ return (
11
+ <div className="border-l-4 border-amber-500 bg-amber-950/50 rounded-r-lg p-4 space-y-1">
12
+ <p className="text-amber-400 text-xs font-semibold uppercase tracking-wider">
13
+ Developer Note
14
+ </p>
15
+ <p className="text-amber-200 text-sm font-medium">{title}</p>
16
+ <div className="text-amber-200/80 text-xs leading-relaxed">
17
+ {children}
18
+ </div>
19
+ </div>
20
+ )
21
+ }
@@ -0,0 +1,84 @@
1
+ import { useState } from 'react'
2
+ import type { Builder } from '../../lib/sdk'
3
+ import { useAuthStore } from '../../stores/auth'
4
+ import { DevNote } from '../DevNote'
5
+
6
+ export function ApproveScreen({
7
+ builder,
8
+ }: {
9
+ builder: React.RefObject<Builder | null>
10
+ }) {
11
+ const { approvalUrl, setStep, setError } = useAuthStore()
12
+ const [waiting, setWaiting] = useState(false)
13
+
14
+ async function handleWaitForApproval() {
15
+ const b = builder.current
16
+ if (!b) {
17
+ setError('No builder instance')
18
+ return
19
+ }
20
+
21
+ setWaiting(true)
22
+ try {
23
+ await b.waitForApproval()
24
+ setStep('recovery')
25
+ } catch (e) {
26
+ setError(e instanceof Error ? e.message : 'Approval failed')
27
+ } finally {
28
+ setWaiting(false)
29
+ }
30
+ }
31
+
32
+ return (
33
+ <div className="flex flex-col items-center justify-center min-h-screen px-4">
34
+ <div className="w-full max-w-md space-y-6">
35
+ <div className="text-center space-y-2">
36
+ <h1 className="text-2xl font-semibold text-white">
37
+ Approve Connection
38
+ </h1>
39
+ <p className="text-neutral-400 text-sm">
40
+ Open the link below in a new tab to approve the connection, then
41
+ click &quot;Check Approval&quot;.
42
+ </p>
43
+ </div>
44
+
45
+ <DevNote title="Out-of-Band Approval">
46
+ <p>
47
+ The user must visit the approval URL in another tab (or on the
48
+ indexer&apos;s dashboard) to authorize your app. This is an
49
+ out-of-band step — your app polls for approval via{' '}
50
+ <code className="text-amber-300">builder.waitForApproval()</code>.
51
+ Once approved, the flow continues to recovery phrase setup.
52
+ </p>
53
+ </DevNote>
54
+
55
+ {approvalUrl && (
56
+ <a
57
+ href={approvalUrl}
58
+ target="_blank"
59
+ rel="noopener noreferrer"
60
+ className="block w-full text-center py-3 bg-neutral-800 hover:bg-neutral-700 text-green-400 font-mono text-sm rounded-lg transition-colors break-all px-4"
61
+ >
62
+ {approvalUrl}
63
+ </a>
64
+ )}
65
+
66
+ <button
67
+ type="button"
68
+ onClick={handleWaitForApproval}
69
+ disabled={waiting}
70
+ className="w-full py-3 bg-green-600 hover:bg-green-700 disabled:bg-neutral-700 disabled:text-neutral-500 text-white font-medium rounded-lg transition-colors"
71
+ >
72
+ {waiting ? (
73
+ <span className="flex items-center justify-center gap-2">
74
+ <span className="w-4 h-4 border-2 border-neutral-400 border-t-white rounded-full animate-spin" />
75
+ Waiting for approval...
76
+ </span>
77
+ ) : (
78
+ 'Check Approval'
79
+ )}
80
+ </button>
81
+ </div>
82
+ </div>
83
+ )
84
+ }
@@ -0,0 +1,77 @@
1
+ import { useEffect, useRef } from 'react'
2
+ import { fromHex } from '../../lib/hex'
3
+ import { AppKey, Builder, initWasm, type SDK } from '../../lib/sdk'
4
+ import { applyPresetToBuilder, useAuthStore } from '../../stores/auth'
5
+ import { ApproveScreen } from './ApproveScreen'
6
+ import { ConnectScreen } from './ConnectScreen'
7
+ import { LoadingScreen } from './LoadingScreen'
8
+ import { RecoveryScreen } from './RecoveryScreen'
9
+
10
+ export function AuthFlow() {
11
+ const step = useAuthStore((s) => s.step)
12
+ const error = useAuthStore((s) => s.error)
13
+ const setError = useAuthStore((s) => s.setError)
14
+ const builderRef = useRef<Builder | null>(null)
15
+
16
+ useEffect(() => {
17
+ let cancelled = false
18
+
19
+ async function init() {
20
+ const { storedKeyHex, indexerUrl, performancePreset, setSdk, setStep } =
21
+ useAuthStore.getState()
22
+ try {
23
+ await initWasm()
24
+
25
+ if (storedKeyHex && indexerUrl) {
26
+ const keyBytes = fromHex(storedKeyHex)
27
+ const appKey = new AppKey(keyBytes)
28
+ const builder = new Builder(indexerUrl)
29
+ applyPresetToBuilder(builder, performancePreset)
30
+ const result: SDK | null = await builder.connected(appKey)
31
+
32
+ if (cancelled) return
33
+ if (result) {
34
+ setSdk(result)
35
+ return
36
+ }
37
+ }
38
+
39
+ if (!cancelled) {
40
+ setStep('connect')
41
+ }
42
+ } catch (e) {
43
+ if (!cancelled) {
44
+ console.error('Init error:', e)
45
+ setStep('connect')
46
+ }
47
+ }
48
+ }
49
+
50
+ init()
51
+ return () => {
52
+ cancelled = true
53
+ }
54
+ }, [])
55
+
56
+ return (
57
+ <>
58
+ {error && (
59
+ <div className="fixed top-4 left-1/2 -translate-x-1/2 z-50 px-4 py-2 bg-red-900/90 border border-red-700 rounded-lg text-red-200 text-sm max-w-md text-center">
60
+ {error}
61
+ <button
62
+ type="button"
63
+ onClick={() => setError(null)}
64
+ className="ml-2 text-red-400 hover:text-red-300"
65
+ >
66
+ Dismiss
67
+ </button>
68
+ </div>
69
+ )}
70
+
71
+ {step === 'loading' && <LoadingScreen />}
72
+ {step === 'connect' && <ConnectScreen builder={builderRef} />}
73
+ {step === 'approve' && <ApproveScreen builder={builderRef} />}
74
+ {step === 'recovery' && <RecoveryScreen builder={builderRef} />}
75
+ </>
76
+ )
77
+ }
@@ -0,0 +1,214 @@
1
+ import { useState } from 'react'
2
+ import { APP_KEY, APP_META, DEFAULT_INDEXER_URL } from '../../lib/constants'
3
+ import { Builder } from '../../lib/sdk'
4
+ import {
5
+ applyPresetToBuilder,
6
+ type PerformancePreset,
7
+ PRESETS,
8
+ useAuthStore,
9
+ } from '../../stores/auth'
10
+ import { DevNote } from '../DevNote'
11
+
12
+ const PRESET_OPTIONS: {
13
+ key: PerformancePreset
14
+ label: string
15
+ sublabel: string
16
+ color: string
17
+ }[] = [
18
+ {
19
+ key: 'conservative',
20
+ label: 'Conservative',
21
+ sublabel: 'Most Stable',
22
+ color: 'bg-green-600 border-green-500',
23
+ },
24
+ {
25
+ key: 'balanced',
26
+ label: 'Balanced',
27
+ sublabel: 'Recommended',
28
+ color: 'bg-blue-600 border-blue-500',
29
+ },
30
+ {
31
+ key: 'fast',
32
+ label: 'Fast',
33
+ sublabel: 'May Be Unstable',
34
+ color: 'bg-amber-600 border-amber-500',
35
+ },
36
+ ]
37
+
38
+ export function ConnectScreen({
39
+ builder,
40
+ }: {
41
+ builder: React.RefObject<Builder | null>
42
+ }) {
43
+ const {
44
+ indexerUrl,
45
+ performancePreset,
46
+ setIndexerUrl,
47
+ setPerformancePreset,
48
+ setStep,
49
+ setError,
50
+ setApprovalUrl,
51
+ } = useAuthStore()
52
+ const [url, setUrl] = useState(indexerUrl || DEFAULT_INDEXER_URL)
53
+ const [loading, setLoading] = useState(false)
54
+ const [showCurl, setShowCurl] = useState(false)
55
+ const [curlResponse, setCurlResponse] = useState('')
56
+
57
+ async function handleConnect() {
58
+ setLoading(true)
59
+ setError(null)
60
+ try {
61
+ const b = new Builder(url)
62
+ applyPresetToBuilder(b, performancePreset)
63
+ builder.current = b
64
+ setIndexerUrl(url)
65
+
66
+ try {
67
+ await b.requestConnection(APP_META)
68
+ const approvalUrl = b.responseUrl()
69
+ setApprovalUrl(approvalUrl)
70
+ setStep('approve')
71
+ } catch {
72
+ setShowCurl(true)
73
+ }
74
+ } catch (e) {
75
+ setError(e instanceof Error ? e.message : 'Failed to connect')
76
+ } finally {
77
+ setLoading(false)
78
+ }
79
+ }
80
+
81
+ function handleCurlSubmit() {
82
+ try {
83
+ const b = builder.current
84
+ if (!b) {
85
+ setError('No builder instance')
86
+ return
87
+ }
88
+ b.setConnectionResponse(APP_KEY, curlResponse)
89
+ const approvalUrl = b.responseUrl()
90
+ setApprovalUrl(approvalUrl)
91
+ setStep('approve')
92
+ } catch (e) {
93
+ setError(e instanceof Error ? e.message : 'Invalid response')
94
+ }
95
+ }
96
+
97
+ const currentPreset = PRESETS[performancePreset]
98
+
99
+ const curlCommand = `curl -X POST ${url}/auth/connect \\
100
+ -H "Content-Type: application/json" \\
101
+ -d '${APP_META}'`
102
+
103
+ return (
104
+ <div className="flex flex-col items-center justify-center min-h-screen px-4">
105
+ <div className="w-full max-w-md space-y-6">
106
+ <div className="text-center space-y-2">
107
+ <h1 className="text-2xl font-semibold text-white">
108
+ Connect to Indexer
109
+ </h1>
110
+ <p className="text-neutral-400 text-sm">
111
+ Enter your Sia indexer URL to get started
112
+ </p>
113
+ </div>
114
+
115
+ <DevNote title="Indexer URL & App Key">
116
+ <p>
117
+ The indexer URL points to your Sia storage provider. The default is{' '}
118
+ <code className="text-amber-300">https://app.sia.storage</code>.
119
+ Your app key (set in{' '}
120
+ <code className="text-amber-300">src/lib/constants.ts</code>)
121
+ uniquely identifies your app to the indexer.
122
+ </p>
123
+ <p className="mt-1">
124
+ If the direct connection fails (CORS), the app falls back to a
125
+ manual curl flow where the user pastes the response.
126
+ </p>
127
+ </DevNote>
128
+
129
+ <div className="space-y-4">
130
+ <input
131
+ type="url"
132
+ value={url}
133
+ onChange={(e) => setUrl(e.target.value)}
134
+ placeholder="https://app.sia.storage"
135
+ className="w-full px-4 py-3 bg-neutral-900 border border-neutral-700 rounded-lg text-white placeholder-neutral-500 focus:outline-none focus:border-green-500"
136
+ />
137
+
138
+ <button
139
+ type="button"
140
+ onClick={handleConnect}
141
+ disabled={loading || !url}
142
+ className="w-full py-3 bg-green-600 hover:bg-green-700 disabled:bg-neutral-700 disabled:text-neutral-500 text-white font-medium rounded-lg transition-colors"
143
+ >
144
+ {loading ? 'Connecting...' : 'Connect'}
145
+ </button>
146
+ </div>
147
+
148
+ <div className="space-y-3">
149
+ <p className="text-sm text-neutral-400">Performance</p>
150
+ <div className="grid grid-cols-3 gap-2">
151
+ {PRESET_OPTIONS.map((opt) => (
152
+ <button
153
+ key={opt.key}
154
+ type="button"
155
+ onClick={() => setPerformancePreset(opt.key)}
156
+ className={`py-3 px-2 rounded-lg text-center transition-all border-2 ${
157
+ performancePreset === opt.key
158
+ ? `${opt.color} text-white`
159
+ : 'bg-neutral-900 border-neutral-700 text-neutral-400 hover:border-neutral-500'
160
+ }`}
161
+ >
162
+ <div className="text-sm font-medium">{opt.label}</div>
163
+ <div className="text-xs opacity-80">{opt.sublabel}</div>
164
+ </button>
165
+ ))}
166
+ </div>
167
+ <DevNote title="Performance Presets">
168
+ <p>
169
+ These control how many concurrent operations the SDK performs.
170
+ Conservative is safest, Fast uses more connections but may hit
171
+ rate limits. See{' '}
172
+ <code className="text-amber-300">src/stores/auth.ts</code> to
173
+ customize values.
174
+ </p>
175
+ </DevNote>
176
+ <div className="text-xs text-neutral-500 space-y-1">
177
+ <p>
178
+ Price fetches: {currentPreset.priceFetches} &middot; Downloads:{' '}
179
+ {currentPreset.downloads} &middot; Uploads:{' '}
180
+ {currentPreset.uploads}
181
+ </p>
182
+ </div>
183
+ </div>
184
+
185
+ {showCurl && (
186
+ <div className="space-y-4 p-4 bg-neutral-900 rounded-lg border border-neutral-700">
187
+ <p className="text-sm text-neutral-300">
188
+ Direct connection failed (CORS). Run this command and paste the
189
+ response:
190
+ </p>
191
+ <pre className="text-xs bg-neutral-950 p-3 rounded overflow-x-auto text-green-400">
192
+ {curlCommand}
193
+ </pre>
194
+ <textarea
195
+ value={curlResponse}
196
+ onChange={(e) => setCurlResponse(e.target.value)}
197
+ placeholder="Paste the JSON response here..."
198
+ rows={4}
199
+ className="w-full px-3 py-2 bg-neutral-950 border border-neutral-700 rounded text-sm text-white placeholder-neutral-500 focus:outline-none focus:border-green-500 font-mono"
200
+ />
201
+ <button
202
+ type="button"
203
+ onClick={handleCurlSubmit}
204
+ disabled={!curlResponse}
205
+ className="w-full py-2 bg-green-600 hover:bg-green-700 disabled:bg-neutral-700 disabled:text-neutral-500 text-white text-sm font-medium rounded-lg transition-colors"
206
+ >
207
+ Submit Response
208
+ </button>
209
+ </div>
210
+ )}
211
+ </div>
212
+ </div>
213
+ )
214
+ }
@@ -0,0 +1,8 @@
1
+ export function LoadingScreen({ message }: { message?: string }) {
2
+ return (
3
+ <div className="flex flex-col items-center justify-center min-h-screen gap-4">
4
+ <div className="w-8 h-8 border-2 border-neutral-600 border-t-green-500 rounded-full animate-spin" />
5
+ <p className="text-neutral-400 text-sm">{message || 'Initializing...'}</p>
6
+ </div>
7
+ )
8
+ }