@pmate/account-sdk 0.6.1 → 0.7.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmate/account-sdk",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -36,6 +36,7 @@
36
36
  }
37
37
  },
38
38
  "dependencies": {
39
+ "@pmate/auth-widgets": "^0.1.0",
39
40
  "@pmate/lang": "^1.0.2",
40
41
  "@pmate/meta": "^1.1.3",
41
42
  "@pmate/service-core": "^1.0.0",
@@ -1,17 +1,17 @@
1
1
  import { lru } from "@pmate/utils"
2
- import type { AppConfig, ProfileStep } from "../types/app"
2
+ import type { AppConfig, AppThemePreset, ProfileStep } from "../types/app"
3
3
 
4
- const APPS_ENDPOINT = "https://apps-api.pmate.chat"
5
- const DEFAULT_BACKGROUND = "linear-gradient(180deg, #9ca3af 0%, #6b7280 100%)"
4
+ const APPS_ENDPOINT =
5
+ process.env.VITE_PUBLIC_APPS_SERVER_ENDPOINT || "https://apps-api.pmate.chat"
6
6
  const DEFAULT_ICON = "https://parrot-static.pmate.chat/parrot-logo.png"
7
+ const DEFAULT_THEME_PRESET: AppThemePreset = "default"
7
8
 
8
9
  type AppRegistryRecord = {
9
10
  id: string
10
11
  name: string
11
12
  icon: string
12
13
  theme?: {
13
- background?: string
14
- themeColor?: string
14
+ preset?: AppThemePreset
15
15
  welcomeText?: string
16
16
  }
17
17
  profileSchema?: ProfileStep[]
@@ -26,12 +26,12 @@ const buildDisplayNameFromAppId = (appId: string): string => {
26
26
 
27
27
  const toAppConfig = (record: AppRegistryRecord): AppConfig => {
28
28
  const fallbackName = buildDisplayNameFromAppId(record.id)
29
+ const themePreset = record.theme?.preset || DEFAULT_THEME_PRESET
29
30
  return {
30
31
  id: record.id,
31
32
  name: record.name || fallbackName,
32
33
  icon: record.icon || DEFAULT_ICON,
33
- background: record.theme?.background || DEFAULT_BACKGROUND,
34
- themeColor: record.theme?.themeColor,
34
+ themePreset,
35
35
  welcomeText: record.theme?.welcomeText || `Welcome to ${fallbackName}`,
36
36
  profiles: record.profileSchema ?? [],
37
37
  }
@@ -1,5 +1,7 @@
1
- import { Button } from "./Button"
2
- import { Drawer } from "./Drawer"
1
+ import {
2
+ AuthLoginPromptSheet,
3
+ AuthSessionErrorCard,
4
+ } from "@pmate/auth-widgets"
3
5
  import { useSetAtom } from "jotai"
4
6
  import {
5
7
  Component,
@@ -8,16 +10,16 @@ import {
8
10
  useEffect,
9
11
  useState,
10
12
  } from "react"
11
- import { useAuthSnapshot } from "../hooks/useAuthSnapshot"
12
13
  import { accountAtom } from "../atoms/accountAtom"
13
- import { AccountLifecycleState } from "../types/account.types"
14
14
  import { userLogoutAtom } from "../atoms/userLogoutAtom"
15
- import { Redirect } from "../utils/Redirect"
15
+ import { useAuthSnapshot } from "../hooks/useAuthSnapshot"
16
+ import { AccountLifecycleState } from "../types/account.types"
16
17
  import { NotAuthenticatedError } from "../utils/errors"
17
18
  import {
18
19
  getWindowPathname,
19
20
  subscribeToLocationChange,
20
21
  } from "../utils/location"
22
+ import { Redirect } from "../utils/Redirect"
21
23
 
22
24
  export type AuthRoute =
23
25
  | string
@@ -69,12 +71,12 @@ const getAuthBehaviors = (
69
71
  }
70
72
  }
71
73
 
72
- type AuthProviderV2Props = PropsWithChildren<{
74
+ type AuthProviderProps = PropsWithChildren<{
73
75
  app: string
74
76
  authRoutes?: AuthRoute[]
75
77
  onLoginSuccess?: () => void | Promise<void>
76
- rtcProvider?: React.ComponentType<{ children: React.ReactNode }>
77
78
  pathname?: string
79
+ rtcProvider?: React.ComponentType<{ children: React.ReactNode }>
78
80
  }>
79
81
 
80
82
  const useWindowPathname = () => {
@@ -87,13 +89,13 @@ const useWindowPathname = () => {
87
89
  return pathname
88
90
  }
89
91
 
90
- export const AuthProviderV2 = ({
92
+ export const AuthProvider = ({
91
93
  app,
92
94
  authRoutes,
93
- rtcProvider: RtcProvider,
94
- pathname: pathnameProp,
95
95
  children,
96
- }: AuthProviderV2Props) => {
96
+ pathname: pathnameProp,
97
+ rtcProvider: RtcProvider,
98
+ }: AuthProviderProps) => {
97
99
  const pathname = pathnameProp ?? useWindowPathname()
98
100
  const [isLoginPromptOpen, setIsLoginPromptOpen] = useState(false)
99
101
  const [isLoginErrorDismissed, setIsLoginErrorDismissed] = useState(false)
@@ -152,6 +154,7 @@ export const AuthProviderV2 = ({
152
154
  requiresAuth && !snapshot.account && authBehavior === "prompt",
153
155
  )
154
156
  }, [authBehavior, loading, requiresAuth, snapshot.account])
157
+
155
158
  useEffect(() => {
156
159
  if (!loginError) {
157
160
  setIsLoginErrorDismissed(false)
@@ -162,9 +165,7 @@ export const AuthProviderV2 = ({
162
165
  setIsLoginPromptOpen(false)
163
166
  if (window.history.length > 1) {
164
167
  window.history.back()
165
- return
166
168
  }
167
- // window.location.href = "/"
168
169
  }
169
170
 
170
171
  if (loading && requiresAuth) {
@@ -172,26 +173,12 @@ export const AuthProviderV2 = ({
172
173
  }
173
174
  if (loginError && requiresAuth && !isLoginErrorDismissed) {
174
175
  return (
175
- <div className="flex min-h-screen items-center justify-center bg-slate-50 p-6">
176
- <div className="w-full max-w-md rounded-xl border border-rose-200 bg-white p-6 shadow-sm">
177
- <div className="text-sm font-semibold text-rose-600">
178
- Login failed
179
- </div>
180
- <p className="mt-2 text-sm text-slate-600">
181
- We could not restore your session. Please try again.
182
- </p>
183
- <div className="mt-3 text-xs text-rose-500">{loginError.message}</div>
184
- <div className="mt-4 flex flex-wrap gap-3">
185
- <button
186
- className="rounded-md bg-rose-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-rose-700"
187
- type="button"
188
- onClick={() => window.location.reload()}
189
- >
190
- Reload
191
- </button>
192
- </div>
193
- </div>
194
- </div>
176
+ <AuthSessionErrorCard
177
+ title="Login failed"
178
+ description="We could not restore your session. Please try again."
179
+ message={loginError.message}
180
+ onAction={() => window.location.reload()}
181
+ />
195
182
  )
196
183
  }
197
184
  if (requiresAuth && hasAccount === null) {
@@ -199,36 +186,12 @@ export const AuthProviderV2 = ({
199
186
  }
200
187
  if (requiresAuth && hasAccount === false && authBehavior === "prompt") {
201
188
  return (
202
- <Drawer
189
+ <AuthLoginPromptSheet
203
190
  open={isLoginPromptOpen}
204
- onClose={handleBack}
205
- anchor="bottom"
206
- overlayClassName="bg-black/40"
207
- >
208
- <div className="rounded-t-2xl px-6 pb-6 pt-4">
209
- <div className="mx-auto mb-3 h-1.5 w-12 rounded-full bg-slate-200" />
210
- <div className="text-lg font-semibold text-slate-900">
211
- You need login to continue ?
212
- </div>
213
- <div className="mt-5 flex items-center justify-end gap-3">
214
- <Button
215
- type="button"
216
- variant="plain"
217
- className="min-w-[96px] justify-center"
218
- onClick={handleBack}
219
- >
220
- Back
221
- </Button>
222
- <Button
223
- type="button"
224
- className="min-w-[96px] justify-center"
225
- onClick={() => Redirect.toLogin(app)}
226
- >
227
- Login
228
- </Button>
229
- </div>
230
- </div>
231
- </Drawer>
191
+ onBack={handleBack}
192
+ onLogin={() => Redirect.toLogin(app)}
193
+ title="You need login to continue ?"
194
+ />
232
195
  )
233
196
  }
234
197
  if (!requiresAuth) {
@@ -242,6 +205,8 @@ export const AuthProviderV2 = ({
242
205
  )
243
206
  }
244
207
 
208
+ export const AuthProviderV2 = AuthProvider
209
+
245
210
  interface AuthErrorBoundaryState {
246
211
  hasError: boolean
247
212
  }
@@ -281,8 +246,8 @@ class AuthErrorBoundaryInner extends Component<
281
246
  }
282
247
 
283
248
  const AuthErrorBoundary = ({
284
- children,
285
249
  app,
250
+ children,
286
251
  }: PropsWithChildren<{
287
252
  app: string
288
253
  }>) => {
@@ -1 +1 @@
1
- export * from "./AuthProviderV2"
1
+ export * from "./AuthProvider"
@@ -1,5 +1,6 @@
1
1
  import { useEffect, useMemo, useState } from "react"
2
2
  import { getWindowSearch, subscribeToLocationChange } from "../utils/location"
3
+ import { getAppThemeBackground } from "../utils/appTheme"
3
4
  import { useAppConfig } from "./useAppConfig"
4
5
 
5
6
  export const useAppBackgroundStyle = () => {
@@ -17,8 +18,7 @@ export const useAppBackgroundStyle = () => {
17
18
 
18
19
  return useMemo(
19
20
  () => ({
20
- background:
21
- appConfig?.background || "linear-gradient(180deg, #9ca3af 0%, #6b7280 100%)",
21
+ background: getAppThemeBackground(appConfig?.themePreset),
22
22
  }),
23
23
  [appConfig]
24
24
  )
package/src/index.ts CHANGED
@@ -2,7 +2,7 @@ export * from "./atoms"
2
2
  export * from "./components"
3
3
  export * from "./i18n"
4
4
  export { DEFAULT_APP_ID } from "./constants"
5
- export type { AppConfig, ProfileStep } from "./types/app"
5
+ export type { AppConfig, AppThemePreset, ProfileStep } from "./types/app"
6
6
  export * from "./hooks"
7
7
  export * from "./utils"
8
8
  export * from "./api"
package/src/types/app.ts CHANGED
@@ -11,12 +11,18 @@ export type ProfileStep = {
11
11
  required: boolean
12
12
  }
13
13
 
14
+ export type AppThemePreset =
15
+ | "default"
16
+ | "chat"
17
+ | "parrotmate"
18
+ | "sunrise"
19
+ | "graphite"
20
+
14
21
  export interface AppConfig {
15
22
  id: string
16
23
  name: string
17
24
  icon: string
18
- background: string
19
- themeColor?: string
25
+ themePreset: AppThemePreset
20
26
  welcomeText: string
21
27
  profiles: ProfileStep[]
22
28
  }
@@ -0,0 +1,16 @@
1
+ import type { AppThemePreset } from "../types/app"
2
+
3
+ const DEFAULT_THEME_PRESET: AppThemePreset = "default"
4
+
5
+ const THEME_BACKGROUNDS: Record<AppThemePreset, string> = {
6
+ default: "linear-gradient(180deg, #9ca3af 0%, #6b7280 100%)",
7
+ chat: "linear-gradient(180deg, #0f766e 0%, #115e59 100%)",
8
+ parrotmate: "linear-gradient(180deg, #5b4cf0 0%, #4537d2 100%)",
9
+ sunrise: "linear-gradient(180deg, #f59e0b 0%, #ea580c 100%)",
10
+ graphite: "linear-gradient(180deg, #111827 0%, #1f2937 100%)",
11
+ }
12
+
13
+ export const getAppThemeBackground = (themePreset?: AppThemePreset) => {
14
+ const preset = themePreset ?? DEFAULT_THEME_PRESET
15
+ return THEME_BACKGROUNDS[preset] ?? THEME_BACKGROUNDS[DEFAULT_THEME_PRESET]
16
+ }
@@ -9,3 +9,4 @@ export * from "./resolveAppId"
9
9
  export * from "./errors"
10
10
  export * from "./profileStep"
11
11
  export * from "./Redirect"
12
+ export * from "./appTheme"
@@ -1,39 +0,0 @@
1
- import React from "react"
2
-
3
- type ButtonVariant = "primary" | "plain"
4
-
5
- export interface ButtonProps
6
- extends React.ButtonHTMLAttributes<HTMLButtonElement> {
7
- variant?: ButtonVariant
8
- }
9
-
10
- const joinClassNames = (...parts: Array<string | false | null | undefined>) =>
11
- parts.filter(Boolean).join(" ")
12
-
13
- const baseClassName =
14
- "inline-flex items-center rounded-md px-3 py-1.5 text-sm font-medium transition-colors"
15
-
16
- const variantClassName: Record<ButtonVariant, string> = {
17
- primary: "bg-slate-900 text-white hover:bg-slate-800",
18
- plain: "border border-slate-200 bg-white text-slate-700 hover:bg-slate-50",
19
- }
20
-
21
- export const Button = ({
22
- variant = "primary",
23
- className,
24
- disabled,
25
- ...props
26
- }: ButtonProps) => {
27
- return (
28
- <button
29
- {...props}
30
- disabled={disabled}
31
- className={joinClassNames(
32
- baseClassName,
33
- variantClassName[variant],
34
- disabled && "cursor-not-allowed opacity-60",
35
- className,
36
- )}
37
- />
38
- )
39
- }
@@ -1,80 +0,0 @@
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
- }