create-openfort 0.1.8 → 0.1.10
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 +12 -0
- package/dist/index.js +16 -16
- package/package.json +1 -1
- package/template/backend/package.json +7 -1
- package/template/backend/pnpm-lock.yaml +187 -33
- package/template/backend/src/app.ts +1 -1
- package/template/openfort-templates/firebase/biome.json +3 -3
- package/template/openfort-templates/firebase/package.json +1 -1
- package/template/openfort-templates/firebase/src/App.tsx +10 -10
- package/template/openfort-templates/firebase/src/components/cards/head.tsx +157 -145
- package/template/openfort-templates/firebase/src/components/cards/main.tsx +63 -52
- package/template/openfort-templates/firebase/src/components/ui/Sheet.tsx +14 -16
- package/template/openfort-templates/firebase/src/components/ui/Tabs.tsx +46 -36
- package/template/openfort-templates/firebase/src/components/ui/TruncateData.tsx +14 -8
- package/template/openfort-templates/firebase/src/integrations/firebase/client.ts +6 -6
- package/template/openfort-templates/firebase/src/integrations/firebase/components/FirebaseAuthCard.tsx +74 -46
- package/template/openfort-templates/firebase/src/integrations/firebase/errors.ts +57 -37
- package/template/openfort-templates/firebase/src/integrations/firebase/index.ts +3 -3
- package/template/openfort-templates/firebase/src/integrations/openfort/index.ts +1 -1
- package/template/openfort-templates/firebase/src/integrations/openfort/providers.tsx +23 -11
- package/template/openfort-templates/firebase/src/ui/openfort/blockchain/ActionsCard.tsx +47 -35
- package/template/openfort-templates/firebase/src/ui/openfort/blockchain/SignCard.tsx +30 -28
- package/template/openfort-templates/firebase/src/ui/openfort/index.ts +4 -6
- package/template/openfort-templates/firebase/src/ui/openfort/profile/UserProfileCard.tsx +32 -20
- package/template/openfort-templates/firebase/src/ui/openfort/wallets/WalletCreation.tsx +51 -27
- package/template/openfort-templates/firebase/src/ui/openfort/wallets/WalletListCard.tsx +66 -40
- package/template/openfort-templates/firebase/src/ui/openfort/wallets/WalletPasswordSheets.tsx +65 -41
- package/template/openfort-templates/firebase/vite.config.ts +3 -3
- package/template/openfort-templates/headless/biome.json +3 -3
- package/template/openfort-templates/headless/package.json +1 -1
- package/template/openfort-templates/headless/src/components/cards/auth.tsx +2 -2
- package/template/openfort-templates/headless/src/components/cards/profile.tsx +1 -3
- package/template/openfort-templates/headless/src/components/providers.tsx +4 -1
- package/template/openfort-templates/headless/src/index.css +10 -4
- package/template/openfort-templates/openfort-ui/biome.json +3 -3
- package/template/openfort-templates/openfort-ui/package.json +1 -1
- package/template/openfort-templates/openfort-ui/src/App.tsx +1 -3
- package/template/openfort-templates/openfort-ui/src/components/cards/auth.tsx +9 -11
- package/template/openfort-templates/openfort-ui/src/components/cards/head.tsx +157 -145
- package/template/openfort-templates/openfort-ui/src/components/cards/main.tsx +50 -41
- package/template/openfort-templates/openfort-ui/src/components/cards/profile.tsx +29 -35
- package/template/openfort-templates/openfort-ui/src/components/cards/sign.tsx +35 -49
- package/template/openfort-templates/openfort-ui/src/components/cards/wallets.tsx +51 -37
- package/template/openfort-templates/openfort-ui/src/components/createWallet.tsx +63 -30
- package/template/openfort-templates/openfort-ui/src/components/passwordRecovery.tsx +61 -56
- package/template/openfort-templates/openfort-ui/src/components/providers.tsx +14 -15
- package/template/openfort-templates/openfort-ui/src/components/ui/Sheet.tsx +14 -16
- package/template/openfort-templates/openfort-ui/src/components/ui/Tabs.tsx +46 -36
- package/template/openfort-templates/openfort-ui/src/components/ui/TruncateData.tsx +14 -8
- package/template/openfort-templates/openfort-ui/vite.config.ts +2 -2
|
@@ -1,23 +1,39 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { useUser } from
|
|
8
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
HomeIcon,
|
|
3
|
+
PencilIcon,
|
|
4
|
+
PlayIcon,
|
|
5
|
+
WalletIcon,
|
|
6
|
+
} from '@heroicons/react/24/outline'
|
|
7
|
+
import { useUser } from '@openfort/react'
|
|
8
|
+
import { useState } from 'react'
|
|
9
|
+
import { useAccount } from 'wagmi'
|
|
10
|
+
import { FirebaseAuthCard } from '../../integrations/firebase'
|
|
11
|
+
import {
|
|
12
|
+
ActionsCard,
|
|
13
|
+
SignCard,
|
|
14
|
+
UserProfileCard,
|
|
15
|
+
WalletListCard,
|
|
16
|
+
} from '../../ui/openfort'
|
|
17
|
+
import { DesktopTabGroup, MobileTabGroup, type TabType } from '../ui/Tabs'
|
|
18
|
+
import { Head } from './head'
|
|
9
19
|
|
|
10
20
|
interface LayoutProps {
|
|
11
|
-
children: React.ReactNode
|
|
12
|
-
step: number
|
|
13
|
-
tabs?: TabType[]
|
|
14
|
-
currentTab?: TabType
|
|
15
|
-
setCurrentTab?: (tab: TabType) => void
|
|
16
|
-
showTabs?: boolean
|
|
21
|
+
children: React.ReactNode
|
|
22
|
+
step: number
|
|
23
|
+
tabs?: TabType[]
|
|
24
|
+
currentTab?: TabType
|
|
25
|
+
setCurrentTab?: (tab: TabType) => void
|
|
26
|
+
showTabs?: boolean
|
|
17
27
|
}
|
|
18
28
|
|
|
19
|
-
const Layout = ({
|
|
20
|
-
|
|
29
|
+
const Layout = ({
|
|
30
|
+
children,
|
|
31
|
+
step,
|
|
32
|
+
tabs,
|
|
33
|
+
currentTab,
|
|
34
|
+
setCurrentTab,
|
|
35
|
+
showTabs,
|
|
36
|
+
}: LayoutProps) => {
|
|
21
37
|
return (
|
|
22
38
|
<div className="min-h-screen min-w-screen bg-zinc-900 flex flex-col items-center justify-center">
|
|
23
39
|
<div className="relative">
|
|
@@ -27,12 +43,12 @@ const Layout = ({ children, step, tabs, currentTab, setCurrentTab, showTabs }: L
|
|
|
27
43
|
setCurrentTab={setCurrentTab}
|
|
28
44
|
showTabs={showTabs}
|
|
29
45
|
/>
|
|
30
|
-
<div
|
|
31
|
-
className="w-(--card-group-width) layout-card-group"
|
|
32
|
-
>
|
|
46
|
+
<div className="w-(--card-group-width) layout-card-group">
|
|
33
47
|
<div
|
|
34
48
|
className="h-(--card-group-height) grid grid-flow-col auto-cols-max transition-transform duration-500"
|
|
35
|
-
style={{
|
|
49
|
+
style={{
|
|
50
|
+
transform: `translateX(calc(-${step} * var(--card-width)))`,
|
|
51
|
+
}}
|
|
36
52
|
>
|
|
37
53
|
{children}
|
|
38
54
|
</div>
|
|
@@ -42,15 +58,14 @@ const Layout = ({ children, step, tabs, currentTab, setCurrentTab, showTabs }: L
|
|
|
42
58
|
)
|
|
43
59
|
}
|
|
44
60
|
|
|
45
|
-
|
|
46
61
|
export const Main = () => {
|
|
47
|
-
const {isConnected} = useAccount()
|
|
48
|
-
const { isAuthenticated } = useUser()
|
|
49
|
-
const [step, setStep] = useState(0)
|
|
62
|
+
const { isConnected } = useAccount()
|
|
63
|
+
const { isAuthenticated } = useUser()
|
|
64
|
+
const [step, setStep] = useState(0)
|
|
50
65
|
|
|
51
66
|
const tabs: TabType[] = [
|
|
52
67
|
{
|
|
53
|
-
name:
|
|
68
|
+
name: 'Home',
|
|
54
69
|
component: (
|
|
55
70
|
<UserProfileCard
|
|
56
71
|
sampleGithubUrl="https://github.com/openfort-xyz/quickstarts/tree/main/react/firebase"
|
|
@@ -60,22 +75,22 @@ export const Main = () => {
|
|
|
60
75
|
icon: HomeIcon,
|
|
61
76
|
},
|
|
62
77
|
{
|
|
63
|
-
name:
|
|
78
|
+
name: 'Signatures',
|
|
64
79
|
component: <SignCard />,
|
|
65
|
-
icon: PencilIcon
|
|
80
|
+
icon: PencilIcon,
|
|
66
81
|
},
|
|
67
82
|
{
|
|
68
|
-
name:
|
|
83
|
+
name: 'Actions',
|
|
69
84
|
component: <ActionsCard />,
|
|
70
85
|
icon: PlayIcon,
|
|
71
86
|
},
|
|
72
87
|
{
|
|
73
|
-
name:
|
|
88
|
+
name: 'Wallets',
|
|
74
89
|
component: <WalletListCard />,
|
|
75
90
|
icon: WalletIcon,
|
|
76
91
|
},
|
|
77
|
-
]
|
|
78
|
-
const [currentTab, setCurrentTab] = useState<TabType>(tabs[0])
|
|
92
|
+
]
|
|
93
|
+
const [currentTab, setCurrentTab] = useState<TabType>(tabs[0])
|
|
79
94
|
|
|
80
95
|
return (
|
|
81
96
|
<Layout
|
|
@@ -88,31 +103,27 @@ export const Main = () => {
|
|
|
88
103
|
<Head
|
|
89
104
|
onStart={() => setStep(1)}
|
|
90
105
|
sample="Firebase"
|
|
91
|
-
color=
|
|
92
|
-
backgroundColor=
|
|
106
|
+
color="rgb(255, 50, 0)"
|
|
107
|
+
backgroundColor="rgb(255, 145, 0)"
|
|
93
108
|
logo="/firebase.svg"
|
|
94
109
|
href="https://firebase.google.com/"
|
|
95
110
|
subtitle="Example of integration of Openfort with Firebase Authentication"
|
|
96
111
|
/>
|
|
97
|
-
{
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<div className="
|
|
102
|
-
<div className="
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
currentTab={currentTab}
|
|
109
|
-
setCurrentTab={setCurrentTab}
|
|
110
|
-
/>
|
|
111
|
-
</div>
|
|
112
|
+
{!isAuthenticated ? (
|
|
113
|
+
<FirebaseAuthCard />
|
|
114
|
+
) : (
|
|
115
|
+
<div className="block relative overflow-y-auto overflow-x-hidden">
|
|
116
|
+
<div className="card flex-col min-h-full">
|
|
117
|
+
<div className="w-full flex-1 flex">{currentTab.component}</div>
|
|
118
|
+
<MobileTabGroup
|
|
119
|
+
tabs={tabs}
|
|
120
|
+
currentTab={currentTab}
|
|
121
|
+
setCurrentTab={setCurrentTab}
|
|
122
|
+
/>
|
|
112
123
|
</div>
|
|
113
|
-
|
|
114
|
-
}
|
|
124
|
+
</div>
|
|
125
|
+
)}
|
|
115
126
|
<div className="card relative" />
|
|
116
127
|
</Layout>
|
|
117
|
-
)
|
|
118
|
-
}
|
|
128
|
+
)
|
|
129
|
+
}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { ChevronLeftIcon } from
|
|
2
|
-
import { useEffect, useState } from
|
|
1
|
+
import { ChevronLeftIcon } from '@heroicons/react/24/outline'
|
|
2
|
+
import { useEffect, useState } from 'react'
|
|
3
3
|
|
|
4
4
|
type SheetProps = {
|
|
5
|
-
open: boolean
|
|
6
|
-
onClose: () => void
|
|
7
|
-
title: string
|
|
8
|
-
description: string
|
|
5
|
+
open: boolean
|
|
6
|
+
onClose: () => void
|
|
7
|
+
title: string
|
|
8
|
+
description: string
|
|
9
9
|
children: React.ReactNode
|
|
10
|
-
}
|
|
10
|
+
}
|
|
11
11
|
|
|
12
12
|
const SheetInner = ({ onClose, title, description, children }: SheetProps) => {
|
|
13
|
-
const [isClosing, setIsClosing] = useState(false)
|
|
13
|
+
const [isClosing, setIsClosing] = useState(false)
|
|
14
14
|
useEffect(() => {
|
|
15
15
|
if (isClosing) {
|
|
16
|
-
const timer = setTimeout(onClose, 300)
|
|
17
|
-
return () => clearTimeout(timer)
|
|
16
|
+
const timer = setTimeout(onClose, 300)
|
|
17
|
+
return () => clearTimeout(timer)
|
|
18
18
|
}
|
|
19
|
-
}, [isClosing, onClose])
|
|
19
|
+
}, [isClosing, onClose])
|
|
20
20
|
|
|
21
21
|
return (
|
|
22
22
|
<div
|
|
@@ -41,9 +41,7 @@ const SheetInner = ({ onClose, title, description, children }: SheetProps) => {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export const Sheet = (props: SheetProps) => {
|
|
44
|
-
if (!props.open) return null
|
|
44
|
+
if (!props.open) return null
|
|
45
45
|
|
|
46
|
-
return
|
|
47
|
-
|
|
48
|
-
)
|
|
49
|
-
}
|
|
46
|
+
return <SheetInner {...props} />
|
|
47
|
+
}
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
export type TabType = {
|
|
2
|
-
name: string
|
|
3
|
-
component: React.ReactNode
|
|
4
|
-
icon: React.ComponentType<React.SVGProps<SVGSVGElement
|
|
5
|
-
}
|
|
2
|
+
name: string
|
|
3
|
+
component: React.ReactNode
|
|
4
|
+
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>
|
|
5
|
+
}
|
|
6
6
|
|
|
7
7
|
type TabProps = {
|
|
8
|
-
onClick?: () => void
|
|
9
|
-
isActive?: boolean
|
|
10
|
-
} & TabType
|
|
8
|
+
onClick?: () => void
|
|
9
|
+
isActive?: boolean
|
|
10
|
+
} & TabType
|
|
11
11
|
|
|
12
12
|
const DesktopTab = ({ name, isActive, ...buttonProps }: TabProps) => {
|
|
13
13
|
return (
|
|
14
14
|
<button
|
|
15
15
|
className="relative h-8 mx-2.5 transition-colors cursor-pointer"
|
|
16
|
-
style={
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
style={
|
|
17
|
+
{
|
|
18
|
+
'--tab-bg-color': isActive
|
|
19
|
+
? 'var(--color-zinc-800)'
|
|
20
|
+
: 'var(--color-zinc-700)',
|
|
21
|
+
opacity: isActive ? 1 : 0.6,
|
|
22
|
+
} as React.CSSProperties
|
|
23
|
+
}
|
|
20
24
|
{...buttonProps}
|
|
21
25
|
>
|
|
22
26
|
<div className="absolute w-5 h-8 bg-(--tab-bg-color) rotate-20 transform origin-top-left top-1" />
|
|
@@ -24,7 +28,7 @@ const DesktopTab = ({ name, isActive, ...buttonProps }: TabProps) => {
|
|
|
24
28
|
<div className="absolute inset-0 rounded-md bg-(--tab-bg-color)" />
|
|
25
29
|
|
|
26
30
|
<span
|
|
27
|
-
className={`${isActive ?
|
|
31
|
+
className={`${isActive ? 'text-white' : 'text-zinc-400'} whitespace-nowrap bg-(--tab-bg-color) z-10 relative mx-2 pb-4`}
|
|
28
32
|
>
|
|
29
33
|
{name}
|
|
30
34
|
</span>
|
|
@@ -33,25 +37,30 @@ const DesktopTab = ({ name, isActive, ...buttonProps }: TabProps) => {
|
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
type TabGroupProps = {
|
|
36
|
-
tabs: TabType[]
|
|
37
|
-
currentTab?: TabType
|
|
40
|
+
tabs: TabType[]
|
|
41
|
+
currentTab?: TabType
|
|
38
42
|
setCurrentTab?: (tab: TabType) => void
|
|
39
43
|
showTabs?: boolean
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
export const DesktopTabGroup = ({
|
|
46
|
+
export const DesktopTabGroup = ({
|
|
47
|
+
tabs,
|
|
48
|
+
currentTab,
|
|
49
|
+
setCurrentTab,
|
|
50
|
+
showTabs,
|
|
51
|
+
}: TabGroupProps) => {
|
|
43
52
|
return (
|
|
44
|
-
<div
|
|
45
|
-
className="absolute left-[100%] top-2 rotate-90 transform origin-top-left hidden xs:block"
|
|
46
|
-
>
|
|
53
|
+
<div className="absolute left-[100%] top-2 rotate-90 transform origin-top-left hidden xs:block">
|
|
47
54
|
<div
|
|
48
55
|
className="flex gap-2 transition-transform duration-500"
|
|
49
|
-
style={{
|
|
56
|
+
style={{
|
|
57
|
+
transform: showTabs ? 'translateY(-100%)' : 'translateY(10px)',
|
|
58
|
+
}}
|
|
50
59
|
>
|
|
51
60
|
{tabs?.map((tab) => (
|
|
52
61
|
<DesktopTab
|
|
53
62
|
key={tab.name}
|
|
54
|
-
onClick={() => setCurrentTab
|
|
63
|
+
onClick={() => setCurrentTab?.(tab)}
|
|
55
64
|
isActive={currentTab?.name === tab.name}
|
|
56
65
|
{...tab}
|
|
57
66
|
/>
|
|
@@ -61,9 +70,12 @@ export const DesktopTabGroup = ({ tabs, currentTab, setCurrentTab, showTabs }: T
|
|
|
61
70
|
)
|
|
62
71
|
}
|
|
63
72
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
const MobileTab = ({
|
|
74
|
+
name,
|
|
75
|
+
isActive,
|
|
76
|
+
icon: Icon,
|
|
77
|
+
...buttonProps
|
|
78
|
+
}: TabProps) => {
|
|
67
79
|
return (
|
|
68
80
|
<button
|
|
69
81
|
className="relative h-8 mx-2.5 transition-colors cursor-pointer"
|
|
@@ -71,7 +83,7 @@ const MobileTab = ({ name, isActive, icon: Icon, ...buttonProps }: TabProps) =>
|
|
|
71
83
|
>
|
|
72
84
|
<Icon className="h-5 w-5 mx-auto mb-1" />
|
|
73
85
|
<span
|
|
74
|
-
className={`${isActive ?
|
|
86
|
+
className={`${isActive ? 'text-white' : 'text-zinc-400'} whitespace-nowrap`}
|
|
75
87
|
>
|
|
76
88
|
{name}
|
|
77
89
|
</span>
|
|
@@ -82,20 +94,18 @@ const MobileTab = ({ name, isActive, icon: Icon, ...buttonProps }: TabProps) =>
|
|
|
82
94
|
export const MobileTabGroup = ({
|
|
83
95
|
tabs,
|
|
84
96
|
currentTab,
|
|
85
|
-
setCurrentTab
|
|
97
|
+
setCurrentTab,
|
|
86
98
|
}: TabGroupProps) => {
|
|
87
99
|
return (
|
|
88
100
|
<div className="mt-auto xs:hidden flex pt-6 pb-2 items-end justify-between text-zinc-400 text-sm">
|
|
89
|
-
{
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
))
|
|
98
|
-
}
|
|
101
|
+
{tabs?.map((tab) => (
|
|
102
|
+
<MobileTab
|
|
103
|
+
key={tab.name}
|
|
104
|
+
onClick={() => setCurrentTab?.(tab)}
|
|
105
|
+
isActive={currentTab?.name === tab.name}
|
|
106
|
+
{...tab}
|
|
107
|
+
/>
|
|
108
|
+
))}
|
|
99
109
|
</div>
|
|
100
110
|
)
|
|
101
|
-
}
|
|
111
|
+
}
|
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
import { useState } from
|
|
1
|
+
import { useState } from 'react'
|
|
2
2
|
|
|
3
|
-
export const TruncateData = ({
|
|
3
|
+
export const TruncateData = ({
|
|
4
|
+
className,
|
|
5
|
+
data,
|
|
6
|
+
}: {
|
|
7
|
+
className?: string
|
|
8
|
+
data?: string
|
|
9
|
+
}) => {
|
|
4
10
|
const [viewMore, setViewMore] = useState(false)
|
|
5
11
|
if (!data) return null
|
|
6
12
|
|
|
7
13
|
return (
|
|
8
|
-
<div
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
>
|
|
14
|
+
<div
|
|
15
|
+
className={`mt-4 p-2 border border-zinc-700 rounded bg-zinc-900 ${className}`}
|
|
16
|
+
>
|
|
17
|
+
<pre className="break-words whitespace-normal text-sm">
|
|
12
18
|
{viewMore ? data : data.length > 90 ? `${data.slice(0, 90)}...` : data}
|
|
13
19
|
</pre>
|
|
14
20
|
{data.length > 90 && (
|
|
@@ -17,9 +23,9 @@ export const TruncateData = ({ className, data }: { className?: string; data?: s
|
|
|
17
23
|
onClick={() => setViewMore(!viewMore)}
|
|
18
24
|
type="button"
|
|
19
25
|
>
|
|
20
|
-
{viewMore ?
|
|
26
|
+
{viewMore ? 'View less' : 'View more'}
|
|
21
27
|
</button>
|
|
22
28
|
)}
|
|
23
29
|
</div>
|
|
24
30
|
)
|
|
25
|
-
}
|
|
31
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { initializeApp } from
|
|
2
|
-
import { getAuth } from
|
|
1
|
+
import { initializeApp } from 'firebase/app'
|
|
2
|
+
import { getAuth } from 'firebase/auth'
|
|
3
3
|
|
|
4
4
|
const firebaseConfig = {
|
|
5
5
|
apiKey: import.meta.env.VITE_apiKey,
|
|
@@ -8,13 +8,13 @@ const firebaseConfig = {
|
|
|
8
8
|
storageBucket: import.meta.env.VITE_storageBucket,
|
|
9
9
|
messagingSenderId: import.meta.env.VITE_messagingSenderId,
|
|
10
10
|
appId: import.meta.env.VITE_appId,
|
|
11
|
-
}
|
|
11
|
+
}
|
|
12
12
|
|
|
13
13
|
// Initialize Firebase
|
|
14
|
-
const app = initializeApp(firebaseConfig)
|
|
14
|
+
const app = initializeApp(firebaseConfig)
|
|
15
15
|
|
|
16
16
|
// Initialize Firebase Authentication and get a reference to the service
|
|
17
|
-
const auth = getAuth(app)
|
|
17
|
+
const auth = getAuth(app)
|
|
18
18
|
|
|
19
19
|
// Export the Firebase auth object and any other services you initialize
|
|
20
|
-
export { auth
|
|
20
|
+
export { auth }
|
|
@@ -1,24 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
createUserWithEmailAndPassword,
|
|
3
|
+
GoogleAuthProvider,
|
|
4
|
+
signInWithEmailAndPassword,
|
|
5
|
+
signInWithPopup,
|
|
6
|
+
} from 'firebase/auth'
|
|
7
|
+
import { useState } from 'react'
|
|
3
8
|
|
|
4
|
-
import { auth } from
|
|
5
|
-
import { getFirebaseErrorMessage, logFirebaseError } from
|
|
9
|
+
import { auth } from '../client'
|
|
10
|
+
import { getFirebaseErrorMessage, logFirebaseError } from '../errors'
|
|
6
11
|
|
|
7
12
|
function GoogleSignInButton({ onError }: { onError: (error: string) => void }) {
|
|
8
|
-
const [loading, setLoading] = useState(false)
|
|
13
|
+
const [loading, setLoading] = useState(false)
|
|
9
14
|
|
|
10
15
|
const signInWithGoogle = async () => {
|
|
11
16
|
try {
|
|
12
|
-
setLoading(true)
|
|
13
|
-
const provider = new GoogleAuthProvider()
|
|
14
|
-
await signInWithPopup(auth, provider)
|
|
17
|
+
setLoading(true)
|
|
18
|
+
const provider = new GoogleAuthProvider()
|
|
19
|
+
await signInWithPopup(auth, provider)
|
|
15
20
|
} catch (error) {
|
|
16
|
-
logFirebaseError(error,
|
|
17
|
-
onError(getFirebaseErrorMessage(error))
|
|
21
|
+
logFirebaseError(error, 'Google Sign-In')
|
|
22
|
+
onError(getFirebaseErrorMessage(error))
|
|
18
23
|
} finally {
|
|
19
|
-
setLoading(false)
|
|
24
|
+
setLoading(false)
|
|
20
25
|
}
|
|
21
|
-
}
|
|
26
|
+
}
|
|
22
27
|
|
|
23
28
|
return (
|
|
24
29
|
<button
|
|
@@ -26,77 +31,97 @@ function GoogleSignInButton({ onError }: { onError: (error: string) => void }) {
|
|
|
26
31
|
disabled={loading}
|
|
27
32
|
className="w-full py-2 px-4 border border-zinc-700 text-white rounded cursor-pointer transition-colors hover:bg-zinc-900/60 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
28
33
|
>
|
|
29
|
-
{loading ?
|
|
34
|
+
{loading ? 'Signing in...' : 'Continue with Google'}
|
|
30
35
|
</button>
|
|
31
|
-
)
|
|
36
|
+
)
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
function EmailPasswordForm({
|
|
35
40
|
isLogin,
|
|
36
41
|
onError,
|
|
37
42
|
}: {
|
|
38
|
-
isLogin: boolean
|
|
39
|
-
onError: (error: string) => void
|
|
43
|
+
isLogin: boolean
|
|
44
|
+
onError: (error: string) => void
|
|
40
45
|
}) {
|
|
41
|
-
const [loading, setLoading] = useState(false)
|
|
46
|
+
const [loading, setLoading] = useState(false)
|
|
42
47
|
|
|
43
48
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
44
|
-
event.preventDefault()
|
|
45
|
-
const form = event.target as HTMLFormElement
|
|
46
|
-
const email = (form.elements[0] as HTMLInputElement).value
|
|
47
|
-
const password = (form.elements[1] as HTMLInputElement).value
|
|
49
|
+
event.preventDefault()
|
|
50
|
+
const form = event.target as HTMLFormElement
|
|
51
|
+
const email = (form.elements[0] as HTMLInputElement).value
|
|
52
|
+
const password = (form.elements[1] as HTMLInputElement).value
|
|
48
53
|
|
|
49
54
|
try {
|
|
50
|
-
setLoading(true)
|
|
51
|
-
onError(
|
|
55
|
+
setLoading(true)
|
|
56
|
+
onError('')
|
|
52
57
|
if (isLogin) {
|
|
53
|
-
await signInWithEmailAndPassword(auth, email, password)
|
|
58
|
+
await signInWithEmailAndPassword(auth, email, password)
|
|
54
59
|
} else {
|
|
55
|
-
await createUserWithEmailAndPassword(auth, email, password)
|
|
60
|
+
await createUserWithEmailAndPassword(auth, email, password)
|
|
56
61
|
}
|
|
57
62
|
} catch (error) {
|
|
58
|
-
logFirebaseError(error, isLogin ?
|
|
59
|
-
onError(getFirebaseErrorMessage(error))
|
|
63
|
+
logFirebaseError(error, isLogin ? 'Email Sign-In' : 'Email Sign-Up')
|
|
64
|
+
onError(getFirebaseErrorMessage(error))
|
|
60
65
|
} finally {
|
|
61
|
-
setLoading(false)
|
|
66
|
+
setLoading(false)
|
|
62
67
|
}
|
|
63
|
-
}
|
|
68
|
+
}
|
|
64
69
|
|
|
65
70
|
return (
|
|
66
71
|
<form onSubmit={handleSubmit} className="space-y-4 mb-4">
|
|
67
|
-
<label
|
|
72
|
+
<label
|
|
73
|
+
className="block text-left text-sm font-medium mb-1"
|
|
74
|
+
htmlFor="email"
|
|
75
|
+
>
|
|
68
76
|
Email
|
|
69
77
|
</label>
|
|
70
|
-
<input
|
|
78
|
+
<input
|
|
79
|
+
id="email"
|
|
80
|
+
type="email"
|
|
81
|
+
placeholder="Enter your email address"
|
|
82
|
+
required
|
|
83
|
+
/>
|
|
71
84
|
|
|
72
85
|
<div>
|
|
73
|
-
<label
|
|
86
|
+
<label
|
|
87
|
+
className="block text-left text-sm font-medium mb-1"
|
|
88
|
+
htmlFor="password"
|
|
89
|
+
>
|
|
74
90
|
Password
|
|
75
91
|
</label>
|
|
76
|
-
<input
|
|
92
|
+
<input
|
|
93
|
+
id="password"
|
|
94
|
+
type="password"
|
|
95
|
+
placeholder="Enter your password"
|
|
96
|
+
required
|
|
97
|
+
/>
|
|
77
98
|
</div>
|
|
78
99
|
|
|
79
|
-
<button
|
|
80
|
-
|
|
100
|
+
<button
|
|
101
|
+
type="submit"
|
|
102
|
+
disabled={loading}
|
|
103
|
+
className="btn mt-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
104
|
+
>
|
|
105
|
+
{loading ? 'Loading...' : isLogin ? 'Sign In' : 'Sign Up'}
|
|
81
106
|
</button>
|
|
82
107
|
</form>
|
|
83
|
-
)
|
|
108
|
+
)
|
|
84
109
|
}
|
|
85
110
|
|
|
86
111
|
export function FirebaseAuthCard() {
|
|
87
|
-
const [isLogin, setIsLogin] = useState(true)
|
|
88
|
-
const [error, setError] = useState<string>(
|
|
112
|
+
const [isLogin, setIsLogin] = useState(true)
|
|
113
|
+
const [error, setError] = useState<string>('')
|
|
89
114
|
|
|
90
115
|
const handleToggleMode = () => {
|
|
91
|
-
setIsLogin((previous) => !previous)
|
|
92
|
-
setError(
|
|
93
|
-
}
|
|
116
|
+
setIsLogin((previous) => !previous)
|
|
117
|
+
setError('')
|
|
118
|
+
}
|
|
94
119
|
|
|
95
120
|
return (
|
|
96
121
|
<div className="card relative space-y-6">
|
|
97
122
|
<div className="relative">
|
|
98
123
|
<h1 className="text-left text-2xl font-semibold tracking-tight">
|
|
99
|
-
{isLogin ?
|
|
124
|
+
{isLogin ? 'Sign in to account' : 'Create an account'}
|
|
100
125
|
</h1>
|
|
101
126
|
</div>
|
|
102
127
|
|
|
@@ -120,11 +145,14 @@ export function FirebaseAuthCard() {
|
|
|
120
145
|
<GoogleSignInButton onError={setError} />
|
|
121
146
|
|
|
122
147
|
<div className="text-left text-sm">
|
|
123
|
-
{isLogin ?
|
|
124
|
-
<button
|
|
125
|
-
{
|
|
148
|
+
{isLogin ? 'Already have an account? ' : "Don't have an account? "}
|
|
149
|
+
<button
|
|
150
|
+
onClick={handleToggleMode}
|
|
151
|
+
className="text-primary hover:underline cursor-pointer font-medium"
|
|
152
|
+
>
|
|
153
|
+
{isLogin ? 'Sign Up' : 'Sign In'}
|
|
126
154
|
</button>
|
|
127
155
|
</div>
|
|
128
156
|
</div>
|
|
129
|
-
)
|
|
157
|
+
)
|
|
130
158
|
}
|