create-sia-app 0.1.14 → 0.1.16
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.
- package/package.json +1 -1
- package/template/CLAUDE.md +4 -4
- package/template/README.md +2 -2
- package/template/package.json +1 -1
- package/template/src/components/CopyButton.tsx +1 -1
- package/template/src/components/DevNote.tsx +4 -4
- package/template/src/components/Navbar.tsx +5 -5
- package/template/src/components/Toast.tsx +1 -1
- package/template/src/components/auth/ApproveScreen.tsx +11 -11
- package/template/src/components/auth/AuthFlow.tsx +3 -3
- package/template/src/components/auth/ConnectScreen.tsx +7 -7
- package/template/src/components/auth/LoadingScreen.tsx +2 -2
- package/template/src/components/auth/RecoveryScreen.tsx +14 -14
- package/template/src/components/upload/UploadZone.tsx +27 -27
- package/template/src/index.css +2 -2
- package/template/src/lib/constants.ts +2 -2
- package/template/src/stores/auth.ts +3 -2
- package/template/vite.config.ts +1 -1
package/package.json
CHANGED
package/template/CLAUDE.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
A starter template for building decentralized storage apps on the Sia network. Uses [
|
|
5
|
+
A starter template for building decentralized storage apps on the Sia network. Uses [`@siafoundation/sia-storage`](https://www.npmjs.com/package/@siafoundation/sia-storage) — a TypeScript SDK that ships a pre-compiled WASM binary for encryption, uploads, downloads, and key management. WASM runs on the main thread (Rust async — no workers required).
|
|
6
6
|
|
|
7
|
-
**Tech stack:** React 19, TypeScript, Vite, Tailwind CSS 4, Zustand,
|
|
7
|
+
**Tech stack:** React 19, TypeScript, Vite, Tailwind CSS 4, Zustand, `@siafoundation/sia-storage`
|
|
8
8
|
|
|
9
9
|
## Architecture
|
|
10
10
|
|
|
@@ -28,7 +28,7 @@ Returning users skip `requestConnection`/`waitForApproval`/`register` entirely.
|
|
|
28
28
|
|
|
29
29
|
### SDK
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
`@siafoundation/sia-storage` handles:
|
|
32
32
|
- Encrypted file uploads/downloads (erasure coding + encryption)
|
|
33
33
|
- Key derivation from recovery phrases (BIP-39)
|
|
34
34
|
- Object pinning and metadata management
|
|
@@ -62,7 +62,7 @@ Auth state persists to localStorage via Zustand's `persist` middleware. The stor
|
|
|
62
62
|
### Upload a file
|
|
63
63
|
|
|
64
64
|
```ts
|
|
65
|
-
import { PinnedObject } from 'sia-storage'
|
|
65
|
+
import { PinnedObject } from '@siafoundation/sia-storage'
|
|
66
66
|
|
|
67
67
|
const object = new PinnedObject()
|
|
68
68
|
const pinnedObject = await sdk.upload(object, file.stream(), {
|
package/template/README.md
CHANGED
|
@@ -76,7 +76,7 @@ const sdk = useAuthStore((s) => s.sdk)
|
|
|
76
76
|
- [Tailwind CSS](https://tailwindcss.com) 4
|
|
77
77
|
- [Zustand](https://zustand.docs.pmnd.rs) (state management)
|
|
78
78
|
- [Biome](https://biomejs.dev) (linting & formatting)
|
|
79
|
-
- [sia-storage](https://www.npmjs.com/package/sia-storage) (Sia SDK — encryption, erasure coding, direct host transfers via WASM)
|
|
79
|
+
- [@siafoundation/sia-storage](https://www.npmjs.com/package/@siafoundation/sia-storage) (Sia SDK — encryption, erasure coding, direct host transfers via WASM)
|
|
80
80
|
|
|
81
81
|
## Project Structure
|
|
82
82
|
|
|
@@ -94,4 +94,4 @@ src/
|
|
|
94
94
|
## Learn More
|
|
95
95
|
|
|
96
96
|
- [Sia Documentation](https://docs.sia.tech)
|
|
97
|
-
- [sia-storage](https://www.npmjs.com/package/sia-storage)
|
|
97
|
+
- [@siafoundation/sia-storage](https://www.npmjs.com/package/@siafoundation/sia-storage)
|
package/template/package.json
CHANGED
|
@@ -16,7 +16,7 @@ export function CopyButton({
|
|
|
16
16
|
navigator.clipboard.writeText(value)
|
|
17
17
|
addToast(label)
|
|
18
18
|
}}
|
|
19
|
-
className="p-1 text-neutral-
|
|
19
|
+
className="p-1 text-neutral-400 hover:text-neutral-700 transition-colors"
|
|
20
20
|
title="Copy"
|
|
21
21
|
>
|
|
22
22
|
<svg
|
|
@@ -8,12 +8,12 @@ export function DevNote({
|
|
|
8
8
|
children: ReactNode
|
|
9
9
|
}) {
|
|
10
10
|
return (
|
|
11
|
-
<div className="border-l-4 border-amber-500 bg-amber-
|
|
12
|
-
<p className="text-amber-
|
|
11
|
+
<div className="border-l-4 border-amber-500 bg-amber-50 rounded-r-lg p-4 space-y-1">
|
|
12
|
+
<p className="text-amber-700 text-xs font-semibold uppercase tracking-wider">
|
|
13
13
|
Developer Note
|
|
14
14
|
</p>
|
|
15
|
-
<p className="text-amber-
|
|
16
|
-
<div className="text-amber-
|
|
15
|
+
<p className="text-amber-900 text-sm font-medium">{title}</p>
|
|
16
|
+
<div className="text-amber-900/80 text-xs leading-relaxed">
|
|
17
17
|
{children}
|
|
18
18
|
</div>
|
|
19
19
|
</div>
|
|
@@ -23,16 +23,16 @@ export function Navbar() {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
return (
|
|
26
|
-
<header className="border-b border-neutral-
|
|
26
|
+
<header className="border-b border-neutral-200/80">
|
|
27
27
|
<div className="flex items-center justify-between px-6 py-3 max-w-5xl mx-auto">
|
|
28
|
-
<h1 className="text-sm font-semibold text-
|
|
28
|
+
<h1 className="text-sm font-semibold text-neutral-900 tracking-tight">
|
|
29
29
|
{APP_NAME}
|
|
30
30
|
</h1>
|
|
31
31
|
{isConnected && publicKey && (
|
|
32
32
|
<div className="flex items-center gap-3">
|
|
33
33
|
<span className="relative flex h-2 w-2">
|
|
34
|
-
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-
|
|
35
|
-
<span className="relative inline-flex rounded-full h-2 w-2 bg-green-
|
|
34
|
+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-500 opacity-75" />
|
|
35
|
+
<span className="relative inline-flex rounded-full h-2 w-2 bg-green-600" />
|
|
36
36
|
</span>
|
|
37
37
|
<span
|
|
38
38
|
className="text-[11px] font-mono text-neutral-500"
|
|
@@ -44,7 +44,7 @@ export function Navbar() {
|
|
|
44
44
|
<button
|
|
45
45
|
type="button"
|
|
46
46
|
onClick={handleSignOut}
|
|
47
|
-
className="text-xs text-neutral-500 hover:text-neutral-
|
|
47
|
+
className="text-xs text-neutral-500 hover:text-neutral-900 transition-colors ml-1"
|
|
48
48
|
>
|
|
49
49
|
Sign Out
|
|
50
50
|
</button>
|
|
@@ -10,7 +10,7 @@ export function Toasts() {
|
|
|
10
10
|
{toasts.map((toast) => (
|
|
11
11
|
<div
|
|
12
12
|
key={toast.id}
|
|
13
|
-
className="animate-fade-in px-4 py-2 bg-
|
|
13
|
+
className="animate-fade-in px-4 py-2 bg-white border border-neutral-200 rounded-lg text-sm text-neutral-700 shadow-lg"
|
|
14
14
|
>
|
|
15
15
|
{toast.message}
|
|
16
16
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react'
|
|
2
|
-
import type { Builder } from 'sia-storage'
|
|
2
|
+
import type { Builder } from '@siafoundation/sia-storage'
|
|
3
3
|
import { useAuthStore } from '../../stores/auth'
|
|
4
4
|
import { CopyButton } from '../CopyButton'
|
|
5
5
|
import { DevNote } from '../DevNote'
|
|
@@ -63,10 +63,10 @@ export function ApproveScreen({
|
|
|
63
63
|
<div className="flex flex-col items-center justify-center flex-1 px-4">
|
|
64
64
|
<div className="w-full max-w-md space-y-6">
|
|
65
65
|
<div className="text-center space-y-2">
|
|
66
|
-
<h1 className="text-2xl font-semibold text-
|
|
66
|
+
<h1 className="text-2xl font-semibold text-neutral-900">
|
|
67
67
|
Approve Connection
|
|
68
68
|
</h1>
|
|
69
|
-
<p className="text-neutral-
|
|
69
|
+
<p className="text-neutral-600 text-sm">
|
|
70
70
|
Open the link below to approve this app, then return here.
|
|
71
71
|
</p>
|
|
72
72
|
</div>
|
|
@@ -76,15 +76,15 @@ export function ApproveScreen({
|
|
|
76
76
|
The user must visit the approval URL in another tab (or on the
|
|
77
77
|
indexer's dashboard) to authorize your app. This is an
|
|
78
78
|
out-of-band step — your app polls for approval via{' '}
|
|
79
|
-
<code className="text-amber-
|
|
79
|
+
<code className="text-amber-700">builder.waitForApproval()</code>.
|
|
80
80
|
Once approved, the flow continues to recovery phrase setup.
|
|
81
81
|
</p>
|
|
82
82
|
</DevNote>
|
|
83
83
|
|
|
84
84
|
{approvalUrl && (
|
|
85
85
|
<div className="space-y-3">
|
|
86
|
-
<div className="flex items-center gap-2 p-3 bg-
|
|
87
|
-
<span className="flex-1 text-sm font-mono text-neutral-
|
|
86
|
+
<div className="flex items-center gap-2 p-3 bg-white border border-neutral-300 rounded-lg">
|
|
87
|
+
<span className="flex-1 text-sm font-mono text-neutral-600 truncate">
|
|
88
88
|
{approvalUrl}
|
|
89
89
|
</span>
|
|
90
90
|
<CopyButton value={approvalUrl} label="URL copied" />
|
|
@@ -104,11 +104,11 @@ export function ApproveScreen({
|
|
|
104
104
|
type="button"
|
|
105
105
|
onClick={handleManualCheck}
|
|
106
106
|
disabled={manualChecking}
|
|
107
|
-
className="w-full py-3 bg-neutral-
|
|
107
|
+
className="w-full py-3 bg-neutral-100 hover:bg-neutral-200 disabled:bg-neutral-200 disabled:text-neutral-400 text-neutral-900 font-medium rounded-lg transition-colors"
|
|
108
108
|
>
|
|
109
109
|
{manualChecking ? (
|
|
110
110
|
<span className="flex items-center justify-center gap-2">
|
|
111
|
-
<span className="w-4 h-4 border-2 border-neutral-
|
|
111
|
+
<span className="w-4 h-4 border-2 border-neutral-300 border-t-neutral-900 rounded-full animate-spin" />
|
|
112
112
|
Checking...
|
|
113
113
|
</span>
|
|
114
114
|
) : (
|
|
@@ -116,12 +116,12 @@ export function ApproveScreen({
|
|
|
116
116
|
)}
|
|
117
117
|
</button>
|
|
118
118
|
|
|
119
|
-
<div className="flex items-center justify-center gap-2 text-xs text-neutral-
|
|
119
|
+
<div className="flex items-center justify-center gap-2 text-xs text-neutral-500">
|
|
120
120
|
{polling ? (
|
|
121
121
|
<>
|
|
122
122
|
<span className="relative flex h-1.5 w-1.5">
|
|
123
|
-
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-
|
|
124
|
-
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-green-
|
|
123
|
+
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-500 opacity-75" />
|
|
124
|
+
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-green-600" />
|
|
125
125
|
</span>
|
|
126
126
|
Polling for approval...
|
|
127
127
|
</>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react'
|
|
2
|
-
import { AppKey, Builder, initSia } from 'sia-storage'
|
|
2
|
+
import { AppKey, Builder, initSia } from '@siafoundation/sia-storage'
|
|
3
3
|
import { APP_META } from '../../lib/constants'
|
|
4
4
|
import { useAuthStore } from '../../stores/auth'
|
|
5
5
|
import { ApproveScreen } from './ApproveScreen'
|
|
@@ -54,12 +54,12 @@ export function AuthFlow() {
|
|
|
54
54
|
return (
|
|
55
55
|
<div className="flex-1 flex flex-col">
|
|
56
56
|
{error && (
|
|
57
|
-
<div className="fixed top-4 left-1/2 -translate-x-1/2 z-50 px-4 py-2 bg-red-
|
|
57
|
+
<div className="fixed top-4 left-1/2 -translate-x-1/2 z-50 px-4 py-2 bg-red-50 border border-red-200 rounded-lg text-red-800 text-sm max-w-md text-center shadow-sm">
|
|
58
58
|
{error}
|
|
59
59
|
<button
|
|
60
60
|
type="button"
|
|
61
61
|
onClick={() => setError(null)}
|
|
62
|
-
className="ml-2 text-red-
|
|
62
|
+
className="ml-2 text-red-600 hover:text-red-900"
|
|
63
63
|
>
|
|
64
64
|
Dismiss
|
|
65
65
|
</button>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
|
-
import { Builder } from 'sia-storage'
|
|
2
|
+
import { Builder } from '@siafoundation/sia-storage'
|
|
3
3
|
import { APP_META, DEFAULT_INDEXER_URL } from '../../lib/constants'
|
|
4
4
|
import { useAuthStore } from '../../stores/auth'
|
|
5
5
|
import { DevNote } from '../DevNote'
|
|
@@ -45,10 +45,10 @@ export function ConnectScreen({
|
|
|
45
45
|
<div className="flex flex-col items-center justify-center flex-1 px-4">
|
|
46
46
|
<div className="w-full max-w-md space-y-6">
|
|
47
47
|
<div className="text-center space-y-2">
|
|
48
|
-
<h1 className="text-2xl font-semibold text-
|
|
48
|
+
<h1 className="text-2xl font-semibold text-neutral-900">
|
|
49
49
|
Connect to Indexer
|
|
50
50
|
</h1>
|
|
51
|
-
<p className="text-neutral-
|
|
51
|
+
<p className="text-neutral-600 text-sm">
|
|
52
52
|
Enter your Sia indexer URL to get started
|
|
53
53
|
</p>
|
|
54
54
|
</div>
|
|
@@ -56,9 +56,9 @@ export function ConnectScreen({
|
|
|
56
56
|
<DevNote title="Indexer URL & App Key">
|
|
57
57
|
<p>
|
|
58
58
|
The indexer URL points to your Sia storage provider. The default is{' '}
|
|
59
|
-
<code className="text-amber-
|
|
59
|
+
<code className="text-amber-700">https://sia.storage</code>. Your
|
|
60
60
|
app key (set in{' '}
|
|
61
|
-
<code className="text-amber-
|
|
61
|
+
<code className="text-amber-700">src/lib/constants.ts</code>)
|
|
62
62
|
uniquely identifies your app to the indexer.
|
|
63
63
|
</p>
|
|
64
64
|
<p className="mt-1">
|
|
@@ -73,14 +73,14 @@ export function ConnectScreen({
|
|
|
73
73
|
value={url}
|
|
74
74
|
onChange={(e) => setUrl(e.target.value)}
|
|
75
75
|
placeholder="https://sia.storage"
|
|
76
|
-
className="w-full px-4 py-3 bg-
|
|
76
|
+
className="w-full px-4 py-3 bg-white border border-neutral-300 rounded-lg text-neutral-900 placeholder-neutral-400 focus:outline-none focus:border-green-600"
|
|
77
77
|
/>
|
|
78
78
|
|
|
79
79
|
<button
|
|
80
80
|
type="button"
|
|
81
81
|
onClick={handleConnect}
|
|
82
82
|
disabled={loading || !url}
|
|
83
|
-
className="w-full py-3 bg-green-600 hover:bg-green-700 disabled:bg-neutral-
|
|
83
|
+
className="w-full py-3 bg-green-600 hover:bg-green-700 disabled:bg-neutral-200 disabled:text-neutral-400 text-white font-medium rounded-lg transition-colors"
|
|
84
84
|
>
|
|
85
85
|
{loading ? 'Connecting...' : 'Connect'}
|
|
86
86
|
</button>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export function LoadingScreen({ message }: { message?: string }) {
|
|
2
2
|
return (
|
|
3
3
|
<div className="flex flex-col items-center justify-center flex-1 gap-4">
|
|
4
|
-
<div className="w-8 h-8 border-2 border-neutral-
|
|
5
|
-
<p className="text-neutral-
|
|
4
|
+
<div className="w-8 h-8 border-2 border-neutral-300 border-t-green-600 rounded-full animate-spin" />
|
|
5
|
+
<p className="text-neutral-500 text-sm">{message || 'Initializing...'}</p>
|
|
6
6
|
</div>
|
|
7
7
|
)
|
|
8
8
|
}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
type Builder,
|
|
4
4
|
generateRecoveryPhrase,
|
|
5
5
|
validateRecoveryPhrase,
|
|
6
|
-
} from 'sia-storage'
|
|
6
|
+
} from '@siafoundation/sia-storage'
|
|
7
7
|
import { useAuthStore } from '../../stores/auth'
|
|
8
8
|
import { CopyButton } from '../CopyButton'
|
|
9
9
|
import { DevNote } from '../DevNote'
|
|
@@ -71,10 +71,10 @@ export function RecoveryScreen({
|
|
|
71
71
|
<div className="flex flex-col items-center justify-center flex-1 px-4">
|
|
72
72
|
<div className="w-full max-w-md space-y-6">
|
|
73
73
|
<div className="text-center space-y-2">
|
|
74
|
-
<h1 className="text-2xl font-semibold text-
|
|
74
|
+
<h1 className="text-2xl font-semibold text-neutral-900">
|
|
75
75
|
Recovery Phrase
|
|
76
76
|
</h1>
|
|
77
|
-
<p className="text-neutral-
|
|
77
|
+
<p className="text-neutral-600 text-sm">
|
|
78
78
|
Generate a new recovery phrase or enter an existing one.
|
|
79
79
|
</p>
|
|
80
80
|
</div>
|
|
@@ -100,7 +100,7 @@ export function RecoveryScreen({
|
|
|
100
100
|
<button
|
|
101
101
|
type="button"
|
|
102
102
|
onClick={() => setMode('import')}
|
|
103
|
-
className="w-full py-3 bg-neutral-
|
|
103
|
+
className="w-full py-3 bg-neutral-100 hover:bg-neutral-200 text-neutral-900 font-medium rounded-lg transition-colors"
|
|
104
104
|
>
|
|
105
105
|
Enter Existing Phrase
|
|
106
106
|
</button>
|
|
@@ -114,12 +114,12 @@ export function RecoveryScreen({
|
|
|
114
114
|
<div className="flex flex-col items-center justify-center flex-1 px-4">
|
|
115
115
|
<div className="w-full max-w-md space-y-6">
|
|
116
116
|
<div className="text-center space-y-2">
|
|
117
|
-
<h1 className="text-2xl font-semibold text-
|
|
117
|
+
<h1 className="text-2xl font-semibold text-neutral-900">
|
|
118
118
|
{mode === 'generate'
|
|
119
119
|
? 'Save Your Recovery Phrase'
|
|
120
120
|
: 'Enter Recovery Phrase'}
|
|
121
121
|
</h1>
|
|
122
|
-
<p className="text-neutral-
|
|
122
|
+
<p className="text-neutral-600 text-sm">
|
|
123
123
|
{mode === 'generate'
|
|
124
124
|
? 'Write down these 12 words in order. You will need them to recover your account.'
|
|
125
125
|
: 'Enter your 12-word recovery phrase.'}
|
|
@@ -128,14 +128,14 @@ export function RecoveryScreen({
|
|
|
128
128
|
|
|
129
129
|
{mode === 'generate' ? (
|
|
130
130
|
<div className="space-y-2">
|
|
131
|
-
<div className="grid grid-cols-3 gap-2 p-4 bg-
|
|
131
|
+
<div className="grid grid-cols-3 gap-2 p-4 bg-white rounded-lg border border-neutral-300">
|
|
132
132
|
{generatedPhrase.split(' ').map((word, i) => (
|
|
133
133
|
<div
|
|
134
134
|
key={`${word}-${i}`}
|
|
135
|
-
className="text-center py-2 bg-neutral-
|
|
135
|
+
className="text-center py-2 bg-neutral-100 rounded text-sm"
|
|
136
136
|
>
|
|
137
|
-
<span className="text-neutral-
|
|
138
|
-
<span className="text-
|
|
137
|
+
<span className="text-neutral-400 mr-1">{i + 1}.</span>
|
|
138
|
+
<span className="text-neutral-900">{word}</span>
|
|
139
139
|
</div>
|
|
140
140
|
))}
|
|
141
141
|
</div>
|
|
@@ -153,10 +153,10 @@ export function RecoveryScreen({
|
|
|
153
153
|
onChange={(e) => handleValidatePhrase(e.target.value)}
|
|
154
154
|
placeholder="Enter your 12-word recovery phrase..."
|
|
155
155
|
rows={3}
|
|
156
|
-
className="w-full px-4 py-3 bg-
|
|
156
|
+
className="w-full px-4 py-3 bg-white border border-neutral-300 rounded-lg text-neutral-900 placeholder-neutral-400 focus:outline-none focus:border-green-600"
|
|
157
157
|
/>
|
|
158
158
|
{phraseError && (
|
|
159
|
-
<p className="text-red-
|
|
159
|
+
<p className="text-red-600 text-sm">{phraseError}</p>
|
|
160
160
|
)}
|
|
161
161
|
</div>
|
|
162
162
|
)}
|
|
@@ -165,7 +165,7 @@ export function RecoveryScreen({
|
|
|
165
165
|
type="button"
|
|
166
166
|
onClick={handleRegister}
|
|
167
167
|
disabled={loading || !phrase.trim()}
|
|
168
|
-
className="w-full py-3 bg-green-600 hover:bg-green-700 disabled:bg-neutral-
|
|
168
|
+
className="w-full py-3 bg-green-600 hover:bg-green-700 disabled:bg-neutral-200 disabled:text-neutral-400 text-white font-medium rounded-lg transition-colors"
|
|
169
169
|
>
|
|
170
170
|
{loading ? 'Registering...' : 'Complete Setup'}
|
|
171
171
|
</button>
|
|
@@ -178,7 +178,7 @@ export function RecoveryScreen({
|
|
|
178
178
|
setGeneratedPhrase('')
|
|
179
179
|
setPhraseError(null)
|
|
180
180
|
}}
|
|
181
|
-
className="w-full py-2 text-neutral-
|
|
181
|
+
className="w-full py-2 text-neutral-500 hover:text-neutral-900 text-sm transition-colors"
|
|
182
182
|
>
|
|
183
183
|
Back
|
|
184
184
|
</button>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
|
-
import { encodedSize, PinnedObject, type ShardProgress } from 'sia-storage'
|
|
2
|
+
import { encodedSize, PinnedObject, type ShardProgress } from '@siafoundation/sia-storage'
|
|
3
3
|
import { APP_KEY, DATA_SHARDS, PARITY_SHARDS } from '../../lib/constants'
|
|
4
4
|
import { useAuthStore } from '../../stores/auth'
|
|
5
5
|
import { DevNote } from '../DevNote'
|
|
@@ -233,34 +233,34 @@ export function UploadZone() {
|
|
|
233
233
|
<DevNote title="Replace Your App Key">
|
|
234
234
|
<p>
|
|
235
235
|
You're using the template placeholder. Set your own key in{' '}
|
|
236
|
-
<code className="text-amber-
|
|
236
|
+
<code className="text-amber-700">src/lib/constants.ts</code> or
|
|
237
237
|
scaffold a fresh project with{' '}
|
|
238
|
-
<code className="text-amber-
|
|
238
|
+
<code className="text-amber-700">bunx create-sia-app</code>.
|
|
239
239
|
</p>
|
|
240
240
|
</DevNote>
|
|
241
241
|
)}
|
|
242
242
|
|
|
243
243
|
<DevNote title="Upload & Download">
|
|
244
244
|
<p>
|
|
245
|
-
<code className="text-amber-
|
|
245
|
+
<code className="text-amber-700">
|
|
246
246
|
sdk.upload(object, file.stream(), opts)
|
|
247
247
|
</code>{' '}
|
|
248
248
|
encrypts, erasure-codes, and streams shards directly to Sia hosts.{' '}
|
|
249
|
-
<code className="text-amber-
|
|
250
|
-
returns a <code className="text-amber-
|
|
249
|
+
<code className="text-amber-700">sdk.download(object, opts)</code>{' '}
|
|
250
|
+
returns a <code className="text-amber-700">ReadableStream</code> of
|
|
251
251
|
decrypted bytes. Per-shard progress is reported via{' '}
|
|
252
|
-
<code className="text-amber-
|
|
253
|
-
<code className="text-amber-
|
|
252
|
+
<code className="text-amber-700">onShardUploaded</code> /{' '}
|
|
253
|
+
<code className="text-amber-700">onShardDownloaded</code>.
|
|
254
254
|
</p>
|
|
255
255
|
</DevNote>
|
|
256
256
|
|
|
257
257
|
{error && (
|
|
258
|
-
<div className="flex items-center justify-between px-4 py-2.5 bg-red-
|
|
258
|
+
<div className="flex items-center justify-between px-4 py-2.5 bg-red-50 border border-red-200 rounded-lg text-red-800 text-sm">
|
|
259
259
|
<span>{error}</span>
|
|
260
260
|
<button
|
|
261
261
|
type="button"
|
|
262
262
|
onClick={() => setError(null)}
|
|
263
|
-
className="text-red-
|
|
263
|
+
className="text-red-600 hover:text-red-900 text-xs ml-4 shrink-0"
|
|
264
264
|
>
|
|
265
265
|
Dismiss
|
|
266
266
|
</button>
|
|
@@ -280,10 +280,10 @@ export function UploadZone() {
|
|
|
280
280
|
}}
|
|
281
281
|
className={`relative block border-2 border-dashed rounded-xl p-16 text-center transition-all duration-150 ${
|
|
282
282
|
uploading
|
|
283
|
-
? 'border-neutral-
|
|
283
|
+
? 'border-neutral-300 cursor-default'
|
|
284
284
|
: dragOver
|
|
285
|
-
? 'border-green-
|
|
286
|
-
: 'border-neutral-
|
|
285
|
+
? 'border-green-600 bg-green-600/5 cursor-pointer'
|
|
286
|
+
: 'border-neutral-300 hover:border-neutral-400 cursor-pointer'
|
|
287
287
|
}`}
|
|
288
288
|
>
|
|
289
289
|
<input
|
|
@@ -300,24 +300,24 @@ export function UploadZone() {
|
|
|
300
300
|
|
|
301
301
|
{activeUpload ? (
|
|
302
302
|
<div className="space-y-4">
|
|
303
|
-
<p className="text-neutral-
|
|
303
|
+
<p className="text-neutral-700 text-sm">
|
|
304
304
|
Uploading{' '}
|
|
305
|
-
<span className="text-
|
|
305
|
+
<span className="text-neutral-900">{activeUpload.fileName}</span>{' '}
|
|
306
306
|
<span className="text-neutral-500">
|
|
307
307
|
({formatBytes(activeUpload.fileSize)})
|
|
308
308
|
</span>
|
|
309
309
|
</p>
|
|
310
|
-
<div className="w-full max-w-xs mx-auto bg-neutral-
|
|
310
|
+
<div className="w-full max-w-xs mx-auto bg-neutral-200 rounded-full h-1.5 overflow-hidden">
|
|
311
311
|
{activeUpload.shardsDone === 0 ? (
|
|
312
|
-
<div className="bg-green-
|
|
312
|
+
<div className="bg-green-600 h-full rounded-full w-1/4 animate-indeterminate" />
|
|
313
313
|
) : (
|
|
314
314
|
<div
|
|
315
|
-
className="bg-green-
|
|
315
|
+
className="bg-green-600 h-full rounded-full transition-all duration-300"
|
|
316
316
|
style={{ width: `${uploadPercent}%` }}
|
|
317
317
|
/>
|
|
318
318
|
)}
|
|
319
319
|
</div>
|
|
320
|
-
<p className="text-neutral-
|
|
320
|
+
<p className="text-neutral-500 text-xs font-mono">
|
|
321
321
|
{activeUpload.shardsDone} shards ·{' '}
|
|
322
322
|
{formatBytes(
|
|
323
323
|
(activeUpload.bytesUploaded / activeUpload.encodedTotal) *
|
|
@@ -329,7 +329,7 @@ export function UploadZone() {
|
|
|
329
329
|
) : (
|
|
330
330
|
<div className="space-y-2">
|
|
331
331
|
<svg
|
|
332
|
-
className="w-8 h-8 mx-auto text-neutral-
|
|
332
|
+
className="w-8 h-8 mx-auto text-neutral-400"
|
|
333
333
|
viewBox="0 0 24 24"
|
|
334
334
|
fill="none"
|
|
335
335
|
stroke="currentColor"
|
|
@@ -339,10 +339,10 @@ export function UploadZone() {
|
|
|
339
339
|
<path d="M12 16V4m0 0l-4 4m4-4l4 4" />
|
|
340
340
|
<path d="M20 16v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2" />
|
|
341
341
|
</svg>
|
|
342
|
-
<p className="text-neutral-
|
|
342
|
+
<p className="text-neutral-600 text-sm">
|
|
343
343
|
Drop files here or click to browse
|
|
344
344
|
</p>
|
|
345
|
-
<p className="text-neutral-
|
|
345
|
+
<p className="text-neutral-500 text-xs">
|
|
346
346
|
Encrypted end-to-end and stored on the Sia network
|
|
347
347
|
</p>
|
|
348
348
|
</div>
|
|
@@ -355,7 +355,7 @@ export function UploadZone() {
|
|
|
355
355
|
<h2 className="text-xs font-medium text-neutral-500 uppercase tracking-wider">
|
|
356
356
|
{files.length} file{files.length !== 1 ? 's' : ''}
|
|
357
357
|
</h2>
|
|
358
|
-
<div className="divide-y divide-neutral-
|
|
358
|
+
<div className="divide-y divide-neutral-200/80">
|
|
359
359
|
{files.map((file) => {
|
|
360
360
|
const isDownloading = downloading === file.id
|
|
361
361
|
return (
|
|
@@ -364,10 +364,10 @@ export function UploadZone() {
|
|
|
364
364
|
className="flex items-center justify-between py-3 group"
|
|
365
365
|
>
|
|
366
366
|
<div className="flex-1 min-w-0 mr-4">
|
|
367
|
-
<p className="text-sm text-neutral-
|
|
367
|
+
<p className="text-sm text-neutral-900 truncate">
|
|
368
368
|
{file.metadata.name}
|
|
369
369
|
</p>
|
|
370
|
-
<p className="text-xs text-neutral-
|
|
370
|
+
<p className="text-xs text-neutral-500 mt-0.5">
|
|
371
371
|
{formatBytes(file.metadata.size)}
|
|
372
372
|
{file.metadata.type !== 'application/octet-stream' && (
|
|
373
373
|
<span> · {file.metadata.type}</span>
|
|
@@ -388,7 +388,7 @@ export function UploadZone() {
|
|
|
388
388
|
type="button"
|
|
389
389
|
onClick={() => downloadFile(file)}
|
|
390
390
|
disabled={downloading !== null}
|
|
391
|
-
className="text-xs text-neutral-
|
|
391
|
+
className="text-xs text-neutral-500 hover:text-neutral-900 disabled:opacity-30 disabled:cursor-default transition-colors"
|
|
392
392
|
title="Download"
|
|
393
393
|
>
|
|
394
394
|
{isDownloading ? (
|
|
@@ -418,7 +418,7 @@ export function UploadZone() {
|
|
|
418
418
|
)}
|
|
419
419
|
</button>
|
|
420
420
|
<span
|
|
421
|
-
className="text-[11px] text-neutral-
|
|
421
|
+
className="text-[11px] text-neutral-400 font-mono group-hover:text-neutral-700 transition-colors"
|
|
422
422
|
title={file.metadata.hash}
|
|
423
423
|
>
|
|
424
424
|
{file.metadata.hash.slice(0, 8)}...
|
package/template/src/index.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { AppMetadata } from 'sia-storage'
|
|
1
|
+
import type { AppMetadata } from '@siafoundation/sia-storage'
|
|
2
2
|
|
|
3
|
-
// biome-ignore format:
|
|
3
|
+
// biome-ignore format: long hex literal
|
|
4
4
|
export const APP_KEY = '{{APP_KEY}}'
|
|
5
5
|
export const APP_NAME = '{{APP_NAME}}'
|
|
6
6
|
export const DEFAULT_INDEXER_URL = '{{INDEXER_URL}}'
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { Sdk } from 'sia-storage'
|
|
1
|
+
import type { Sdk } from '@siafoundation/sia-storage'
|
|
2
2
|
import { create } from 'zustand'
|
|
3
3
|
import { persist } from 'zustand/middleware'
|
|
4
|
+
import { APP_KEY } from '../lib/constants'
|
|
4
5
|
|
|
5
6
|
export type AuthStep =
|
|
6
7
|
| 'loading'
|
|
@@ -50,7 +51,7 @@ export const useAuthStore = create<AuthState>()(
|
|
|
50
51
|
}),
|
|
51
52
|
}),
|
|
52
53
|
{
|
|
53
|
-
name:
|
|
54
|
+
name: `sia-auth-${APP_KEY.slice(0, 16)}`,
|
|
54
55
|
partialize: (state) => ({
|
|
55
56
|
storedKeyHex: state.storedKeyHex,
|
|
56
57
|
indexerUrl: state.indexerUrl,
|
package/template/vite.config.ts
CHANGED
|
@@ -6,5 +6,5 @@ export default defineConfig({
|
|
|
6
6
|
plugins: [react(), tailwindcss()],
|
|
7
7
|
// sia-storage loads its WASM via `new URL(..., import.meta.url)`; excluding
|
|
8
8
|
// it from the deps pre-bundler keeps that URL pointing at the real file.
|
|
9
|
-
optimizeDeps: { exclude: ['sia-storage'] },
|
|
9
|
+
optimizeDeps: { exclude: ['@siafoundation/sia-storage'] },
|
|
10
10
|
})
|