create-openfort 0.1.10 → 1.0.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.
- package/CHANGELOG.md +16 -0
- package/dist/index.js +24 -23
- package/package.json +1 -1
- package/template/openfort-templates/firebase/AGENTS.md +1 -1
- package/template/openfort-templates/firebase/package.json +3 -3
- package/template/openfort-templates/firebase/src/App.tsx +2 -1
- package/template/openfort-templates/firebase/src/integrations/openfort/providers.tsx +36 -35
- package/template/openfort-templates/firebase/src/lib/contracts.ts +34 -0
- package/template/openfort-templates/firebase/src/ui/openfort/blockchain/ActionsCard.tsx +25 -13
- package/template/openfort-templates/firebase/src/ui/openfort/wallets/WalletCreation.tsx +108 -63
- package/template/openfort-templates/firebase/src/ui/openfort/wallets/WalletListCard.tsx +211 -41
- package/template/openfort-templates/firebase/src/ui/openfort/wallets/WalletPasswordSheets.tsx +45 -21
- package/template/openfort-templates/headless/AGENTS.md +1 -1
- package/template/openfort-templates/headless/package.json +2 -2
- package/template/openfort-templates/headless/src/components/cards/actions.tsx +30 -21
- package/template/openfort-templates/headless/src/components/cards/profile.tsx +0 -2
- package/template/openfort-templates/headless/src/components/cards/wallets.tsx +230 -67
- package/template/openfort-templates/headless/src/components/createWallet.tsx +115 -73
- package/template/openfort-templates/headless/src/components/passwordRecovery.tsx +48 -23
- package/template/openfort-templates/headless/src/components/providers.tsx +30 -25
- package/template/openfort-templates/headless/src/lib/contracts.ts +43 -0
- package/template/openfort-templates/openfort-ui/AGENTS.md +1 -1
- package/template/openfort-templates/openfort-ui/package.json +3 -3
- package/template/openfort-templates/openfort-ui/src/components/cards/actions.tsx +30 -15
- package/template/openfort-templates/openfort-ui/src/components/cards/auth.tsx +1 -1
- package/template/openfort-templates/openfort-ui/src/components/cards/wallets.tsx +232 -67
- package/template/openfort-templates/openfort-ui/src/components/createWallet.tsx +115 -73
- package/template/openfort-templates/openfort-ui/src/components/passwordRecovery.tsx +48 -23
- package/template/openfort-templates/openfort-ui/src/components/providers.tsx +34 -30
- package/template/openfort-templates/openfort-ui/src/lib/contracts.ts +35 -0
- package/template/openfort-templates/solana-headless/biome.json +70 -0
- package/template/openfort-templates/solana-headless/index.html +16 -0
- package/template/openfort-templates/solana-headless/package.json +34 -0
- package/template/openfort-templates/solana-headless/public/githubLogo.svg +5 -0
- package/template/openfort-templates/solana-headless/public/openfort.svg +13 -0
- package/template/openfort-templates/solana-headless/public/solanaLogo.svg +11 -0
- package/template/openfort-templates/solana-headless/src/App.tsx +7 -0
- package/template/openfort-templates/solana-headless/src/components/cards/auth.tsx +167 -0
- package/template/openfort-templates/solana-headless/src/components/cards/head.tsx +359 -0
- package/template/openfort-templates/solana-headless/src/components/cards/history.tsx +134 -0
- package/template/openfort-templates/solana-headless/src/components/cards/main.tsx +140 -0
- package/template/openfort-templates/solana-headless/src/components/cards/profile.tsx +80 -0
- package/template/openfort-templates/solana-headless/src/components/cards/send.tsx +242 -0
- package/template/openfort-templates/solana-headless/src/components/cards/sign.tsx +48 -0
- package/template/openfort-templates/solana-headless/src/components/cards/wallets.tsx +199 -0
- package/template/openfort-templates/solana-headless/src/components/createWallet.tsx +117 -0
- package/template/openfort-templates/solana-headless/src/components/passwordRecovery.tsx +167 -0
- package/template/openfort-templates/solana-headless/src/components/providers.tsx +23 -0
- package/template/openfort-templates/solana-headless/src/components/ui/Sheet.tsx +47 -0
- package/template/openfort-templates/solana-headless/src/components/ui/Tabs.tsx +111 -0
- package/template/openfort-templates/solana-headless/src/components/ui/TruncateData.tsx +31 -0
- package/template/openfort-templates/solana-headless/src/hooks/useSolanaMessageSigner.ts +37 -0
- package/template/openfort-templates/solana-headless/src/index.css +180 -0
- package/template/openfort-templates/solana-headless/src/lib/errors.ts +4 -0
- package/template/openfort-templates/solana-headless/src/lib/solana/balance.ts +17 -0
- package/template/openfort-templates/solana-headless/src/lib/solana/index.ts +4 -0
- package/template/openfort-templates/solana-headless/src/lib/solana/kora.ts +137 -0
- package/template/openfort-templates/solana-headless/src/lib/solana/transaction.ts +146 -0
- package/template/openfort-templates/solana-headless/src/lib/solana/transactionHistory.ts +39 -0
- package/template/openfort-templates/solana-headless/src/main.tsx +13 -0
- package/template/openfort-templates/solana-headless/src/vite-env.d.ts +1 -0
- package/template/openfort-templates/solana-headless/tsconfig.app.json +24 -0
- package/template/openfort-templates/solana-headless/tsconfig.json +7 -0
- package/template/openfort-templates/solana-headless/tsconfig.node.json +22 -0
- package/template/openfort-templates/solana-headless/vite.config.ts +8 -0
|
@@ -1,25 +1,44 @@
|
|
|
1
1
|
import { CheckCircleIcon } from '@heroicons/react/24/outline'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
AccountTypeEnum,
|
|
4
|
+
RecoveryMethod,
|
|
5
|
+
type ConnectedEmbeddedEthereumWallet,
|
|
6
|
+
} from '@openfort/react'
|
|
7
|
+
import type { EmbeddedAccount } from '@openfort/react'
|
|
8
|
+
import { useEthereumEmbeddedWallet } from '@openfort/react/ethereum'
|
|
9
|
+
import { useState } from 'react'
|
|
3
10
|
import { Sheet } from './ui/Sheet'
|
|
4
11
|
|
|
5
12
|
type CreateWalletPasswordSheetProps = {
|
|
6
13
|
open: boolean
|
|
7
14
|
onClose: () => void
|
|
8
15
|
onCreateWallet?: () => void
|
|
16
|
+
create: (options: {
|
|
17
|
+
recoveryMethod: RecoveryMethod
|
|
18
|
+
accountType?: AccountTypeEnum
|
|
19
|
+
password?: string
|
|
20
|
+
}) => Promise<EmbeddedAccount>
|
|
21
|
+
status: string
|
|
22
|
+
accountType: AccountTypeEnum
|
|
9
23
|
}
|
|
24
|
+
|
|
10
25
|
export const CreateWalletPasswordSheet = ({
|
|
11
26
|
open,
|
|
12
27
|
onClose,
|
|
13
28
|
onCreateWallet,
|
|
29
|
+
create,
|
|
30
|
+
status,
|
|
31
|
+
accountType,
|
|
14
32
|
}: CreateWalletPasswordSheetProps) => {
|
|
15
|
-
const
|
|
33
|
+
const [error, setError] = useState<string | null>(null)
|
|
34
|
+
const isCreating = status === 'creating'
|
|
16
35
|
|
|
17
36
|
return (
|
|
18
37
|
<Sheet
|
|
19
38
|
open={open}
|
|
20
39
|
onClose={() => {
|
|
21
40
|
onClose()
|
|
22
|
-
|
|
41
|
+
setError(null)
|
|
23
42
|
}}
|
|
24
43
|
title="Enter Password"
|
|
25
44
|
description="Please enter the password of your wallet."
|
|
@@ -31,40 +50,41 @@ export const CreateWalletPasswordSheet = ({
|
|
|
31
50
|
const formData = new FormData(e.target as HTMLFormElement)
|
|
32
51
|
const password = formData.get('password') as string
|
|
33
52
|
|
|
34
|
-
|
|
35
|
-
|
|
53
|
+
try {
|
|
54
|
+
await create({
|
|
36
55
|
recoveryMethod: RecoveryMethod.PASSWORD,
|
|
56
|
+
accountType,
|
|
37
57
|
password,
|
|
38
|
-
}
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
if (!error) {
|
|
58
|
+
})
|
|
42
59
|
onCreateWallet?.()
|
|
43
60
|
onClose()
|
|
61
|
+
} catch (err) {
|
|
62
|
+
setError(err instanceof Error ? err.message : 'Failed to create wallet')
|
|
44
63
|
}
|
|
45
64
|
}}
|
|
46
65
|
>
|
|
47
66
|
<div className="flex flex-col gap-2 mr-4 mb-4">
|
|
48
67
|
<div className="flex items-center gap-2">
|
|
49
68
|
<CheckCircleIcon className="h-5 w-5 text-primary my-4 shrink-0" />
|
|
50
|
-
<span>This password will be used to secure your account
|
|
69
|
+
<span>This password will be used to secure your account.</span>
|
|
51
70
|
</div>
|
|
52
71
|
<div className="flex items-center gap-2">
|
|
53
72
|
<CheckCircleIcon className="h-5 w-5 text-primary my-4 shrink-0" />
|
|
54
73
|
<span>
|
|
55
74
|
If you lose this password, you will not be able to access your
|
|
56
|
-
wallet
|
|
75
|
+
wallet.
|
|
57
76
|
</span>
|
|
58
77
|
</div>
|
|
59
78
|
</div>
|
|
60
79
|
<input
|
|
61
80
|
type="password"
|
|
62
81
|
name="password"
|
|
82
|
+
autoComplete="new-password"
|
|
63
83
|
placeholder="Enter your wallet's password"
|
|
64
84
|
className="w-full mt-2 p-2 border border-gray-300 rounded"
|
|
65
85
|
/>
|
|
66
86
|
{error && (
|
|
67
|
-
<span className="text-red-500 text-sm mt-2">{error
|
|
87
|
+
<span className="text-red-500 text-sm mt-2">{error}</span>
|
|
68
88
|
)}
|
|
69
89
|
<button
|
|
70
90
|
className="mt-4 w-full bg-zinc-700 text-white p-2 rounded cursor-pointer"
|
|
@@ -81,7 +101,7 @@ export const CreateWalletPasswordSheet = ({
|
|
|
81
101
|
type WalletRecoverPasswordProps = {
|
|
82
102
|
open: boolean
|
|
83
103
|
onClose: () => void
|
|
84
|
-
wallet:
|
|
104
|
+
wallet: ConnectedEmbeddedEthereumWallet | null
|
|
85
105
|
}
|
|
86
106
|
|
|
87
107
|
export const WalletRecoverPasswordSheet = ({
|
|
@@ -89,34 +109,38 @@ export const WalletRecoverPasswordSheet = ({
|
|
|
89
109
|
onClose,
|
|
90
110
|
wallet,
|
|
91
111
|
}: WalletRecoverPasswordProps) => {
|
|
92
|
-
const {
|
|
112
|
+
const { setActive, status } = useEthereumEmbeddedWallet()
|
|
113
|
+
const [error, setError] = useState<string | null>(null)
|
|
114
|
+
const isConnecting = status === 'connecting'
|
|
93
115
|
|
|
94
116
|
return (
|
|
95
117
|
<Sheet
|
|
96
118
|
open={open}
|
|
97
119
|
onClose={() => {
|
|
98
120
|
onClose()
|
|
99
|
-
|
|
121
|
+
setError(null)
|
|
100
122
|
}}
|
|
101
123
|
title="Enter Password"
|
|
102
124
|
description="Please enter the password of your wallet."
|
|
103
125
|
>
|
|
104
126
|
<form
|
|
105
127
|
className="w-full flex-1 flex flex-col justify-center"
|
|
106
|
-
onSubmit={(e) => {
|
|
128
|
+
onSubmit={async (e) => {
|
|
107
129
|
e.preventDefault()
|
|
108
130
|
const formData = new FormData(e.target as HTMLFormElement)
|
|
109
131
|
const password = formData.get('password') as string
|
|
110
132
|
if (!wallet) throw new Error('No wallet to recover')
|
|
111
133
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
134
|
+
try {
|
|
135
|
+
await setActive({
|
|
136
|
+
address: wallet.address,
|
|
115
137
|
recoveryMethod: RecoveryMethod.PASSWORD,
|
|
116
138
|
password,
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
})
|
|
139
|
+
})
|
|
140
|
+
onClose()
|
|
141
|
+
} catch (err) {
|
|
142
|
+
setError(err instanceof Error ? err.message : 'Failed to recover wallet')
|
|
143
|
+
}
|
|
120
144
|
}}
|
|
121
145
|
>
|
|
122
146
|
{wallet && (
|
|
@@ -128,11 +152,12 @@ export const WalletRecoverPasswordSheet = ({
|
|
|
128
152
|
<input
|
|
129
153
|
type="password"
|
|
130
154
|
name="password"
|
|
155
|
+
autoComplete="current-password"
|
|
131
156
|
placeholder="Enter your wallet's password"
|
|
132
157
|
className="w-full mt-2 p-2 border border-gray-300 rounded"
|
|
133
158
|
/>
|
|
134
159
|
{error && (
|
|
135
|
-
<span className="text-red-500 text-sm mt-2">{error
|
|
160
|
+
<span className="text-red-500 text-sm mt-2">{error}</span>
|
|
136
161
|
)}
|
|
137
162
|
<button
|
|
138
163
|
className="mt-4 w-full bg-zinc-700 text-white p-2 rounded cursor-pointer"
|
|
@@ -1,46 +1,50 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OpenfortProvider, type Theme } from '@openfort/react'
|
|
2
|
+
import { getDefaultConfig, OpenfortWagmiBridge } from '@openfort/react/wagmi'
|
|
2
3
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
3
|
-
import { useState } from 'react'
|
|
4
4
|
import { beamTestnet, polygonAmoy } from 'viem/chains'
|
|
5
5
|
import { createConfig, WagmiProvider } from 'wagmi'
|
|
6
6
|
|
|
7
7
|
const config = createConfig(
|
|
8
8
|
getDefaultConfig({
|
|
9
|
-
appName: 'Openfort
|
|
9
|
+
appName: 'Openfort React demo',
|
|
10
10
|
chains: [beamTestnet, polygonAmoy], // The chains you want to support
|
|
11
11
|
walletConnectProjectId: import.meta.env.VITE_WALLET_CONNECT_PROJECT_ID, // The WalletConnect Project ID
|
|
12
12
|
}),
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
const [queryClient] = useState(() => new QueryClient())
|
|
17
|
-
return (
|
|
18
|
-
<WagmiProvider config={config}>
|
|
19
|
-
<QueryClientProvider client={queryClient}>
|
|
20
|
-
<OpenfortProvider
|
|
21
|
-
debugMode
|
|
22
|
-
publishableKey={import.meta.env.VITE_OPENFORT_PUBLISHABLE_KEY!}
|
|
23
|
-
// Set the wallet configuration. In this example, we will be using the embedded signer.
|
|
24
|
-
walletConfig={{
|
|
25
|
-
shieldPublishableKey: import.meta.env.VITE_SHIELD_PUBLISHABLE_KEY!, // The public key for your Openfort Shield account get it from https://dashboard.openfort.io
|
|
15
|
+
const queryClient = new QueryClient()
|
|
26
16
|
|
|
27
|
-
|
|
17
|
+
const walletConfig = {
|
|
18
|
+
shieldPublishableKey: import.meta.env.VITE_SHIELD_PUBLISHABLE_KEY!, // The public key for your Openfort Shield account get it from https://dashboard.openfort.io
|
|
19
|
+
ethereum: {
|
|
20
|
+
ethereumFeeSponsorshipId: import.meta.env.VITE_FEE_SPONSORSHIP_ID,
|
|
21
|
+
},
|
|
22
|
+
// If you want to use AUTOMATIC embedded wallet recovery, an encryption session is required.
|
|
23
|
+
// See: https://www.openfort.io/docs/products/embedded-wallet/react-native/quickstart/automatic
|
|
24
|
+
// For backend setup, check: https://github.com/openfort-xyz/openfort-backend-quickstart
|
|
25
|
+
createEncryptedSessionEndpoint: import.meta.env.VITE_CREATE_ENCRYPTED_SESSION_ENDPOINT,
|
|
26
|
+
connectOnLogin: false, // We will manually call create/setActive wallet after auth
|
|
27
|
+
}
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
createEncryptedSessionEndpoint: import.meta.env
|
|
33
|
-
.VITE_CREATE_ENCRYPTED_SESSION_ENDPOINT,
|
|
29
|
+
const uiConfig = {
|
|
30
|
+
theme: import.meta.env.VITE_OPENFORT_THEME as Theme,
|
|
31
|
+
}
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
34
|
+
return (
|
|
35
|
+
<QueryClientProvider client={queryClient}>
|
|
36
|
+
<WagmiProvider config={config}>
|
|
37
|
+
<OpenfortWagmiBridge>
|
|
38
|
+
<OpenfortProvider
|
|
39
|
+
debugMode
|
|
40
|
+
publishableKey={import.meta.env.VITE_OPENFORT_PUBLISHABLE_KEY!}
|
|
41
|
+
walletConfig={walletConfig}
|
|
42
|
+
uiConfig={uiConfig}
|
|
43
|
+
>
|
|
44
|
+
{children}
|
|
45
|
+
</OpenfortProvider>
|
|
46
|
+
</OpenfortWagmiBridge>
|
|
47
|
+
</WagmiProvider>
|
|
48
|
+
</QueryClientProvider>
|
|
45
49
|
)
|
|
46
50
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Chain IDs for mint contracts (viem/wagmi) */
|
|
2
|
+
const BEAM_CHAIN_ID = 13337
|
|
3
|
+
const POLYGON_CHAIN_ID = 80002
|
|
4
|
+
const BASE_SEPOLIA_CHAIN_ID = 84532
|
|
5
|
+
|
|
6
|
+
/** Fallback addresses when env vars are not set */
|
|
7
|
+
const DEFAULT_POLYGON_MINT = '0xef147ed8bb07a2a0e7df4c1ac09e96dec459ffac'
|
|
8
|
+
const DEFAULT_BEAM_MINT = '0x45238AB60ACA6862a70fe996D1A8baDb71Af5A8f'
|
|
9
|
+
|
|
10
|
+
type MintContractType = 'claim' | 'mint'
|
|
11
|
+
|
|
12
|
+
interface MintContractConfig {
|
|
13
|
+
address: string
|
|
14
|
+
type: MintContractType
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getMintContractAddress(chainId: number | undefined): string | undefined {
|
|
18
|
+
if (chainId == null) return undefined
|
|
19
|
+
if (chainId === BEAM_CHAIN_ID) {
|
|
20
|
+
return import.meta.env.VITE_BEAM_MINT_CONTRACT ?? DEFAULT_BEAM_MINT
|
|
21
|
+
}
|
|
22
|
+
if (chainId === POLYGON_CHAIN_ID || chainId === BASE_SEPOLIA_CHAIN_ID) {
|
|
23
|
+
return import.meta.env.VITE_POLYGON_MINT_CONTRACT ?? DEFAULT_POLYGON_MINT
|
|
24
|
+
}
|
|
25
|
+
return import.meta.env.VITE_POLYGON_MINT_CONTRACT ?? DEFAULT_POLYGON_MINT
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getMintContractConfig(chainId: number | undefined): MintContractConfig | undefined {
|
|
29
|
+
const address = getMintContractAddress(chainId)
|
|
30
|
+
if (!address) return undefined
|
|
31
|
+
return {
|
|
32
|
+
address,
|
|
33
|
+
type: chainId === BEAM_CHAIN_ID ? 'claim' : 'mint',
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.3.8/schema.json",
|
|
3
|
+
"root": true,
|
|
4
|
+
"vcs": {
|
|
5
|
+
"enabled": true,
|
|
6
|
+
"clientKind": "git",
|
|
7
|
+
"useIgnoreFile": true
|
|
8
|
+
},
|
|
9
|
+
"files": {
|
|
10
|
+
"ignoreUnknown": true,
|
|
11
|
+
"includes": ["**/*.tsx", "**/*.ts", "**/*.jsx", "**/*.js"]
|
|
12
|
+
},
|
|
13
|
+
"formatter": {
|
|
14
|
+
"enabled": true,
|
|
15
|
+
"formatWithErrors": true,
|
|
16
|
+
"indentStyle": "space",
|
|
17
|
+
"indentWidth": 2
|
|
18
|
+
},
|
|
19
|
+
"linter": {
|
|
20
|
+
"enabled": true,
|
|
21
|
+
"rules": {
|
|
22
|
+
"recommended": true,
|
|
23
|
+
"a11y": {
|
|
24
|
+
"recommended": true,
|
|
25
|
+
"useButtonType": "off"
|
|
26
|
+
},
|
|
27
|
+
"complexity": {
|
|
28
|
+
"recommended": true
|
|
29
|
+
},
|
|
30
|
+
"correctness": {
|
|
31
|
+
"recommended": true,
|
|
32
|
+
"useUniqueElementIds": "off"
|
|
33
|
+
},
|
|
34
|
+
"suspicious": {
|
|
35
|
+
"recommended": true,
|
|
36
|
+
"noUnknownAtRules": "off"
|
|
37
|
+
},
|
|
38
|
+
"style": {
|
|
39
|
+
"recommended": true,
|
|
40
|
+
"noDescendingSpecificity": "off",
|
|
41
|
+
"noNonNullAssertion": "off"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"javascript": {
|
|
46
|
+
"formatter": {
|
|
47
|
+
"quoteStyle": "single",
|
|
48
|
+
"semicolons": "asNeeded"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"overrides": [
|
|
52
|
+
{
|
|
53
|
+
"linter": {
|
|
54
|
+
"rules": {
|
|
55
|
+
"correctness": {
|
|
56
|
+
"useExhaustiveDependencies": "warn"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"assist": {
|
|
63
|
+
"enabled": true,
|
|
64
|
+
"actions": {
|
|
65
|
+
"source": {
|
|
66
|
+
"organizeImports": "on"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8" />
|
|
6
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
8
|
+
<title>Solana Headless Quickstart</title>
|
|
9
|
+
</head>
|
|
10
|
+
|
|
11
|
+
<body>
|
|
12
|
+
<div id="root"></div>
|
|
13
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
14
|
+
</body>
|
|
15
|
+
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-openfort-template-solana-headless",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"check": "biome check --write --unsafe",
|
|
10
|
+
"preview": "vite preview"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@heroicons/react": "^2.2.0",
|
|
14
|
+
"@openfort/react": "latest",
|
|
15
|
+
"@solana-program/compute-budget": "^0.12.0",
|
|
16
|
+
"@solana-program/system": "^0.10.0",
|
|
17
|
+
"@solana/kit": "^5.4.0",
|
|
18
|
+
"@solana/kora": "^0.1.1",
|
|
19
|
+
"@tanstack/react-query": "^5.64.1",
|
|
20
|
+
"ox": "^0.6.10",
|
|
21
|
+
"react": "^19.0.0",
|
|
22
|
+
"react-dom": "^19.0.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@biomejs/biome": "^2.3.8",
|
|
26
|
+
"@tailwindcss/vite": "^4.1.15",
|
|
27
|
+
"@types/react": "^19.1.10",
|
|
28
|
+
"@types/react-dom": "^19.1.7",
|
|
29
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
30
|
+
"tailwindcss": "^4.1.15",
|
|
31
|
+
"typescript": "~5.8.3",
|
|
32
|
+
"vite": "^6.4.1"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd"
|
|
3
|
+
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
|
|
4
|
+
fill="#fff" />
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg width="18" height="11" viewBox="0 0 18 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g clip-path="url(#clip0_1086_43)">
|
|
3
|
+
<path d="M9.9528 7.63477H8.04474V11H9.9528V7.63477Z" fill="#FC3627" />
|
|
4
|
+
<path d="M16.0795 11H18V0.00195312L0.00466919 0V11H1.91535V1.90479H16.0795V11Z" fill="#FC3627" />
|
|
5
|
+
<path d="M14.1479 11L14.1583 3.81055H3.83386V11H5.74454V5.71338H12.2398V11H14.1479Z"
|
|
6
|
+
fill="#FC3627" />
|
|
7
|
+
</g>
|
|
8
|
+
<defs>
|
|
9
|
+
<clipPath id="clip0_1086_43">
|
|
10
|
+
<rect width="18" height="11" fill="white" />
|
|
11
|
+
</clipPath>
|
|
12
|
+
</defs>
|
|
13
|
+
</svg>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 397.7 311.7">
|
|
2
|
+
<linearGradient id="a" x1="360.879" x2="141.213" y1="351.455" y2="-69.986" gradientUnits="userSpaceOnUse">
|
|
3
|
+
<stop offset="0" stop-color="#9945ff"/>
|
|
4
|
+
<stop offset=".14" stop-color="#8752f3"/>
|
|
5
|
+
<stop offset=".42" stop-color="#5497d5"/>
|
|
6
|
+
<stop offset=".68" stop-color="#43b4ca"/>
|
|
7
|
+
<stop offset=".88" stop-color="#28e0b9"/>
|
|
8
|
+
<stop offset="1" stop-color="#19fb9b"/>
|
|
9
|
+
</linearGradient>
|
|
10
|
+
<path fill="url(#a)" d="M64.6 237.9c2.4-2.4 5.7-3.8 9.2-3.8h317.4c5.8 0 8.7 7 4.6 11.1l-62.7 62.7c-2.4 2.4-5.7 3.8-9.2 3.8H6.5c-5.8 0-8.7-7-4.6-11.1l62.7-62.7zm0-164.2c2.4-2.4 5.7-3.8 9.2-3.8h317.4c5.8 0 8.7 7 4.6 11.1L333.1 143c-2.4 2.4-5.7 3.8-9.2 3.8H6.5c-5.8 0-8.7-7-4.6-11.1l62.7-62.9v-.1zm256.8 82.1c-2.4-2.4-5.7-3.8-9.2-3.8H-5.2c-5.8 0-8.7 7-4.6 11.1l62.7 62.7c2.4 2.4 5.7 3.8 9.2 3.8h317.4c5.8 0 8.7-7 4.6-11.1l-62.7-62.7z" transform="translate(0 .1)"/>
|
|
11
|
+
</svg>
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OAuthProvider,
|
|
3
|
+
useAuthCallback,
|
|
4
|
+
useEmailAuth,
|
|
5
|
+
useGuestAuth,
|
|
6
|
+
useOAuth,
|
|
7
|
+
} from '@openfort/react'
|
|
8
|
+
import type React from 'react'
|
|
9
|
+
import { useState } from 'react'
|
|
10
|
+
|
|
11
|
+
const GoogleSignInButton: React.FC = () => {
|
|
12
|
+
// Sign in with Google
|
|
13
|
+
const { initOAuth, isLoading } = useOAuth()
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<button
|
|
17
|
+
onClick={() => initOAuth({ provider: OAuthProvider.GOOGLE })}
|
|
18
|
+
className="w-full py-2 px-4 border border-zinc-700 text-white rounded cursor-pointer transition-colors hover:bg-zinc-900/60"
|
|
19
|
+
>
|
|
20
|
+
{isLoading ? 'Loading...' : 'Continue with Google'}
|
|
21
|
+
</button>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const GuestSignInButton: React.FC = () => {
|
|
26
|
+
// Sign in with Google
|
|
27
|
+
const { signUpGuest, isLoading } = useGuestAuth()
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<button
|
|
31
|
+
onClick={() => signUpGuest()}
|
|
32
|
+
className="w-full py-2 px-4 border border-zinc-700 text-white rounded cursor-pointer transition-colors hover:bg-zinc-900/60"
|
|
33
|
+
>
|
|
34
|
+
{isLoading ? 'Loading...' : 'Continue as Guest'}
|
|
35
|
+
</button>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const EmailForm = ({ isLogin }: { isLogin: boolean }) => {
|
|
40
|
+
const { signInEmail, signUpEmail, error, isLoading } = useEmailAuth()
|
|
41
|
+
|
|
42
|
+
const handleSubmit = async (event: React.FormEvent) => {
|
|
43
|
+
event.preventDefault()
|
|
44
|
+
const form = event.target as HTMLFormElement
|
|
45
|
+
const email = (form.elements[0] as HTMLInputElement).value
|
|
46
|
+
const password = (form.elements[1] as HTMLInputElement).value
|
|
47
|
+
if (isLogin) {
|
|
48
|
+
const { requiresEmailVerification } = await signInEmail({
|
|
49
|
+
email,
|
|
50
|
+
password,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
if (requiresEmailVerification) {
|
|
54
|
+
alert(
|
|
55
|
+
'User is not verified. Please check your email to verify your account.',
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
const { requiresEmailVerification } = await signUpEmail({
|
|
60
|
+
email,
|
|
61
|
+
password,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
if (requiresEmailVerification) {
|
|
65
|
+
alert(
|
|
66
|
+
'Registration successful! Please check your email to verify your account.',
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<form onSubmit={handleSubmit} className="space-y-4 mb-4">
|
|
74
|
+
<label
|
|
75
|
+
className="block text-left text-sm font-medium mb-1"
|
|
76
|
+
htmlFor="email"
|
|
77
|
+
>
|
|
78
|
+
Email
|
|
79
|
+
</label>
|
|
80
|
+
<input
|
|
81
|
+
id="email"
|
|
82
|
+
type="email"
|
|
83
|
+
placeholder="Enter your email address"
|
|
84
|
+
required
|
|
85
|
+
/>
|
|
86
|
+
<div>
|
|
87
|
+
<label
|
|
88
|
+
className="block text-left text-sm font-medium mb-1"
|
|
89
|
+
htmlFor="password"
|
|
90
|
+
>
|
|
91
|
+
Password
|
|
92
|
+
</label>
|
|
93
|
+
<input
|
|
94
|
+
id="password"
|
|
95
|
+
type="password"
|
|
96
|
+
placeholder="Enter your password"
|
|
97
|
+
required
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
{error && <p className="text-red-500 text-sm">{error.message}</p>}
|
|
101
|
+
|
|
102
|
+
<button type="submit" className="btn mt-2">
|
|
103
|
+
{isLoading ? 'Loading...' : isLogin ? 'Sign In' : 'Sign Up'}
|
|
104
|
+
</button>
|
|
105
|
+
</form>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const AuthForm = () => {
|
|
110
|
+
const [isLogin, setIsLogin] = useState(true)
|
|
111
|
+
const { isLoading } = useAuthCallback({
|
|
112
|
+
onSuccess: () => {
|
|
113
|
+
console.log('Authentication verified!')
|
|
114
|
+
},
|
|
115
|
+
onError: (e) => {
|
|
116
|
+
console.error(`Authentication verification failed!${e.message}`)
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
if (isLoading) {
|
|
121
|
+
return <div>Verifying authentication...</div>
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<div className="space-y-6">
|
|
126
|
+
<div className="relative">
|
|
127
|
+
<h1 className="text-left text-2xl font-semibold tracking-tight">
|
|
128
|
+
{isLogin ? 'Sign in to account' : 'Create an account'}
|
|
129
|
+
</h1>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<EmailForm isLogin={isLogin} />
|
|
133
|
+
|
|
134
|
+
<div className="relative opacity-80 mb-4">
|
|
135
|
+
<div className="absolute inset-0 flex items-center">
|
|
136
|
+
<div className="w-full border-t border-gray-300" />
|
|
137
|
+
</div>
|
|
138
|
+
<div className="relative flex justify-center text-sm">
|
|
139
|
+
<span className="bg-zinc-800 px-2">or</span>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div className="space-y-3">
|
|
144
|
+
<GuestSignInButton />
|
|
145
|
+
<GoogleSignInButton />
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<div className="text-left text-sm">
|
|
149
|
+
{isLogin ? 'Already have an account? ' : "Don't have an account? "}
|
|
150
|
+
<button
|
|
151
|
+
onClick={() => setIsLogin(!isLogin)}
|
|
152
|
+
className="text-primary hover:underline cursor-pointer font-medium"
|
|
153
|
+
>
|
|
154
|
+
{isLogin ? 'Sign Up' : 'Sign In'}
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const Auth = () => {
|
|
162
|
+
return (
|
|
163
|
+
<div className="card relative">
|
|
164
|
+
<AuthForm />
|
|
165
|
+
</div>
|
|
166
|
+
)
|
|
167
|
+
}
|