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.
Files changed (65) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/index.js +24 -23
  3. package/package.json +1 -1
  4. package/template/openfort-templates/firebase/AGENTS.md +1 -1
  5. package/template/openfort-templates/firebase/package.json +3 -3
  6. package/template/openfort-templates/firebase/src/App.tsx +2 -1
  7. package/template/openfort-templates/firebase/src/integrations/openfort/providers.tsx +36 -35
  8. package/template/openfort-templates/firebase/src/lib/contracts.ts +34 -0
  9. package/template/openfort-templates/firebase/src/ui/openfort/blockchain/ActionsCard.tsx +25 -13
  10. package/template/openfort-templates/firebase/src/ui/openfort/wallets/WalletCreation.tsx +108 -63
  11. package/template/openfort-templates/firebase/src/ui/openfort/wallets/WalletListCard.tsx +211 -41
  12. package/template/openfort-templates/firebase/src/ui/openfort/wallets/WalletPasswordSheets.tsx +45 -21
  13. package/template/openfort-templates/headless/AGENTS.md +1 -1
  14. package/template/openfort-templates/headless/package.json +2 -2
  15. package/template/openfort-templates/headless/src/components/cards/actions.tsx +30 -21
  16. package/template/openfort-templates/headless/src/components/cards/profile.tsx +0 -2
  17. package/template/openfort-templates/headless/src/components/cards/wallets.tsx +230 -67
  18. package/template/openfort-templates/headless/src/components/createWallet.tsx +115 -73
  19. package/template/openfort-templates/headless/src/components/passwordRecovery.tsx +48 -23
  20. package/template/openfort-templates/headless/src/components/providers.tsx +30 -25
  21. package/template/openfort-templates/headless/src/lib/contracts.ts +43 -0
  22. package/template/openfort-templates/openfort-ui/AGENTS.md +1 -1
  23. package/template/openfort-templates/openfort-ui/package.json +3 -3
  24. package/template/openfort-templates/openfort-ui/src/components/cards/actions.tsx +30 -15
  25. package/template/openfort-templates/openfort-ui/src/components/cards/auth.tsx +1 -1
  26. package/template/openfort-templates/openfort-ui/src/components/cards/wallets.tsx +232 -67
  27. package/template/openfort-templates/openfort-ui/src/components/createWallet.tsx +115 -73
  28. package/template/openfort-templates/openfort-ui/src/components/passwordRecovery.tsx +48 -23
  29. package/template/openfort-templates/openfort-ui/src/components/providers.tsx +34 -30
  30. package/template/openfort-templates/openfort-ui/src/lib/contracts.ts +35 -0
  31. package/template/openfort-templates/solana-headless/biome.json +70 -0
  32. package/template/openfort-templates/solana-headless/index.html +16 -0
  33. package/template/openfort-templates/solana-headless/package.json +34 -0
  34. package/template/openfort-templates/solana-headless/public/githubLogo.svg +5 -0
  35. package/template/openfort-templates/solana-headless/public/openfort.svg +13 -0
  36. package/template/openfort-templates/solana-headless/public/solanaLogo.svg +11 -0
  37. package/template/openfort-templates/solana-headless/src/App.tsx +7 -0
  38. package/template/openfort-templates/solana-headless/src/components/cards/auth.tsx +167 -0
  39. package/template/openfort-templates/solana-headless/src/components/cards/head.tsx +359 -0
  40. package/template/openfort-templates/solana-headless/src/components/cards/history.tsx +134 -0
  41. package/template/openfort-templates/solana-headless/src/components/cards/main.tsx +140 -0
  42. package/template/openfort-templates/solana-headless/src/components/cards/profile.tsx +80 -0
  43. package/template/openfort-templates/solana-headless/src/components/cards/send.tsx +242 -0
  44. package/template/openfort-templates/solana-headless/src/components/cards/sign.tsx +48 -0
  45. package/template/openfort-templates/solana-headless/src/components/cards/wallets.tsx +199 -0
  46. package/template/openfort-templates/solana-headless/src/components/createWallet.tsx +117 -0
  47. package/template/openfort-templates/solana-headless/src/components/passwordRecovery.tsx +167 -0
  48. package/template/openfort-templates/solana-headless/src/components/providers.tsx +23 -0
  49. package/template/openfort-templates/solana-headless/src/components/ui/Sheet.tsx +47 -0
  50. package/template/openfort-templates/solana-headless/src/components/ui/Tabs.tsx +111 -0
  51. package/template/openfort-templates/solana-headless/src/components/ui/TruncateData.tsx +31 -0
  52. package/template/openfort-templates/solana-headless/src/hooks/useSolanaMessageSigner.ts +37 -0
  53. package/template/openfort-templates/solana-headless/src/index.css +180 -0
  54. package/template/openfort-templates/solana-headless/src/lib/errors.ts +4 -0
  55. package/template/openfort-templates/solana-headless/src/lib/solana/balance.ts +17 -0
  56. package/template/openfort-templates/solana-headless/src/lib/solana/index.ts +4 -0
  57. package/template/openfort-templates/solana-headless/src/lib/solana/kora.ts +137 -0
  58. package/template/openfort-templates/solana-headless/src/lib/solana/transaction.ts +146 -0
  59. package/template/openfort-templates/solana-headless/src/lib/solana/transactionHistory.ts +39 -0
  60. package/template/openfort-templates/solana-headless/src/main.tsx +13 -0
  61. package/template/openfort-templates/solana-headless/src/vite-env.d.ts +1 -0
  62. package/template/openfort-templates/solana-headless/tsconfig.app.json +24 -0
  63. package/template/openfort-templates/solana-headless/tsconfig.json +7 -0
  64. package/template/openfort-templates/solana-headless/tsconfig.node.json +22 -0
  65. 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 { RecoveryMethod, type UserWallet, useWallets } from '@openfort/react'
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 { createWallet, error, isCreating, reset } = useWallets()
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
- reset()
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
- const { error } = await createWallet({
35
- recovery: {
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</span>
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?.message}</span>
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: UserWallet | null
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 { setActiveWallet, error, isConnecting, reset } = useWallets()
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
- reset()
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
- setActiveWallet({
113
- walletId: 'xyz.openfort',
114
- recovery: {
134
+ try {
135
+ await setActive({
136
+ address: wallet.address,
115
137
  recoveryMethod: RecoveryMethod.PASSWORD,
116
138
  password,
117
- },
118
- address: wallet.address,
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?.message}</span>
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 { getDefaultConfig, OpenfortProvider, type Theme } from '@openfort/react'
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 Next.js demo',
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
- export function Providers({ children }: { children: React.ReactNode }) {
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
- ethereumProviderPolicyId: import.meta.env.VITE_POLICY_ID, // The policy ID for sponsoring transactions
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
- // If you want to use AUTOMATIC embedded wallet recovery, an encryption session is required.
30
- // See: https://www.openfort.io/docs/products/embedded-wallet/react-native/quickstart/automatic
31
- // For backend setup, check: https://github.com/openfort-xyz/openfort-backend-quickstart
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
- recoverWalletAutomaticallyAfterAuth: false, // We will manually call create/setActive wallet after auth
36
- }}
37
- uiConfig={{
38
- theme: import.meta.env.VITE_OPENFORT_THEME as Theme,
39
- }}
40
- >
41
- {children}
42
- </OpenfortProvider>
43
- </QueryClientProvider>
44
- </WagmiProvider>
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,7 @@
1
+ import { Main } from './components/cards/main'
2
+
3
+ function App() {
4
+ return <Main />
5
+ }
6
+
7
+ export default App
@@ -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
+ }