@pmate/account-sdk 0.5.5 → 0.6.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/package.json +14 -21
- package/src/api/AccountService.ts +97 -0
- package/src/api/Api.ts +99 -0
- package/src/api/AppService.ts +71 -0
- package/src/api/EntityService.ts +41 -0
- package/src/api/ProfileService.ts +133 -0
- package/src/api/cacheInMem.ts +73 -0
- package/src/api/index.ts +5 -0
- package/src/atoms/accountAtom.ts +18 -0
- package/src/atoms/accountProfileAtom.ts +13 -0
- package/src/atoms/atomWithLoadable.ts +48 -0
- package/src/atoms/createProfileAtom.ts +27 -0
- package/src/atoms/index.ts +16 -0
- package/src/atoms/learningLangAtom.ts +8 -0
- package/src/atoms/localStorageAtom.ts +39 -0
- package/src/atoms/loginAtom.ts +8 -0
- package/src/atoms/motherTongueAtom.ts +8 -0
- package/src/atoms/profileAtom.ts +24 -0
- package/src/atoms/profileDraftAtom.ts +7 -0
- package/src/atoms/profilesAtom.ts +20 -0
- package/src/atoms/sessionCheckAtom.ts +10 -0
- package/src/atoms/switchProfileAtom.ts +9 -0
- package/src/atoms/updateProfileAtom.ts +35 -0
- package/src/atoms/uploadAvatarAtom.ts +49 -0
- package/src/atoms/userLogoutAtom.ts +8 -0
- package/src/atoms/userSettingsAtom.ts +58 -0
- package/src/components/AuthProviderV2.tsx +315 -0
- package/src/components/Button.tsx +39 -0
- package/src/components/Drawer.tsx +80 -0
- package/src/components/index.ts +1 -0
- package/src/constants.ts +1 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useAppBackgroundStyle.ts +25 -0
- package/src/hooks/useAppConfig.ts +44 -0
- package/src/hooks/useAuthApp.ts +165 -0
- package/src/hooks/useAuthSnapshot.ts +84 -0
- package/src/hooks/useIsAuthenticated.ts +19 -0
- package/src/hooks/useProfileStepFlow.ts +59 -0
- package/src/i18n/index.ts +59 -0
- package/src/index.ts +9 -0
- package/src/locales/ar-SA.json +183 -0
- package/src/locales/de-DE.json +183 -0
- package/src/locales/el-GR.json +183 -0
- package/src/locales/en.json +183 -0
- package/src/locales/es-ES.json +183 -0
- package/src/locales/fi-FI.json +183 -0
- package/src/locales/fil-PH.json +183 -0
- package/src/locales/fr-FR.json +183 -0
- package/src/locales/hi-IN.json +183 -0
- package/src/locales/ja-JP.json +183 -0
- package/src/locales/ko-KR.json +183 -0
- package/src/locales/pt-BR.json +183 -0
- package/src/locales/pt-PT.json +183 -0
- package/src/locales/ru-RU.json +183 -0
- package/src/locales/ta-IN.json +183 -0
- package/src/locales/uk-UA.json +183 -0
- package/src/locales/zh-CN.json +183 -0
- package/src/locales/zh-TW.json +183 -0
- package/src/types/account.types.ts +28 -0
- package/src/types/app.ts +22 -0
- package/src/types/profile.ts +6 -0
- package/src/utils/AccountManagerV2.ts +349 -0
- package/src/utils/Redirect.ts +17 -0
- package/src/utils/accountStorage.ts +46 -0
- package/src/utils/errors.ts +1 -0
- package/src/utils/index.ts +11 -0
- package/src/utils/location.ts +34 -0
- package/src/utils/profileStep.ts +26 -0
- package/src/utils/resolveAppId.ts +13 -0
- package/src/utils/selectedProfileStorage.ts +46 -0
- package/src/utils/tokenStorage.ts +47 -0
- package/dist/index.cjs.js +0 -22
- package/dist/index.cjs.js.map +0 -1
- package/dist/index.d.ts +0 -305
- package/dist/index.es.js +0 -8897
- package/dist/index.es.js.map +0 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react"
|
|
2
|
+
|
|
3
|
+
type DrawerAnchor = "left" | "right" | "top" | "bottom"
|
|
4
|
+
|
|
5
|
+
export interface DrawerProps {
|
|
6
|
+
open: boolean
|
|
7
|
+
onClose: () => void
|
|
8
|
+
children: React.ReactNode
|
|
9
|
+
anchor?: DrawerAnchor
|
|
10
|
+
className?: string
|
|
11
|
+
overlayClassName?: string
|
|
12
|
+
id?: string
|
|
13
|
+
style?: React.CSSProperties
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const joinClassNames = (...parts: Array<string | false | null | undefined>) =>
|
|
17
|
+
parts.filter(Boolean).join(" ")
|
|
18
|
+
|
|
19
|
+
const positionClassMap: Record<DrawerAnchor, string> = {
|
|
20
|
+
left: "left-0 top-0 h-full",
|
|
21
|
+
right: "right-0 top-0 h-full",
|
|
22
|
+
top: "left-0 top-0 w-full",
|
|
23
|
+
bottom: "bottom-0 left-0 w-full",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const hiddenTransformMap: Record<DrawerAnchor, string> = {
|
|
27
|
+
left: "-translate-x-full",
|
|
28
|
+
right: "translate-x-full",
|
|
29
|
+
top: "-translate-y-full",
|
|
30
|
+
bottom: "translate-y-full",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const Drawer = ({
|
|
34
|
+
open,
|
|
35
|
+
onClose,
|
|
36
|
+
children,
|
|
37
|
+
anchor = "right",
|
|
38
|
+
className,
|
|
39
|
+
overlayClassName,
|
|
40
|
+
id,
|
|
41
|
+
style,
|
|
42
|
+
}: DrawerProps) => {
|
|
43
|
+
const [mounted, setMounted] = useState(open)
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (open) {
|
|
47
|
+
setMounted(true)
|
|
48
|
+
}
|
|
49
|
+
}, [open])
|
|
50
|
+
|
|
51
|
+
const handleTransitionEnd = () => {
|
|
52
|
+
if (!open) {
|
|
53
|
+
setMounted(false)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!mounted) return null
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
id={id}
|
|
62
|
+
className={joinClassNames("fixed inset-0 z-[1002]", overlayClassName)}
|
|
63
|
+
onClick={onClose}
|
|
64
|
+
>
|
|
65
|
+
<div
|
|
66
|
+
className={joinClassNames(
|
|
67
|
+
"absolute bg-white transition-transform duration-300",
|
|
68
|
+
positionClassMap[anchor],
|
|
69
|
+
open ? "translate-x-0 translate-y-0" : hiddenTransformMap[anchor],
|
|
70
|
+
className,
|
|
71
|
+
)}
|
|
72
|
+
style={style}
|
|
73
|
+
onClick={(event) => event.stopPropagation()}
|
|
74
|
+
onTransitionEnd={handleTransitionEnd}
|
|
75
|
+
>
|
|
76
|
+
{children}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./AuthProviderV2"
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEFAULT_APP_ID = "@pmate/chat"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from "react"
|
|
2
|
+
import { getWindowSearch, subscribeToLocationChange } from "../utils/location"
|
|
3
|
+
import { useAppConfig } from "./useAppConfig"
|
|
4
|
+
|
|
5
|
+
export const useAppBackgroundStyle = () => {
|
|
6
|
+
const [search, setSearch] = useState(getWindowSearch())
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const update = () => setSearch(getWindowSearch())
|
|
9
|
+
const unsubscribe = subscribeToLocationChange(update)
|
|
10
|
+
return () => unsubscribe()
|
|
11
|
+
}, [])
|
|
12
|
+
const appParam = useMemo(
|
|
13
|
+
() => new URLSearchParams(search).get("app"),
|
|
14
|
+
[search]
|
|
15
|
+
)
|
|
16
|
+
const { appConfig } = useAppConfig(appParam)
|
|
17
|
+
|
|
18
|
+
return useMemo(
|
|
19
|
+
() => ({
|
|
20
|
+
background:
|
|
21
|
+
appConfig?.background || "linear-gradient(180deg, #9ca3af 0%, #6b7280 100%)",
|
|
22
|
+
}),
|
|
23
|
+
[appConfig]
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useEffect, useState } from "react"
|
|
2
|
+
import { AppService } from "../api/AppService"
|
|
3
|
+
import { DEFAULT_APP_ID } from "../constants"
|
|
4
|
+
import type { AppConfig } from "../types/app"
|
|
5
|
+
import { resolveAppId } from "../utils/resolveAppId"
|
|
6
|
+
|
|
7
|
+
type AppConfigState = {
|
|
8
|
+
appConfig: AppConfig | null
|
|
9
|
+
isLoading: boolean
|
|
10
|
+
error: Error | null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const useAppConfig = (app?: string | null): AppConfigState => {
|
|
14
|
+
const resolvedApp = resolveAppId(app ?? DEFAULT_APP_ID)
|
|
15
|
+
const [state, setState] = useState<AppConfigState>({
|
|
16
|
+
appConfig: null,
|
|
17
|
+
isLoading: true,
|
|
18
|
+
error: null,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
let active = true
|
|
23
|
+
setState({ appConfig: null, isLoading: true, error: null })
|
|
24
|
+
AppService.getAppConfig(resolvedApp)
|
|
25
|
+
.then((appConfig) => {
|
|
26
|
+
if (!active) return
|
|
27
|
+
setState({ appConfig, isLoading: false, error: null })
|
|
28
|
+
})
|
|
29
|
+
.catch((error: unknown) => {
|
|
30
|
+
if (!active) return
|
|
31
|
+
setState({
|
|
32
|
+
appConfig: null,
|
|
33
|
+
isLoading: false,
|
|
34
|
+
error: error instanceof Error ? error : new Error("Failed to load app config"),
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
return () => {
|
|
39
|
+
active = false
|
|
40
|
+
}
|
|
41
|
+
}, [resolvedApp])
|
|
42
|
+
|
|
43
|
+
return state
|
|
44
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { useCallback, useMemo } from "react"
|
|
2
|
+
import { DEFAULT_APP_ID } from "../constants"
|
|
3
|
+
import { resolveAppId } from "../utils/resolveAppId"
|
|
4
|
+
import type { ProfileStepType } from "../utils/profileStep"
|
|
5
|
+
|
|
6
|
+
type UseAuthAppOptions = {
|
|
7
|
+
app?: string
|
|
8
|
+
redirect?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type AuthAppRedirectOptions = {
|
|
12
|
+
app?: string
|
|
13
|
+
redirect?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type LogoutScope = "app" | "all"
|
|
17
|
+
|
|
18
|
+
type AuthLogoutRedirectOptions = AuthAppRedirectOptions & {
|
|
19
|
+
scope?: LogoutScope
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type AuthProfileRedirectOptions = AuthAppRedirectOptions & {
|
|
23
|
+
step?: ProfileStepType
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const AUTH_APP_BASE = "https://auth.pmate.chat"
|
|
27
|
+
|
|
28
|
+
const getDefaultRedirect = () => {
|
|
29
|
+
if (typeof window === "undefined") {
|
|
30
|
+
return ""
|
|
31
|
+
}
|
|
32
|
+
return window.location.href
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const useAuthApp = (options: UseAuthAppOptions = {}) => {
|
|
36
|
+
const app = resolveAppId(options.app ?? DEFAULT_APP_ID)
|
|
37
|
+
const redirect = useMemo(
|
|
38
|
+
() => options.redirect ?? getDefaultRedirect(),
|
|
39
|
+
[options.redirect]
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
const buildUrl = useCallback(
|
|
43
|
+
(path = "/", overrides: AuthAppRedirectOptions = {}) => {
|
|
44
|
+
const targetRedirect = overrides.redirect ?? redirect
|
|
45
|
+
const targetApp = overrides.app ?? app
|
|
46
|
+
const url = new URL(path, AUTH_APP_BASE)
|
|
47
|
+
if (targetRedirect) {
|
|
48
|
+
url.searchParams.set("redirect", targetRedirect)
|
|
49
|
+
}
|
|
50
|
+
if (targetApp) {
|
|
51
|
+
url.searchParams.set("app", targetApp)
|
|
52
|
+
}
|
|
53
|
+
return url.toString()
|
|
54
|
+
},
|
|
55
|
+
[app, redirect]
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const buildProfileUrl = useCallback(
|
|
59
|
+
(path: string, overrides: AuthProfileRedirectOptions = {}) => {
|
|
60
|
+
const url = new URL(buildUrl(path, overrides))
|
|
61
|
+
if (overrides.step) {
|
|
62
|
+
url.searchParams.set("step", overrides.step)
|
|
63
|
+
}
|
|
64
|
+
return url.toString()
|
|
65
|
+
},
|
|
66
|
+
[buildUrl]
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
const buildLogoutUrl = useCallback(
|
|
70
|
+
(overrides?: AuthLogoutRedirectOptions) => {
|
|
71
|
+
const url = new URL(buildUrl("/logout", overrides))
|
|
72
|
+
if (overrides?.scope === "all") {
|
|
73
|
+
url.searchParams.set("scope", "all")
|
|
74
|
+
}
|
|
75
|
+
return url.toString()
|
|
76
|
+
},
|
|
77
|
+
[buildUrl]
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
const buildLoginUrl = useCallback(
|
|
81
|
+
(overrides?: AuthAppRedirectOptions) => buildUrl("/", overrides),
|
|
82
|
+
[buildUrl]
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
const buildCreateProfileUrl = useCallback(
|
|
86
|
+
(overrides?: AuthProfileRedirectOptions) =>
|
|
87
|
+
buildProfileUrl("/create-profile", overrides),
|
|
88
|
+
[buildProfileUrl]
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
const buildSelectProfileUrl = useCallback(
|
|
92
|
+
(overrides?: AuthAppRedirectOptions) =>
|
|
93
|
+
buildUrl("/select-profile", overrides),
|
|
94
|
+
[buildUrl]
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
const buildEditProfileUrl = useCallback(
|
|
98
|
+
(overrides?: AuthProfileRedirectOptions) =>
|
|
99
|
+
buildProfileUrl("/edit-profile", overrides),
|
|
100
|
+
[buildProfileUrl]
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
const login = useCallback(
|
|
104
|
+
(overrides?: AuthAppRedirectOptions) => {
|
|
105
|
+
if (typeof window === "undefined") {
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
window.location.assign(buildLoginUrl(overrides))
|
|
109
|
+
},
|
|
110
|
+
[buildLoginUrl]
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
const logout = useCallback(
|
|
114
|
+
(scopeOrOverrides?: LogoutScope | AuthLogoutRedirectOptions) => {
|
|
115
|
+
if (typeof window === "undefined") {
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
const overrides =
|
|
119
|
+
typeof scopeOrOverrides === "string"
|
|
120
|
+
? { scope: scopeOrOverrides }
|
|
121
|
+
: (scopeOrOverrides ?? {})
|
|
122
|
+
window.location.assign(buildLogoutUrl(overrides))
|
|
123
|
+
},
|
|
124
|
+
[buildLogoutUrl]
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
const redirectToCreateProfile = useCallback(
|
|
128
|
+
(overrides?: AuthProfileRedirectOptions) => {
|
|
129
|
+
if (typeof window === "undefined") {
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
window.location.assign(buildCreateProfileUrl(overrides))
|
|
133
|
+
},
|
|
134
|
+
[buildCreateProfileUrl]
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
const redirectToSelectProfile = useCallback(
|
|
138
|
+
(overrides?: AuthAppRedirectOptions) => {
|
|
139
|
+
if (typeof window === "undefined") {
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
window.location.assign(buildSelectProfileUrl(overrides))
|
|
143
|
+
},
|
|
144
|
+
[buildSelectProfileUrl]
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
const redirectToEditProfile = useCallback(
|
|
148
|
+
(overrides?: AuthProfileRedirectOptions) => {
|
|
149
|
+
if (typeof window === "undefined") {
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
window.location.assign(buildEditProfileUrl(overrides))
|
|
153
|
+
},
|
|
154
|
+
[buildEditProfileUrl]
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
app,
|
|
159
|
+
login,
|
|
160
|
+
logout,
|
|
161
|
+
selectProfile: redirectToSelectProfile,
|
|
162
|
+
createProfile: redirectToCreateProfile,
|
|
163
|
+
updateProfile: redirectToEditProfile,
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { useEffect, useState } from "react"
|
|
2
|
+
import type { AccountSnapshot, AuthBehaviors } from "../types/account.types"
|
|
3
|
+
import { AccountLifecycleState } from "../types/account.types"
|
|
4
|
+
import { AccountManagerEvent, AccountManagerV2 } from "../utils/AccountManagerV2"
|
|
5
|
+
|
|
6
|
+
const checkAuth = async ({
|
|
7
|
+
app,
|
|
8
|
+
behaviors,
|
|
9
|
+
}: {
|
|
10
|
+
app: string
|
|
11
|
+
behaviors: AuthBehaviors
|
|
12
|
+
}): Promise<AccountSnapshot> => {
|
|
13
|
+
const manager = AccountManagerV2.get(app)
|
|
14
|
+
if (behaviors.requiresAuth === false) {
|
|
15
|
+
return manager.getSnapshot()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const account = await manager.loginUrlSessionOverride()
|
|
20
|
+
if (account) {
|
|
21
|
+
const profiles = await manager.getProfiles()
|
|
22
|
+
if (profiles.length > 0) {
|
|
23
|
+
manager.setSelectedProfile(profiles[0].id)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return manager.getSnapshot()
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error(error)
|
|
29
|
+
return manager.getSnapshot()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const useAuthSnapshot = ({
|
|
34
|
+
app,
|
|
35
|
+
behaviors,
|
|
36
|
+
}: {
|
|
37
|
+
app: string
|
|
38
|
+
behaviors: AuthBehaviors
|
|
39
|
+
}) => {
|
|
40
|
+
const [loading, setLoading] = useState(true)
|
|
41
|
+
const [snapshot, setSnapshot] = useState<AccountSnapshot>({
|
|
42
|
+
state: AccountLifecycleState.Idle,
|
|
43
|
+
profiles: [],
|
|
44
|
+
profile: null,
|
|
45
|
+
accountId: null,
|
|
46
|
+
account: null,
|
|
47
|
+
error: null,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
let isActive = true
|
|
52
|
+
const manager = AccountManagerV2.get(app)
|
|
53
|
+
|
|
54
|
+
const refreshSnapshot = async () => {
|
|
55
|
+
const next = await manager.getSnapshot()
|
|
56
|
+
if (!isActive) {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
setSnapshot(next)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const loadSnapshot = async () => {
|
|
63
|
+
setLoading(true)
|
|
64
|
+
const snap = await checkAuth({ app, behaviors })
|
|
65
|
+
if (!isActive) {
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
setSnapshot(snap)
|
|
69
|
+
setLoading(false)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
void loadSnapshot()
|
|
73
|
+
const unsubscribe = manager.on(AccountManagerEvent.StateChange, () => {
|
|
74
|
+
void refreshSnapshot()
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
return () => {
|
|
78
|
+
isActive = false
|
|
79
|
+
unsubscribe()
|
|
80
|
+
}
|
|
81
|
+
}, [app, behaviors.authBehavior, behaviors.requiresAuth])
|
|
82
|
+
|
|
83
|
+
return { loading, snapshot }
|
|
84
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { DEFAULT_APP_ID } from "../constants"
|
|
2
|
+
import { useAuthSnapshot } from "./useAuthSnapshot"
|
|
3
|
+
import { resolveAppId } from "../utils/resolveAppId"
|
|
4
|
+
|
|
5
|
+
export const useIsAuthenticated = (app?: string) => {
|
|
6
|
+
const { loading, snapshot } = useAuthSnapshot({
|
|
7
|
+
app: resolveAppId(app ?? DEFAULT_APP_ID),
|
|
8
|
+
behaviors: {
|
|
9
|
+
authBehavior: "prompt",
|
|
10
|
+
requiresAuth: false,
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
if (loading) {
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return Boolean(snapshot.account)
|
|
19
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useCallback, useMemo } from "react"
|
|
2
|
+
import { isProfileStepType, ProfileStepType } from "../utils/profileStep"
|
|
3
|
+
import { useAppConfig } from "./useAppConfig"
|
|
4
|
+
|
|
5
|
+
const DEFAULT_CREATE_STEP: ProfileStepType = "learning-language"
|
|
6
|
+
|
|
7
|
+
type UseProfileStepFlowParams = {
|
|
8
|
+
params: URLSearchParams
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const useProfileStepFlow = ({
|
|
12
|
+
params,
|
|
13
|
+
}: UseProfileStepFlowParams) => {
|
|
14
|
+
const appParam = params.get("app")
|
|
15
|
+
const redirectParam = params.get("redirect")
|
|
16
|
+
const { appConfig } = useAppConfig(appParam)
|
|
17
|
+
const appProfileSteps = useMemo<ProfileStepType[]>(
|
|
18
|
+
() =>
|
|
19
|
+
(appConfig?.profiles ?? [])
|
|
20
|
+
.map((profile) => profile.type)
|
|
21
|
+
.filter((type): type is ProfileStepType => isProfileStepType(type)),
|
|
22
|
+
[appConfig]
|
|
23
|
+
)
|
|
24
|
+
const stepParam = params.get("step")
|
|
25
|
+
const normalizedStep: ProfileStepType | null =
|
|
26
|
+
appProfileSteps.find((item) => item === stepParam) ?? null
|
|
27
|
+
const defaultStep: ProfileStepType = appProfileSteps[0] ?? DEFAULT_CREATE_STEP
|
|
28
|
+
const activeStep: ProfileStepType = normalizedStep ?? defaultStep
|
|
29
|
+
const createSteps = useMemo<ProfileStepType[]>(() => {
|
|
30
|
+
return appProfileSteps.length > 0 ? appProfileSteps : [DEFAULT_CREATE_STEP]
|
|
31
|
+
}, [appProfileSteps])
|
|
32
|
+
const currentStepIndex = createSteps.indexOf(activeStep)
|
|
33
|
+
const nextStep =
|
|
34
|
+
currentStepIndex >= 0 ? createSteps[currentStepIndex + 1] : undefined
|
|
35
|
+
const isCreateFlowStep = createSteps.includes(activeStep)
|
|
36
|
+
const buildStepUrl = useCallback(
|
|
37
|
+
(next: ProfileStepType) => {
|
|
38
|
+
const search = new URLSearchParams()
|
|
39
|
+
search.set("step", next)
|
|
40
|
+
if (appParam) {
|
|
41
|
+
search.set("app", appParam)
|
|
42
|
+
}
|
|
43
|
+
if (redirectParam) {
|
|
44
|
+
search.set("redirect", redirectParam)
|
|
45
|
+
}
|
|
46
|
+
return `/create-profile?${search.toString()}`
|
|
47
|
+
},
|
|
48
|
+
[appParam, redirectParam]
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
activeStep,
|
|
53
|
+
appProfileSteps,
|
|
54
|
+
buildStepUrl,
|
|
55
|
+
createSteps,
|
|
56
|
+
isCreateFlowStep,
|
|
57
|
+
nextStep,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import i18next from "i18next"
|
|
2
|
+
import {
|
|
3
|
+
I18nextProvider,
|
|
4
|
+
initReactI18next,
|
|
5
|
+
useTranslation as useI18nTranslation,
|
|
6
|
+
} from "react-i18next"
|
|
7
|
+
import arSA from "../locales/ar-SA.json"
|
|
8
|
+
import deDE from "../locales/de-DE.json"
|
|
9
|
+
import elGR from "../locales/el-GR.json"
|
|
10
|
+
import en from "../locales/en.json"
|
|
11
|
+
import esES from "../locales/es-ES.json"
|
|
12
|
+
import fiFI from "../locales/fi-FI.json"
|
|
13
|
+
import filPH from "../locales/fil-PH.json"
|
|
14
|
+
import frFR from "../locales/fr-FR.json"
|
|
15
|
+
import hiIN from "../locales/hi-IN.json"
|
|
16
|
+
import jaJP from "../locales/ja-JP.json"
|
|
17
|
+
import koKR from "../locales/ko-KR.json"
|
|
18
|
+
import ptBR from "../locales/pt-BR.json"
|
|
19
|
+
import ptPT from "../locales/pt-PT.json"
|
|
20
|
+
import ruRU from "../locales/ru-RU.json"
|
|
21
|
+
import taIN from "../locales/ta-IN.json"
|
|
22
|
+
import ukUA from "../locales/uk-UA.json"
|
|
23
|
+
import zhCN from "../locales/zh-CN.json"
|
|
24
|
+
import zhTW from "../locales/zh-TW.json"
|
|
25
|
+
|
|
26
|
+
const resources = {
|
|
27
|
+
en: { translation: en },
|
|
28
|
+
"ar-SA": { translation: arSA },
|
|
29
|
+
"de-DE": { translation: deDE },
|
|
30
|
+
"el-GR": { translation: elGR },
|
|
31
|
+
"es-ES": { translation: esES },
|
|
32
|
+
"fi-FI": { translation: fiFI },
|
|
33
|
+
"fil-PH": { translation: filPH },
|
|
34
|
+
"fr-FR": { translation: frFR },
|
|
35
|
+
"hi-IN": { translation: hiIN },
|
|
36
|
+
"ja-JP": { translation: jaJP },
|
|
37
|
+
"ko-KR": { translation: koKR },
|
|
38
|
+
"pt-BR": { translation: ptBR },
|
|
39
|
+
"pt-PT": { translation: ptPT },
|
|
40
|
+
"ru-RU": { translation: ruRU },
|
|
41
|
+
"ta-IN": { translation: taIN },
|
|
42
|
+
"uk-UA": { translation: ukUA },
|
|
43
|
+
"zh-CN": { translation: zhCN },
|
|
44
|
+
"zh-TW": { translation: zhTW },
|
|
45
|
+
} as const
|
|
46
|
+
|
|
47
|
+
const supportedLngs = Object.keys(resources)
|
|
48
|
+
|
|
49
|
+
const i18n = i18next.createInstance()
|
|
50
|
+
i18n.use(initReactI18next).init({
|
|
51
|
+
resources,
|
|
52
|
+
supportedLngs,
|
|
53
|
+
lng: "zh-CN",
|
|
54
|
+
fallbackLng: "zh-CN",
|
|
55
|
+
interpolation: { escapeValue: false },
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
export { i18n, I18nextProvider }
|
|
59
|
+
export const useTranslation = () => useI18nTranslation().t
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./atoms"
|
|
2
|
+
export * from "./components"
|
|
3
|
+
export * from "./i18n"
|
|
4
|
+
export { DEFAULT_APP_ID } from "./constants"
|
|
5
|
+
export type { AppConfig, ProfileStep } from "./types/app"
|
|
6
|
+
export * from "./hooks"
|
|
7
|
+
export * from "./utils"
|
|
8
|
+
export * from "./api"
|
|
9
|
+
export type { ProfileDraft } from "./types/profile"
|