Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Shared Express app (no .listen here)
import express from 'express'
import cors from 'cors'
import multer from 'multer'
import pinataSDK from '@pinata/sdk'
import cors from 'cors'
import dotenv from 'dotenv'
import { Readable } from 'stream'
import express from 'express'
import multer from 'multer'
import path from 'path'
import { Readable } from 'stream'
import { fileURLToPath } from 'url'

const __filename = fileURLToPath(import.meta.url)
Expand Down Expand Up @@ -46,6 +46,12 @@ const pinata = process.env.PINATA_JWT

// Optional: test credentials at cold start
;(async () => {
const skip = process.env.SKIP_PINATA_TEST === 'true' || !process.env.PINATA_API_KEY || process.env.PINATA_API_KEY.includes('YOUR_PINATA')
if (skip) {
console.log('Pinata auth test skipped (set SKIP_PINATA_TEST=false and provide real keys to enable).')
return
}

try {
const auth = await pinata.testAuthentication?.()
console.log('Pinata auth OK:', auth || 'ok')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"main": "smart_contracts/index.ts",
"type": "module",
"scripts": {
"dev": "node nft_mint_server/server.js",
"build": "algokit compile ts smart_contracts --output-source-map --out-dir artifacts && algokit generate client smart_contracts/artifacts --output {app_spec_dir}/{contract_name}Client.ts",
"deploy": "ts-node-dev --transpile-only --watch .env -r dotenv/config smart_contracts/index.ts",
"deploy:ci": "ts-node --transpile-only -r dotenv/config smart_contracts/index.ts",
Expand Down
160 changes: 151 additions & 9 deletions QuickStartTemplate/projects/QuickStartTemplate-frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { SupportedWallet, WalletId, WalletManager, WalletProvider } from '@txnlab/use-wallet-react'
import { Analytics } from '@vercel/analytics/react'
import { SnackbarProvider } from 'notistack'
import Home from './Home'
import { getAlgodConfigFromViteEnvironment, getKmdConfigFromViteEnvironment } from './utils/network/getAlgoClientConfigs'
import { Analytics } from '@vercel/analytics/react'

import React, { useState } from 'react'
// ServicesPage replaced by rendering `Home` directly so users see service links on the homepage
import HelpPage from './pages/HelpPage'
import RegisterVehicle from './pages/RegisterVehicle'
import Settings from './pages/Settings'
import TransferTitle from './pages/TransferTitle'

// ...existing code...

let supportedWallets: SupportedWallet[]
if (import.meta.env.VITE_ALGOD_NETWORK === 'localnet') {
Expand Down Expand Up @@ -32,6 +41,19 @@ if (import.meta.env.VITE_ALGOD_NETWORK === 'localnet') {
export default function App() {
const algodConfig = getAlgodConfigFromViteEnvironment()

const [stage, setStage] = useState<'auth' | 'congrats' | 'app'>('auth')
const [authPage, setAuthPage] = useState<'signin' | 'signup'>('signin')
const [formUserId, setFormUserId] = useState('')
const [formPassphrase, setFormPassphrase] = useState('')
const [formConfirmPassphrase, setFormConfirmPassphrase] = useState('')

// app view state inside authenticated 'app' stage (declare hooks unconditionally)
const [appView, setAppView] = useState<'services' | 'register' | 'transfer' | 'help' | 'settings'>('services')

// Authenticated user info to show on congrats page
const [authUser, setAuthUser] = useState<string | null>(null)
const [authInternalId, setAuthInternalId] = useState<string | null>(null)

const walletManager = new WalletManager({
wallets: supportedWallets,
defaultNetwork: algodConfig.network,
Expand All @@ -49,12 +71,132 @@ export default function App() {
},
})

return (
<SnackbarProvider maxSnack={3}>
<WalletProvider manager={walletManager}>
<Home />
<Analytics />
</WalletProvider>
</SnackbarProvider>
)
// Render sign-in / sign-up before showing the wallet UI
if (stage === 'auth') {
const handleSignIn = (e: React.FormEvent) => {
e.preventDefault()
// basic validation placeholder
if (!formUserId || !formPassphrase) return
// set authenticated user info and move to congratulations screen
setAuthUser(formUserId)
setAuthInternalId(Date.now().toString(36))
setStage('congrats')
}

const handleSignUp = (e: React.FormEvent) => {
e.preventDefault()
// basic validation placeholder
if (!formUserId || !formPassphrase || !formConfirmPassphrase) return
if (formPassphrase !== formConfirmPassphrase) return
// set authenticated user info and move to congratulations screen
setAuthUser(formUserId)
setAuthInternalId(Date.now().toString(36))
setStage('congrats')
}

return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100">
<div className="bg-white p-8 rounded shadow-md w-full max-w-md">
<h2 className="text-2xl font-bold mb-6 text-center">
{authPage === 'signin' ? 'Sign In' : 'Sign Up'}
</h2>
{authPage === 'signin' ? (
<form onSubmit={handleSignIn} className="space-y-4">
<div>
<label className="block mb-1 font-medium">User ID</label>
<input type="text" value={formUserId} onChange={e => setFormUserId(e.target.value)} className="w-full border rounded px-3 py-2" placeholder="Enter your User ID" />
</div>
<div>
<label className="block mb-1 font-medium">Passphrase</label>
<input type="password" value={formPassphrase} onChange={e => setFormPassphrase(e.target.value)} className="w-full border rounded px-3 py-2" placeholder="Enter your passphrase" />
</div>
<button type="submit" className="w-full bg-blue-600 text-white py-2 rounded font-semibold hover:bg-blue-700">Sign In</button>
</form>
) : (
<form onSubmit={handleSignUp} className="space-y-4">
<div>
<label className="block mb-1 font-medium">User ID</label>
<input type="text" value={formUserId} onChange={e => setFormUserId(e.target.value)} className="w-full border rounded px-3 py-2" placeholder="Enter your User ID" />
</div>
<div>
<label className="block mb-1 font-medium">Passphrase</label>
<input type="password" value={formPassphrase} onChange={e => setFormPassphrase(e.target.value)} className="w-full border rounded px-3 py-2" placeholder="Enter your passphrase" />
</div>
<div>
<label className="block mb-1 font-medium">Confirm Passphrase</label>
<input type="password" value={formConfirmPassphrase} onChange={e => setFormConfirmPassphrase(e.target.value)} className="w-full border rounded px-3 py-2" placeholder="Confirm your passphrase" />
</div>
<button type="submit" className="w-full bg-blue-600 text-white py-2 rounded font-semibold hover:bg-blue-700">Sign Up</button>
</form>
)}

<div className="mt-4 text-center">
{authPage === 'signin' ? (
<button onClick={() => setAuthPage('signup')} className="text-blue-600 underline">Don't have an account? Sign Up</button>
) : (
<button onClick={() => setAuthPage('signin')} className="text-blue-600 underline">Already have an account? Sign In</button>
)}
</div>
</div>
</div>
)
}

// Congratulations page shown after successful sign in / sign up
if (stage === 'congrats') {
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-100">
<div className="bg-white p-8 rounded shadow-md w-full max-w-md text-center">
<h2 className="text-2xl font-bold mb-4">Congratulations!</h2>
{authUser && authInternalId ? (
<div className="mb-4">
<p className="font-medium">Welcome, <span className="text-indigo-700">{authUser}</span>!</p>
<p className="text-sm text-gray-600">Your ID: <span className="font-mono text-sm">{authInternalId}</span></p>
</div>
) : (
<p className="mb-4">Your account is ready.</p>
)}
<p className="mb-6">Click the button below to continue to the app.</p>
<button
onClick={() => setStage('app')}
className="w-full bg-green-600 text-white py-2 rounded font-semibold hover:bg-green-700"
>
Continue to Home
</button>
</div>
</div>
)
}

if (stage === 'app') {
const goBackToServices = () => setAppView('services')

return (
<SnackbarProvider maxSnack={3}>
<WalletProvider manager={walletManager}>
<div className="min-h-screen">
<header className="p-4 border-b flex justify-between items-center">
<h3 className="font-bold">QuickStart Template</h3>
<div className="flex items-center gap-2">
<button onClick={() => setStage('auth')} className="text-sm text-red-600">Logout</button>
</div>
</header>

<main>
{appView === 'services' && <Home onNavigate={v => setAppView(v)} />}
{appView === 'register' && <RegisterVehicle onBack={goBackToServices} />}
{appView === 'transfer' && <TransferTitle onBack={goBackToServices} />}
{appView === 'help' && <HelpPage onBack={goBackToServices} />}
{appView === 'settings' && <Settings onBack={goBackToServices} />}
</main>

<Analytics />
</div>
</WalletProvider>
</SnackbarProvider>
)
}

// Fallback (shouldn't be reachable because `stage` is restricted)
return null
}
Loading