@blu1606/create-walrus-app 2.0.0 → 2.2.0
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/dist/context.js +1 -0
- package/dist/generator/layers.js +3 -0
- package/dist/index.js +1 -0
- package/dist/post-install/index.js +5 -0
- package/dist/post-install/walrus-deploy.d.ts +6 -0
- package/dist/post-install/walrus-deploy.js +77 -0
- package/dist/prompts.js +17 -0
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
- package/presets/react-mysten-gallery/README.md +60 -0
- package/presets/react-mysten-gallery/package.json +2 -1
- package/presets/react-mysten-gallery/scripts/setup-walrus-deploy.sh +282 -0
- package/presets/react-mysten-simple-upload/README.md +60 -0
- package/presets/react-mysten-simple-upload/package.json +2 -1
- package/presets/react-mysten-simple-upload/scripts/setup-walrus-deploy.sh +286 -0
- package/presets/react-mysten-simple-upload-enoki/.env.example +40 -0
- package/presets/react-mysten-simple-upload-enoki/.gitkeep +4 -0
- package/presets/react-mysten-simple-upload-enoki/README.md +332 -0
- package/presets/react-mysten-simple-upload-enoki/index.html +13 -0
- package/presets/react-mysten-simple-upload-enoki/package.json +37 -0
- package/presets/react-mysten-simple-upload-enoki/scripts/setup-walrus-deploy.sh +286 -0
- package/presets/react-mysten-simple-upload-enoki/src/App.tsx +27 -0
- package/presets/react-mysten-simple-upload-enoki/src/components/features/enoki-auth-button.tsx +29 -0
- package/presets/react-mysten-simple-upload-enoki/src/components/features/file-preview.tsx +73 -0
- package/presets/react-mysten-simple-upload-enoki/src/components/features/upload-form.tsx +61 -0
- package/presets/react-mysten-simple-upload-enoki/src/components/features/wallet-connect.tsx +38 -0
- package/presets/react-mysten-simple-upload-enoki/src/components/layout/app-layout.tsx +21 -0
- package/presets/react-mysten-simple-upload-enoki/src/hooks/use-download.ts +24 -0
- package/presets/react-mysten-simple-upload-enoki/src/hooks/use-enoki-auth.ts +52 -0
- package/presets/react-mysten-simple-upload-enoki/src/hooks/use-upload.ts +45 -0
- package/presets/react-mysten-simple-upload-enoki/src/hooks/use-wallet.ts +32 -0
- package/presets/react-mysten-simple-upload-enoki/src/index.css +322 -0
- package/presets/react-mysten-simple-upload-enoki/src/index.ts +24 -0
- package/presets/react-mysten-simple-upload-enoki/src/lib/enoki/constants.ts +23 -0
- package/presets/react-mysten-simple-upload-enoki/src/lib/enoki/index.ts +6 -0
- package/presets/react-mysten-simple-upload-enoki/src/lib/enoki/storage-adapter.ts +31 -0
- package/presets/react-mysten-simple-upload-enoki/src/lib/walrus/adapter.ts +197 -0
- package/presets/react-mysten-simple-upload-enoki/src/lib/walrus/client.ts +87 -0
- package/presets/react-mysten-simple-upload-enoki/src/lib/walrus/index.ts +4 -0
- package/presets/react-mysten-simple-upload-enoki/src/lib/walrus/types.ts +92 -0
- package/presets/react-mysten-simple-upload-enoki/src/main.tsx +19 -0
- package/presets/react-mysten-simple-upload-enoki/src/providers/EnokiProvider.tsx +23 -0
- package/presets/react-mysten-simple-upload-enoki/src/providers/QueryProvider.tsx +18 -0
- package/presets/react-mysten-simple-upload-enoki/src/providers/WalletProvider.tsx +52 -0
- package/presets/react-mysten-simple-upload-enoki/src/providers/index.ts +7 -0
- package/presets/react-mysten-simple-upload-enoki/src/utils/env.ts +41 -0
- package/presets/react-mysten-simple-upload-enoki/src/utils/mime-type.ts +97 -0
- package/presets/react-mysten-simple-upload-enoki/tsconfig.json +39 -0
- package/presets/react-mysten-simple-upload-enoki/tsconfig.node.json +10 -0
- package/presets/react-mysten-simple-upload-enoki/vite.config.ts +19 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Walrus application with Enoki zkLogin authentication scaffolded with create-walrus-app",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc && vite build",
|
|
9
|
+
"dev": "vite",
|
|
10
|
+
"lint": "eslint . --ext .ts,.tsx",
|
|
11
|
+
"preview": "vite preview",
|
|
12
|
+
"type-check": "tsc --noEmit",
|
|
13
|
+
"setup-walrus-deploy": "bash scripts/setup-walrus-deploy.sh"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@mysten/dapp-kit": "^0.14.0",
|
|
17
|
+
"@mysten/enoki": "^0.15.0",
|
|
18
|
+
"@mysten/sui": "^1.10.0",
|
|
19
|
+
"@mysten/walrus": "^0.9.0",
|
|
20
|
+
"@tanstack/react-query": "^5.17.0",
|
|
21
|
+
"react": "^18.2.0",
|
|
22
|
+
"react-dom": "^18.2.0",
|
|
23
|
+
"zod": "^3.22.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/react": "^18.2.48",
|
|
27
|
+
"@types/react-dom": "^18.2.18",
|
|
28
|
+
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
|
29
|
+
"@typescript-eslint/parser": "^6.19.0",
|
|
30
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
31
|
+
"eslint": "^8.56.0",
|
|
32
|
+
"eslint-plugin-react": "^7.33.2",
|
|
33
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
34
|
+
"typescript": "^5.3.3",
|
|
35
|
+
"vite": "^5.0.11"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# setup-walrus-deploy.sh - Zero-config Walrus Sites deployment setup (testnet)
|
|
3
|
+
# Auto-installs dependencies, downloads tools, clones portal
|
|
4
|
+
# Supports: Linux, macOS, Windows (Git Bash/WSL)
|
|
5
|
+
|
|
6
|
+
set -e # Exit on error
|
|
7
|
+
|
|
8
|
+
echo "🦭 Walrus Sites Zero-Config Setup (testnet)"
|
|
9
|
+
echo ""
|
|
10
|
+
|
|
11
|
+
# ============================================================================
|
|
12
|
+
# 1. Detect OS & Architecture
|
|
13
|
+
# ============================================================================
|
|
14
|
+
detect_os() {
|
|
15
|
+
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
|
|
16
|
+
ARCH=$(uname -m)
|
|
17
|
+
|
|
18
|
+
case "$OS" in
|
|
19
|
+
linux*) OS_TYPE="linux" ;;
|
|
20
|
+
darwin*) OS_TYPE="macos" ;;
|
|
21
|
+
mingw*|msys*|cygwin*) OS_TYPE="windows" ;;
|
|
22
|
+
*)
|
|
23
|
+
echo "❌ Unsupported OS: $OS"
|
|
24
|
+
exit 1
|
|
25
|
+
;;
|
|
26
|
+
esac
|
|
27
|
+
|
|
28
|
+
echo "✅ Detected: $OS_TYPE ($ARCH)"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# ============================================================================
|
|
32
|
+
# 2. Auto-install Bun (if not exists)
|
|
33
|
+
# ============================================================================
|
|
34
|
+
setup_bun() {
|
|
35
|
+
if command -v bun &>/dev/null; then
|
|
36
|
+
echo "✅ Bun already installed: $(bun --version)"
|
|
37
|
+
return 0
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
echo "📥 Installing Bun..."
|
|
41
|
+
if [ "$OS_TYPE" = "windows" ]; then
|
|
42
|
+
# Windows (PowerShell install via Git Bash)
|
|
43
|
+
powershell -c "irm bun.sh/install.ps1 | iex"
|
|
44
|
+
else
|
|
45
|
+
# Linux/macOS
|
|
46
|
+
curl -fsSL https://bun.sh/install | bash
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Add to PATH for current session
|
|
50
|
+
if [ "$OS_TYPE" = "windows" ]; then
|
|
51
|
+
export PATH="$USERPROFILE/.bun/bin:$PATH"
|
|
52
|
+
else
|
|
53
|
+
export BUN_INSTALL="$HOME/.bun"
|
|
54
|
+
export PATH="$BUN_INSTALL/bin:$PATH"
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
# Verify installation
|
|
58
|
+
if command -v bun &>/dev/null; then
|
|
59
|
+
echo "✅ Bun installed: $(bun --version)"
|
|
60
|
+
else
|
|
61
|
+
echo "⚠️ Bun installed but not in PATH. Restart terminal or run:"
|
|
62
|
+
echo " export PATH=\"\$HOME/.bun/bin:\$PATH\""
|
|
63
|
+
fi
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# ============================================================================
|
|
67
|
+
# 3. Download site-builder binary (if not exists)
|
|
68
|
+
# ============================================================================
|
|
69
|
+
setup_site_builder() {
|
|
70
|
+
# Set install directory based on OS
|
|
71
|
+
if [ "$OS_TYPE" = "windows" ]; then
|
|
72
|
+
WALRUS_BIN="$USERPROFILE/.walrus/bin"
|
|
73
|
+
SITE_BUILDER="$WALRUS_BIN/site-builder.exe"
|
|
74
|
+
else
|
|
75
|
+
WALRUS_BIN="$HOME/.walrus/bin"
|
|
76
|
+
SITE_BUILDER="$WALRUS_BIN/site-builder"
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# Check if already exists
|
|
80
|
+
if [ -f "$SITE_BUILDER" ]; then
|
|
81
|
+
echo "✅ site-builder already exists: $SITE_BUILDER"
|
|
82
|
+
chmod +x "$SITE_BUILDER" 2>/dev/null || true
|
|
83
|
+
return 0
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
echo "📥 Downloading site-builder for $OS_TYPE..."
|
|
87
|
+
mkdir -p "$WALRUS_BIN"
|
|
88
|
+
|
|
89
|
+
# Select binary based on OS
|
|
90
|
+
case "$OS_TYPE" in
|
|
91
|
+
linux) BINARY_NAME="site-builder-linux" ;;
|
|
92
|
+
macos) BINARY_NAME="site-builder-macos" ;;
|
|
93
|
+
windows) BINARY_NAME="site-builder-windows.exe" ;;
|
|
94
|
+
esac
|
|
95
|
+
|
|
96
|
+
DOWNLOAD_URL="https://github.com/MystenLabs/walrus-sites/releases/latest/download/$BINARY_NAME"
|
|
97
|
+
|
|
98
|
+
# Download with retry
|
|
99
|
+
if ! curl -fsSL -o "$SITE_BUILDER" "$DOWNLOAD_URL"; then
|
|
100
|
+
echo "❌ Failed to download site-builder from: $DOWNLOAD_URL"
|
|
101
|
+
exit 1
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
chmod +x "$SITE_BUILDER"
|
|
105
|
+
echo "✅ site-builder installed: $SITE_BUILDER"
|
|
106
|
+
|
|
107
|
+
# Add to PATH hint (won't persist after script)
|
|
108
|
+
if [ "$OS_TYPE" = "windows" ]; then
|
|
109
|
+
export PATH="$USERPROFILE/.walrus/bin:$PATH"
|
|
110
|
+
else
|
|
111
|
+
export PATH="$HOME/.walrus/bin:$PATH"
|
|
112
|
+
fi
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# ============================================================================
|
|
116
|
+
# 4. Clone Walrus Portal (if not exists)
|
|
117
|
+
# ============================================================================
|
|
118
|
+
setup_portal() {
|
|
119
|
+
if [ "$OS_TYPE" = "windows" ]; then
|
|
120
|
+
PORTAL_DIR="$USERPROFILE/.walrus/portal"
|
|
121
|
+
else
|
|
122
|
+
PORTAL_DIR="$HOME/.walrus/portal"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
if [ -d "$PORTAL_DIR" ]; then
|
|
126
|
+
echo "✅ Portal already exists: $PORTAL_DIR"
|
|
127
|
+
echo " Updating..."
|
|
128
|
+
cd "$PORTAL_DIR"
|
|
129
|
+
git pull --quiet || echo "⚠️ Git pull failed (may be offline)"
|
|
130
|
+
else
|
|
131
|
+
echo "📂 Cloning Walrus Sites portal..."
|
|
132
|
+
mkdir -p "$(dirname "$PORTAL_DIR")"
|
|
133
|
+
|
|
134
|
+
# Clone with depth=1 for faster download
|
|
135
|
+
if ! git clone --depth=1 https://github.com/MystenLabs/walrus-sites.git "$PORTAL_DIR"; then
|
|
136
|
+
echo "❌ Failed to clone portal repository"
|
|
137
|
+
exit 1
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
cd "$PORTAL_DIR"
|
|
141
|
+
echo "✅ Portal cloned to: $PORTAL_DIR"
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
# Setup .env if not exists
|
|
145
|
+
if [ ! -f ".env" ]; then
|
|
146
|
+
if [ -f ".env.example" ]; then
|
|
147
|
+
cp .env.example .env
|
|
148
|
+
echo "✅ Created .env from .env.example"
|
|
149
|
+
else
|
|
150
|
+
# Create minimal .env
|
|
151
|
+
cat > .env << 'EOF'
|
|
152
|
+
# Walrus Portal Configuration (Testnet)
|
|
153
|
+
WALRUS_NETWORK=testnet
|
|
154
|
+
SUI_PRIVATE_KEY=
|
|
155
|
+
|
|
156
|
+
# Optional: Uncomment to customize
|
|
157
|
+
# PORTAL_PORT=3000
|
|
158
|
+
EOF
|
|
159
|
+
echo "✅ Created .env template"
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
echo ""
|
|
163
|
+
echo "⚠️ ACTION REQUIRED:"
|
|
164
|
+
echo " Edit $PORTAL_DIR/.env"
|
|
165
|
+
echo " Add your SUI_PRIVATE_KEY=0x..."
|
|
166
|
+
echo ""
|
|
167
|
+
else
|
|
168
|
+
echo "✅ .env already configured"
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
# Install portal dependencies
|
|
172
|
+
echo "📦 Installing portal dependencies..."
|
|
173
|
+
if ! bun install --silent; then
|
|
174
|
+
echo "❌ Bun install failed"
|
|
175
|
+
exit 1
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
echo "✅ Portal dependencies installed"
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
# ============================================================================
|
|
182
|
+
# 5. Add npm scripts to project package.json
|
|
183
|
+
# ============================================================================
|
|
184
|
+
add_project_scripts() {
|
|
185
|
+
PROJECT_DIR="${1:-.}" # Default to current directory
|
|
186
|
+
cd "$PROJECT_DIR" || { echo "❌ Invalid project directory"; exit 1; }
|
|
187
|
+
|
|
188
|
+
if [ ! -f "package.json" ]; then
|
|
189
|
+
echo "❌ No package.json found in $PROJECT_DIR"
|
|
190
|
+
exit 1
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
echo "📝 Adding Walrus deploy scripts to package.json..."
|
|
194
|
+
|
|
195
|
+
# Use Node.js to safely modify package.json (guaranteed to exist in Node projects)
|
|
196
|
+
node -e "
|
|
197
|
+
const fs = require('fs');
|
|
198
|
+
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
199
|
+
|
|
200
|
+
pkg.scripts = pkg.scripts || {};
|
|
201
|
+
|
|
202
|
+
// Add scripts (only if not already exists)
|
|
203
|
+
if (!pkg.scripts['setup-walrus-deploy']) {
|
|
204
|
+
pkg.scripts['setup-walrus-deploy'] = 'bash scripts/setup-walrus-deploy.sh';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!pkg.scripts['deploy:walrus']) {
|
|
208
|
+
const siteBuilderPath = process.platform === 'win32'
|
|
209
|
+
? '%USERPROFILE%/.walrus/bin/site-builder.exe'
|
|
210
|
+
: '~/.walrus/bin/site-builder';
|
|
211
|
+
pkg.scripts['deploy:walrus'] = siteBuilderPath + ' --context=testnet deploy ./dist --epochs 10';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!pkg.scripts['walrus:portal']) {
|
|
215
|
+
const portalPath = process.platform === 'win32'
|
|
216
|
+
? 'cd %USERPROFILE%/.walrus/portal'
|
|
217
|
+
: 'cd ~/.walrus/portal';
|
|
218
|
+
pkg.scripts['walrus:portal'] = portalPath + ' && bun run server';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\\n');
|
|
222
|
+
" || {
|
|
223
|
+
echo "❌ Failed to update package.json"
|
|
224
|
+
exit 1
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
echo "✅ Scripts added to package.json:"
|
|
228
|
+
echo " - setup-walrus-deploy"
|
|
229
|
+
echo " - deploy:walrus"
|
|
230
|
+
echo " - walrus:portal"
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
# ============================================================================
|
|
234
|
+
# Main Execution
|
|
235
|
+
# ============================================================================
|
|
236
|
+
main() {
|
|
237
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
238
|
+
|
|
239
|
+
# Trap errors
|
|
240
|
+
trap 'echo "❌ Setup failed at line $LINENO"' ERR
|
|
241
|
+
|
|
242
|
+
# Prerequisites check
|
|
243
|
+
if ! command -v git &>/dev/null; then
|
|
244
|
+
echo "❌ Git not found. Install: https://git-scm.com"
|
|
245
|
+
exit 1
|
|
246
|
+
fi
|
|
247
|
+
|
|
248
|
+
if ! command -v node &>/dev/null; then
|
|
249
|
+
echo "❌ Node.js not found. Install: https://nodejs.org"
|
|
250
|
+
exit 1
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
# Run setup steps
|
|
254
|
+
detect_os
|
|
255
|
+
setup_bun
|
|
256
|
+
setup_site_builder
|
|
257
|
+
setup_portal
|
|
258
|
+
add_project_scripts "$@"
|
|
259
|
+
|
|
260
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
261
|
+
echo ""
|
|
262
|
+
echo "🎉 Setup Complete!"
|
|
263
|
+
echo ""
|
|
264
|
+
echo "Next Steps:"
|
|
265
|
+
echo " 1. Configure your SUI private key:"
|
|
266
|
+
if [ "$OS_TYPE" = "windows" ]; then
|
|
267
|
+
echo " notepad %USERPROFILE%\\.walrus\\portal\\.env"
|
|
268
|
+
else
|
|
269
|
+
echo " nano ~/.walrus/portal/.env"
|
|
270
|
+
fi
|
|
271
|
+
echo " Add: SUI_PRIVATE_KEY=0x..."
|
|
272
|
+
echo ""
|
|
273
|
+
echo " 2. Build your project:"
|
|
274
|
+
echo " pnpm build"
|
|
275
|
+
echo ""
|
|
276
|
+
echo " 3. Deploy to Walrus Sites:"
|
|
277
|
+
echo " pnpm deploy:walrus"
|
|
278
|
+
echo ""
|
|
279
|
+
echo " 4. (Optional) Preview locally:"
|
|
280
|
+
echo " pnpm walrus:portal"
|
|
281
|
+
echo ""
|
|
282
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
# Run main with all script arguments
|
|
286
|
+
main "$@"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AppLayout } from './components/layout/app-layout.js';
|
|
2
|
+
import { UploadForm } from './components/features/upload-form.js';
|
|
3
|
+
import { FilePreview } from './components/features/file-preview.js';
|
|
4
|
+
import './index.css';
|
|
5
|
+
|
|
6
|
+
function App() {
|
|
7
|
+
return (
|
|
8
|
+
<AppLayout>
|
|
9
|
+
<div className="simple-upload-app">
|
|
10
|
+
<h2><span className="text-accent">📤</span> Simple Upload</h2>
|
|
11
|
+
<p className="text-secondary">Upload a file to <span className="text-accent">Walrus</span> and download it by <span className="text-accent">Blob ID</span></p>
|
|
12
|
+
|
|
13
|
+
<section className="upload-section">
|
|
14
|
+
<h3>Upload File</h3>
|
|
15
|
+
<UploadForm />
|
|
16
|
+
</section>
|
|
17
|
+
|
|
18
|
+
<section className="download-section">
|
|
19
|
+
<h3>Download File</h3>
|
|
20
|
+
<FilePreview />
|
|
21
|
+
</section>
|
|
22
|
+
</div>
|
|
23
|
+
</AppLayout>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default App;
|
package/presets/react-mysten-simple-upload-enoki/src/components/features/enoki-auth-button.tsx
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useEnokiAuth } from '../../hooks/use-enoki-auth.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Google OAuth login/logout button for zkLogin
|
|
5
|
+
*
|
|
6
|
+
* Shows login button when disconnected, logout button when connected
|
|
7
|
+
*/
|
|
8
|
+
export function EnokiAuthButton() {
|
|
9
|
+
const { isEnokiConnected, enokiAddress, login, logout } = useEnokiAuth();
|
|
10
|
+
|
|
11
|
+
if (isEnokiConnected) {
|
|
12
|
+
return (
|
|
13
|
+
<div className="enoki-auth">
|
|
14
|
+
<span className="enoki-address">
|
|
15
|
+
zkLogin: {enokiAddress?.slice(0, 6)}...{enokiAddress?.slice(-4)}
|
|
16
|
+
</span>
|
|
17
|
+
<button onClick={logout} className="logout-btn">
|
|
18
|
+
Logout
|
|
19
|
+
</button>
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<button onClick={login} className="google-login-btn">
|
|
26
|
+
🔐 Login with Google
|
|
27
|
+
</button>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useDownload, useMetadata } from '../../hooks/use-download.js';
|
|
3
|
+
|
|
4
|
+
export function FilePreview() {
|
|
5
|
+
const [blobId, setBlobId] = useState('');
|
|
6
|
+
const { data, isLoading, error } = useDownload(blobId);
|
|
7
|
+
const { data: metadata } = useMetadata(blobId);
|
|
8
|
+
|
|
9
|
+
const handleDownload = () => {
|
|
10
|
+
if (!data) return;
|
|
11
|
+
|
|
12
|
+
// Use original filename from metadata if available
|
|
13
|
+
let filename = metadata?.fileName || `walrus-${blobId.slice(0, 8)}`;
|
|
14
|
+
|
|
15
|
+
// If metadata doesn't have filename, auto-detect from content type
|
|
16
|
+
if (!metadata?.fileName) {
|
|
17
|
+
const contentType = metadata?.contentType || 'application/octet-stream';
|
|
18
|
+
|
|
19
|
+
if (contentType.startsWith('image/')) {
|
|
20
|
+
filename += `.${contentType.split('/')[1]}`;
|
|
21
|
+
} else if (contentType.startsWith('text/')) {
|
|
22
|
+
filename += '.txt';
|
|
23
|
+
} else if (contentType.includes('json')) {
|
|
24
|
+
filename += '.json';
|
|
25
|
+
} else {
|
|
26
|
+
filename += '.bin';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const contentType = metadata?.contentType || 'application/octet-stream';
|
|
31
|
+
const blob = new Blob([data as Uint8Array], { type: contentType });
|
|
32
|
+
const url = URL.createObjectURL(blob);
|
|
33
|
+
const a = document.createElement('a');
|
|
34
|
+
a.href = url;
|
|
35
|
+
a.download = filename;
|
|
36
|
+
a.click();
|
|
37
|
+
URL.revokeObjectURL(url);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Auto-detect if data is text or JSON for preview
|
|
41
|
+
const canPreview = data && (typeof data === 'string' || data instanceof Uint8Array);
|
|
42
|
+
const preview = canPreview && typeof data === 'string'
|
|
43
|
+
? data.slice(0, 200) // Show first 200 chars for text
|
|
44
|
+
: null;
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className="file-preview">
|
|
48
|
+
<input
|
|
49
|
+
type="text"
|
|
50
|
+
placeholder="Enter Blob ID"
|
|
51
|
+
value={blobId}
|
|
52
|
+
onChange={(e) => setBlobId(e.target.value)}
|
|
53
|
+
/>
|
|
54
|
+
|
|
55
|
+
{isLoading && <p className="text-secondary">Loading...</p>}
|
|
56
|
+
{error && <p className="error">Error: {error.message}</p>}
|
|
57
|
+
|
|
58
|
+
{data && (
|
|
59
|
+
<div className="preview-content icon-list">
|
|
60
|
+
<p className="text-success">✓ Blob found <span className="text-secondary">({data.byteLength || data.length} bytes)</span></p>
|
|
61
|
+
{metadata?.fileName && <p className="text-secondary">File: <span className="text-accent">{metadata.fileName}</span></p>}
|
|
62
|
+
{metadata?.contentType && <p className="text-secondary">Type: <span className="text-accent">{metadata.contentType}</span></p>}
|
|
63
|
+
{preview && (
|
|
64
|
+
<pre>
|
|
65
|
+
{preview}...
|
|
66
|
+
</pre>
|
|
67
|
+
)}
|
|
68
|
+
<button onClick={handleDownload}>Download File</button>
|
|
69
|
+
</div>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useUpload } from '../../hooks/use-upload.js';
|
|
3
|
+
import { useWallet } from '../../hooks/use-wallet.js';
|
|
4
|
+
|
|
5
|
+
export function UploadForm() {
|
|
6
|
+
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
|
7
|
+
const upload = useUpload();
|
|
8
|
+
const { isConnected } = useWallet();
|
|
9
|
+
|
|
10
|
+
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
11
|
+
const file = e.target.files?.[0];
|
|
12
|
+
if (file) setSelectedFile(file);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const handleUpload = async () => {
|
|
16
|
+
if (!selectedFile) return;
|
|
17
|
+
|
|
18
|
+
upload.mutate(
|
|
19
|
+
{ file: selectedFile, options: { epochs: 1 } },
|
|
20
|
+
{
|
|
21
|
+
onSuccess: (data) => {
|
|
22
|
+
alert(`Upload successful! Blob ID: ${data.blobId}`);
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="upload-form">
|
|
30
|
+
<input
|
|
31
|
+
type="file"
|
|
32
|
+
onChange={handleFileChange}
|
|
33
|
+
disabled={upload.isPending}
|
|
34
|
+
/>
|
|
35
|
+
|
|
36
|
+
{selectedFile && (
|
|
37
|
+
<div className="file-info">
|
|
38
|
+
<p className="text-secondary">Selected: <span className="text-accent">{selectedFile.name}</span></p>
|
|
39
|
+
<p className="text-secondary">Size: <span className="text-accent">{(selectedFile.size / 1024).toFixed(2)} KB</span></p>
|
|
40
|
+
</div>
|
|
41
|
+
)}
|
|
42
|
+
|
|
43
|
+
{!isConnected && (
|
|
44
|
+
<p className="warning">⚠️ Please connect your wallet to upload files</p>
|
|
45
|
+
)}
|
|
46
|
+
|
|
47
|
+
<button
|
|
48
|
+
onClick={handleUpload}
|
|
49
|
+
disabled={!selectedFile || !isConnected || upload.isPending}
|
|
50
|
+
>
|
|
51
|
+
{!isConnected
|
|
52
|
+
? 'Connect Wallet First'
|
|
53
|
+
: upload.isPending
|
|
54
|
+
? 'Uploading...'
|
|
55
|
+
: 'Upload to Walrus'}
|
|
56
|
+
</button>
|
|
57
|
+
|
|
58
|
+
{upload.isError && <p className="error">Error: {upload.error.message}</p>}
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ConnectButton } from '@mysten/dapp-kit';
|
|
2
|
+
import { useWallet } from '../../hooks/use-wallet.js';
|
|
3
|
+
import { useEnokiAuth } from '../../hooks/use-enoki-auth.js';
|
|
4
|
+
import { EnokiAuthButton } from './enoki-auth-button.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Dual authentication component
|
|
8
|
+
*
|
|
9
|
+
* Supports both zkLogin (Google OAuth) and standard Sui wallets
|
|
10
|
+
*/
|
|
11
|
+
export function WalletConnect() {
|
|
12
|
+
const { isConnected, address } = useWallet();
|
|
13
|
+
const { isEnokiConnected } = useEnokiAuth();
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="wallet-connect">
|
|
17
|
+
<EnokiAuthButton />
|
|
18
|
+
|
|
19
|
+
{!isEnokiConnected && (
|
|
20
|
+
<>
|
|
21
|
+
<div className="auth-divider">
|
|
22
|
+
<span>OR</span>
|
|
23
|
+
</div>
|
|
24
|
+
{isConnected ? (
|
|
25
|
+
<div className="wallet-info">
|
|
26
|
+
<span>
|
|
27
|
+
Wallet: {address?.slice(0, 6)}...{address?.slice(-4)}
|
|
28
|
+
</span>
|
|
29
|
+
</div>
|
|
30
|
+
) : (
|
|
31
|
+
<p>Connect standard wallet</p>
|
|
32
|
+
)}
|
|
33
|
+
<ConnectButton />
|
|
34
|
+
</>
|
|
35
|
+
)}
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { WalletConnect } from '../features/wallet-connect.js';
|
|
3
|
+
|
|
4
|
+
interface LayoutProps {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function AppLayout({ children }: LayoutProps) {
|
|
9
|
+
return (
|
|
10
|
+
<div className="app-layout">
|
|
11
|
+
<header className="app-header">
|
|
12
|
+
<h1><span className="text-secondary">🌊</span> <span className="text-accent">Walrus</span> App</h1>
|
|
13
|
+
<WalletConnect />
|
|
14
|
+
</header>
|
|
15
|
+
<main className="app-main">{children}</main>
|
|
16
|
+
<footer className="app-footer">
|
|
17
|
+
<p className="text-secondary">Powered by <span className="text-accent">Walrus</span> & <span style={{ color: 'var(--walrus-accent-blue)' }}>Sui</span></p>
|
|
18
|
+
</footer>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useQuery } from '@tanstack/react-query';
|
|
2
|
+
import { storageAdapter } from '../lib/walrus/index.js';
|
|
3
|
+
|
|
4
|
+
export function useDownload(blobId: string | null) {
|
|
5
|
+
return useQuery({
|
|
6
|
+
queryKey: ['blob', blobId],
|
|
7
|
+
queryFn: async () => {
|
|
8
|
+
if (!blobId) throw new Error('No blob ID provided');
|
|
9
|
+
return await storageAdapter.download(blobId);
|
|
10
|
+
},
|
|
11
|
+
enabled: !!blobId,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useMetadata(blobId: string | null) {
|
|
16
|
+
return useQuery({
|
|
17
|
+
queryKey: ['metadata', blobId],
|
|
18
|
+
queryFn: async () => {
|
|
19
|
+
if (!blobId) throw new Error('No blob ID provided');
|
|
20
|
+
return await storageAdapter.getMetadata(blobId);
|
|
21
|
+
},
|
|
22
|
+
enabled: !!blobId,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useEnokiFlow, useZkLogin } from '@mysten/enoki/react';
|
|
2
|
+
import { enokiConfig } from '../lib/enoki/index.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Enoki authentication hook
|
|
6
|
+
*
|
|
7
|
+
* Provides Google OAuth login/logout, zkLogin state, and unified signer interface
|
|
8
|
+
*/
|
|
9
|
+
export function useEnokiAuth() {
|
|
10
|
+
const enokiFlow = useEnokiFlow();
|
|
11
|
+
const { address } = useZkLogin();
|
|
12
|
+
|
|
13
|
+
const login = async () => {
|
|
14
|
+
const protocol = window.location.protocol;
|
|
15
|
+
const host = window.location.host;
|
|
16
|
+
const redirectUrl = `${protocol}//${host}/auth`;
|
|
17
|
+
|
|
18
|
+
const authUrl = await enokiFlow.createAuthorizationURL({
|
|
19
|
+
provider: 'google',
|
|
20
|
+
clientId: enokiConfig.VITE_GOOGLE_CLIENT_ID,
|
|
21
|
+
redirectUrl,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
window.location.href = authUrl;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const logout = () => {
|
|
28
|
+
enokiFlow.logout();
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const getSigner = () => {
|
|
32
|
+
if (!address || !enokiFlow) return null;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
address,
|
|
36
|
+
signAndExecuteTransaction: async (args: any) => {
|
|
37
|
+
const result = await enokiFlow.signAndExecuteTransaction({
|
|
38
|
+
transaction: args.transaction,
|
|
39
|
+
});
|
|
40
|
+
return { digest: result.digest };
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
isEnokiConnected: !!address,
|
|
47
|
+
enokiAddress: address,
|
|
48
|
+
login,
|
|
49
|
+
logout,
|
|
50
|
+
getSigner,
|
|
51
|
+
};
|
|
52
|
+
}
|