@mezo-org/passport 0.15.1-dev.7 → 0.16.0-dev.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 (170) hide show
  1. package/dist/src/api/auth.d.ts +9 -0
  2. package/dist/src/api/auth.d.ts.map +1 -1
  3. package/dist/src/api/auth.js +7 -0
  4. package/dist/src/api/auth.js.map +1 -1
  5. package/dist/src/assets/DefaultAvatar.d.ts +5 -0
  6. package/dist/src/assets/DefaultAvatar.d.ts.map +1 -0
  7. package/dist/src/assets/DefaultAvatar.js +21 -0
  8. package/dist/src/assets/DefaultAvatar.js.map +1 -0
  9. package/dist/src/assets/EditIcon.d.ts +5 -0
  10. package/dist/src/assets/EditIcon.d.ts.map +1 -0
  11. package/dist/src/assets/EditIcon.js +10 -0
  12. package/dist/src/assets/EditIcon.js.map +1 -0
  13. package/dist/src/components/Dropdown/ConnectedTrigger.d.ts +8 -0
  14. package/dist/src/components/Dropdown/ConnectedTrigger.d.ts.map +1 -0
  15. package/dist/src/components/Dropdown/ConnectedTrigger.js +39 -0
  16. package/dist/src/components/Dropdown/ConnectedTrigger.js.map +1 -0
  17. package/dist/src/components/Dropdown/Content.d.ts +8 -0
  18. package/dist/src/components/Dropdown/Content.d.ts.map +1 -0
  19. package/dist/src/components/Dropdown/Content.js +29 -0
  20. package/dist/src/components/Dropdown/Content.js.map +1 -0
  21. package/dist/src/components/Dropdown/DisconnectedTrigger.d.ts +7 -0
  22. package/dist/src/components/Dropdown/DisconnectedTrigger.d.ts.map +1 -0
  23. package/dist/src/components/Dropdown/DisconnectedTrigger.js +13 -0
  24. package/dist/src/components/Dropdown/DisconnectedTrigger.js.map +1 -0
  25. package/dist/src/components/Dropdown/Dropdown.d.ts +23 -0
  26. package/dist/src/components/Dropdown/Dropdown.d.ts.map +1 -0
  27. package/dist/src/components/Dropdown/Dropdown.js +45 -0
  28. package/dist/src/components/Dropdown/Dropdown.js.map +1 -0
  29. package/dist/src/components/Dropdown/ListingItem.d.ts +14 -0
  30. package/dist/src/components/Dropdown/ListingItem.d.ts.map +1 -0
  31. package/dist/src/components/Dropdown/ListingItem.js +42 -0
  32. package/dist/src/components/Dropdown/ListingItem.js.map +1 -0
  33. package/dist/src/components/Dropdown/NestedViewLayout.d.ts +8 -0
  34. package/dist/src/components/Dropdown/NestedViewLayout.d.ts.map +1 -0
  35. package/dist/src/components/Dropdown/NestedViewLayout.js +33 -0
  36. package/dist/src/components/Dropdown/NestedViewLayout.js.map +1 -0
  37. package/dist/src/components/Dropdown/Receive/Receive.d.ts +4 -0
  38. package/dist/src/components/Dropdown/Receive/Receive.d.ts.map +1 -0
  39. package/dist/src/components/Dropdown/Receive/Receive.js +64 -0
  40. package/dist/src/components/Dropdown/Receive/Receive.js.map +1 -0
  41. package/dist/src/components/Dropdown/Root/AccountAddressActions.d.ts +4 -0
  42. package/dist/src/components/Dropdown/Root/AccountAddressActions.d.ts.map +1 -0
  43. package/dist/src/components/Dropdown/Root/AccountAddressActions.js +49 -0
  44. package/dist/src/components/Dropdown/Root/AccountAddressActions.js.map +1 -0
  45. package/dist/src/components/Dropdown/Root/AccountBalance.d.ts +6 -0
  46. package/dist/src/components/Dropdown/Root/AccountBalance.d.ts.map +1 -0
  47. package/dist/src/components/Dropdown/Root/AccountBalance.js +32 -0
  48. package/dist/src/components/Dropdown/Root/AccountBalance.js.map +1 -0
  49. package/dist/src/components/Dropdown/Root/AccountBtcListing.d.ts +6 -0
  50. package/dist/src/components/Dropdown/Root/AccountBtcListing.d.ts.map +1 -0
  51. package/dist/src/components/Dropdown/Root/AccountBtcListing.js +28 -0
  52. package/dist/src/components/Dropdown/Root/AccountBtcListing.js.map +1 -0
  53. package/dist/src/components/Dropdown/Root/AccountError.d.ts +8 -0
  54. package/dist/src/components/Dropdown/Root/AccountError.d.ts.map +1 -0
  55. package/dist/src/components/Dropdown/Root/AccountError.js +17 -0
  56. package/dist/src/components/Dropdown/Root/AccountError.js.map +1 -0
  57. package/dist/src/components/Dropdown/Root/AccountMusdListing.d.ts +4 -0
  58. package/dist/src/components/Dropdown/Root/AccountMusdListing.d.ts.map +1 -0
  59. package/dist/src/components/Dropdown/Root/AccountMusdListing.js +21 -0
  60. package/dist/src/components/Dropdown/Root/AccountMusdListing.js.map +1 -0
  61. package/dist/src/components/Dropdown/Root/AccountOtherAssets.d.ts +8 -0
  62. package/dist/src/components/Dropdown/Root/AccountOtherAssets.d.ts.map +1 -0
  63. package/dist/src/components/Dropdown/Root/AccountOtherAssets.js +43 -0
  64. package/dist/src/components/Dropdown/Root/AccountOtherAssets.js.map +1 -0
  65. package/dist/src/components/Dropdown/Root/Root.d.ts +8 -0
  66. package/dist/src/components/Dropdown/Root/Root.d.ts.map +1 -0
  67. package/dist/src/components/Dropdown/Root/Root.js +49 -0
  68. package/dist/src/components/Dropdown/Root/Root.js.map +1 -0
  69. package/dist/src/components/Dropdown/Root/WalletAddress.d.ts +4 -0
  70. package/dist/src/components/Dropdown/Root/WalletAddress.d.ts.map +1 -0
  71. package/dist/src/components/Dropdown/Root/WalletAddress.js +66 -0
  72. package/dist/src/components/Dropdown/Root/WalletAddress.js.map +1 -0
  73. package/dist/src/components/Dropdown/Root/WelcomeBlock.d.ts +6 -0
  74. package/dist/src/components/Dropdown/Root/WelcomeBlock.d.ts.map +1 -0
  75. package/dist/src/components/Dropdown/Root/WelcomeBlock.js +88 -0
  76. package/dist/src/components/Dropdown/Root/WelcomeBlock.js.map +1 -0
  77. package/dist/src/components/Dropdown/Settings/InlineEditField.d.ts +12 -0
  78. package/dist/src/components/Dropdown/Settings/InlineEditField.d.ts.map +1 -0
  79. package/dist/src/components/Dropdown/Settings/InlineEditField.js +95 -0
  80. package/dist/src/components/Dropdown/Settings/InlineEditField.js.map +1 -0
  81. package/dist/src/components/Dropdown/Settings/Settings.d.ts +4 -0
  82. package/dist/src/components/Dropdown/Settings/Settings.d.ts.map +1 -0
  83. package/dist/src/components/Dropdown/Settings/Settings.js +36 -0
  84. package/dist/src/components/Dropdown/Settings/Settings.js.map +1 -0
  85. package/dist/src/components/Dropdown/SlotNumber.d.ts +19 -0
  86. package/dist/src/components/Dropdown/SlotNumber.d.ts.map +1 -0
  87. package/dist/src/components/Dropdown/SlotNumber.js +67 -0
  88. package/dist/src/components/Dropdown/SlotNumber.js.map +1 -0
  89. package/dist/src/components/Dropdown/TestnetTopBanner.d.ts +3 -0
  90. package/dist/src/components/Dropdown/TestnetTopBanner.d.ts.map +1 -0
  91. package/dist/src/components/Dropdown/TestnetTopBanner.js +14 -0
  92. package/dist/src/components/Dropdown/TestnetTopBanner.js.map +1 -0
  93. package/dist/src/components/Dropdown/index.d.ts +3 -0
  94. package/dist/src/components/Dropdown/index.d.ts.map +1 -0
  95. package/dist/src/components/Dropdown/index.js +2 -0
  96. package/dist/src/components/Dropdown/index.js.map +1 -0
  97. package/dist/src/components/index.d.ts +2 -0
  98. package/dist/src/components/index.d.ts.map +1 -0
  99. package/dist/src/components/index.js +2 -0
  100. package/dist/src/components/index.js.map +1 -0
  101. package/dist/src/hooks/index.d.ts +1 -0
  102. package/dist/src/hooks/index.d.ts.map +1 -1
  103. package/dist/src/hooks/index.js +1 -0
  104. package/dist/src/hooks/index.js.map +1 -1
  105. package/dist/src/hooks/useAcceptDocuments.d.ts +18 -0
  106. package/dist/src/hooks/useAcceptDocuments.d.ts.map +1 -1
  107. package/dist/src/hooks/useAuthenticateWithWallet.d.ts +11 -11
  108. package/dist/src/hooks/useCreateAccount.d.ts +18 -0
  109. package/dist/src/hooks/useCreateAccount.d.ts.map +1 -1
  110. package/dist/src/hooks/useSignInWithWallet.d.ts +11 -11
  111. package/dist/src/hooks/useSignUpWithWallet.d.ts +11 -11
  112. package/dist/src/hooks/useUpdateMezoId.d.ts +18 -0
  113. package/dist/src/hooks/useUpdateMezoId.d.ts.map +1 -1
  114. package/dist/src/hooks/useUpdateUserProfile.d.ts +171 -0
  115. package/dist/src/hooks/useUpdateUserProfile.d.ts.map +1 -0
  116. package/dist/src/hooks/useUpdateUserProfile.js +19 -0
  117. package/dist/src/hooks/useUpdateUserProfile.js.map +1 -0
  118. package/dist/src/hooks/useWalletAccount.d.ts +2 -5
  119. package/dist/src/hooks/useWalletAccount.d.ts.map +1 -1
  120. package/dist/src/hooks/useWalletAccount.js.map +1 -1
  121. package/dist/src/index.d.ts +1 -0
  122. package/dist/src/index.d.ts.map +1 -1
  123. package/dist/src/index.js +1 -0
  124. package/dist/src/index.js.map +1 -1
  125. package/dist/src/stores/dropdownStore.d.ts +2 -1
  126. package/dist/src/stores/dropdownStore.d.ts.map +1 -1
  127. package/dist/src/stores/dropdownStore.js +1 -0
  128. package/dist/src/stores/dropdownStore.js.map +1 -1
  129. package/dist/src/utils/validation.d.ts +13 -0
  130. package/dist/src/utils/validation.d.ts.map +1 -0
  131. package/dist/src/utils/validation.js +34 -0
  132. package/dist/src/utils/validation.js.map +1 -0
  133. package/dist/src/utils/validation.test.d.ts +2 -0
  134. package/dist/src/utils/validation.test.d.ts.map +1 -0
  135. package/dist/src/utils/validation.test.js +63 -0
  136. package/dist/src/utils/validation.test.js.map +1 -0
  137. package/package.json +10 -7
  138. package/src/api/auth.ts +22 -0
  139. package/src/assets/DefaultAvatar.tsx +74 -0
  140. package/src/assets/EditIcon.tsx +33 -0
  141. package/src/components/Dropdown/ConnectedTrigger.tsx +76 -0
  142. package/src/components/Dropdown/Content.tsx +56 -0
  143. package/src/components/Dropdown/DisconnectedTrigger.tsx +36 -0
  144. package/src/components/Dropdown/Dropdown.tsx +100 -0
  145. package/src/components/Dropdown/ListingItem.tsx +176 -0
  146. package/src/components/Dropdown/NestedViewLayout.tsx +88 -0
  147. package/src/components/Dropdown/README.md +41 -0
  148. package/src/components/Dropdown/Receive/Receive.tsx +144 -0
  149. package/src/components/Dropdown/Root/AccountAddressActions.tsx +99 -0
  150. package/src/components/Dropdown/Root/AccountBalance.tsx +69 -0
  151. package/src/components/Dropdown/Root/AccountBtcListing.tsx +53 -0
  152. package/src/components/Dropdown/Root/AccountError.tsx +34 -0
  153. package/src/components/Dropdown/Root/AccountMusdListing.tsx +45 -0
  154. package/src/components/Dropdown/Root/AccountOtherAssets.tsx +85 -0
  155. package/src/components/Dropdown/Root/Root.tsx +104 -0
  156. package/src/components/Dropdown/Root/WalletAddress.tsx +123 -0
  157. package/src/components/Dropdown/Root/WelcomeBlock.tsx +173 -0
  158. package/src/components/Dropdown/Settings/InlineEditField.tsx +212 -0
  159. package/src/components/Dropdown/Settings/Settings.tsx +87 -0
  160. package/src/components/Dropdown/SlotNumber.tsx +131 -0
  161. package/src/components/Dropdown/TestnetTopBanner.tsx +32 -0
  162. package/src/components/Dropdown/index.ts +2 -0
  163. package/src/components/index.ts +1 -0
  164. package/src/hooks/index.ts +1 -0
  165. package/src/hooks/useUpdateUserProfile.ts +34 -0
  166. package/src/hooks/useWalletAccount.ts +2 -8
  167. package/src/index.ts +1 -0
  168. package/src/stores/dropdownStore.ts +1 -0
  169. package/src/utils/validation.test.ts +97 -0
  170. package/src/utils/validation.ts +35 -0
@@ -0,0 +1,131 @@
1
+ /* eslint-disable react/no-array-index-key */
2
+ import React, { useMemo } from "react"
3
+ import { AnimatePresence, motion } from "motion/react"
4
+ import { Block, useStyletron } from "@mezo-org/mezo-clay"
5
+
6
+ type SlotNumberProps = {
7
+ className?: string
8
+ children: number
9
+ formatFunction?: (value: number) => string
10
+ }
11
+
12
+ type SlotNumberDigitProps = {
13
+ className?: string
14
+ children: number
15
+ }
16
+
17
+ function SlotNumberDigit(props: SlotNumberDigitProps) {
18
+ const { children: value, ...restProps } = props
19
+
20
+ const [css] = useStyletron()
21
+
22
+ const digits = [...Array(10).keys()]
23
+
24
+ return (
25
+ <AnimatePresence initial={false}>
26
+ <motion.div
27
+ initial={{
28
+ y: "100%",
29
+ }}
30
+ animate={{
31
+ y: "0%",
32
+ }}
33
+ layout="position"
34
+ className={css({
35
+ position: "relative",
36
+ })}
37
+ {...restProps}
38
+ >
39
+ <Block
40
+ overrides={{
41
+ Block: {
42
+ style: {
43
+ visibility: "hidden",
44
+ },
45
+ },
46
+ }}
47
+ >
48
+ {value}
49
+ </Block>
50
+
51
+ <motion.div
52
+ className={css({
53
+ display: "flex",
54
+ flexDirection: "column",
55
+ position: "absolute",
56
+ inset: 0,
57
+ height: "fit-content",
58
+ })}
59
+ initial={false}
60
+ animate={{ y: `${(value / 10) * -100}%` }}
61
+ transition={{
62
+ type: "spring",
63
+ damping: 16,
64
+ stiffness: 220,
65
+ }}
66
+ >
67
+ {digits.map((digit) => (
68
+ <Block as="span" key={digit}>
69
+ {digit}
70
+ </Block>
71
+ ))}
72
+ </motion.div>
73
+ </motion.div>
74
+ </AnimatePresence>
75
+ )
76
+ }
77
+
78
+ /**
79
+ * SlotNumber component displays a number with animated digits.
80
+ *
81
+ * @typedef {object} SlotNumberProps
82
+ * @property {number} children - The number to display.
83
+ * @property {function} [formatFunction] - Optional function to format the number.
84
+ * @property {BlockProps} [restProps] - Additional properties for the Block component.
85
+ * @see https://pyszkowski.dev/writings/slot-number
86
+ * @returns {JSX.Element} The rendered SlotNumber component.
87
+ */
88
+ function SlotNumber(props: SlotNumberProps) {
89
+ const { children: value, formatFunction, ...restProps } = props
90
+
91
+ const [css] = useStyletron()
92
+
93
+ const characters = useMemo(() => {
94
+ const formattedValue = formatFunction
95
+ ? formatFunction(value)
96
+ : value.toString()
97
+
98
+ return formattedValue
99
+ .split("")
100
+ .map((character) => (/^[0-9]$/.test(character) ? +character : character))
101
+ .reverse()
102
+ }, [formatFunction, value])
103
+
104
+ return (
105
+ <motion.div
106
+ layout
107
+ className={css({
108
+ display: "inline-flex",
109
+ flexDirection: "row-reverse",
110
+ justifyContent: "flex-end",
111
+ overflow: "hidden",
112
+ userSelect: "none",
113
+ })}
114
+ {...restProps}
115
+ >
116
+ {characters.map((character, index) =>
117
+ typeof character === "number" ? (
118
+ <SlotNumberDigit key={`slot-number-character-${index}`}>
119
+ {character}
120
+ </SlotNumberDigit>
121
+ ) : (
122
+ <motion.span layout key={`slot-number-character-${index}`}>
123
+ {character}
124
+ </motion.span>
125
+ ),
126
+ )}
127
+ </motion.div>
128
+ )
129
+ }
130
+
131
+ export default SlotNumber
@@ -0,0 +1,32 @@
1
+ import React from "react"
2
+ import {
3
+ Block,
4
+ InfoCircle,
5
+ LabelSmall,
6
+ useStyletron,
7
+ } from "@mezo-org/mezo-clay"
8
+ import { usePassportContext } from "../../hooks/usePassportContext"
9
+
10
+ export default function TestnetTopBanner() {
11
+ const [, theme] = useStyletron()
12
+
13
+ const { environment } = usePassportContext()
14
+
15
+ if (environment !== "testnet") {
16
+ return null
17
+ }
18
+
19
+ return (
20
+ <Block
21
+ display="flex"
22
+ alignItems="center"
23
+ padding={`${theme.sizing.scale700} ${theme.sizing.scale600}`}
24
+ backgroundColor={theme.colors.warning}
25
+ >
26
+ <InfoCircle size={theme.sizing.scale550} />
27
+ <LabelSmall marginLeft={theme.sizing.scale500}>
28
+ You are using testnet funds.
29
+ </LabelSmall>
30
+ </Block>
31
+ )
32
+ }
@@ -0,0 +1,2 @@
1
+ export { Dropdown } from "./Dropdown"
2
+ export type { DropdownProps } from "./Dropdown"
@@ -0,0 +1 @@
1
+ export { Dropdown, DropdownProps } from "./Dropdown"
@@ -25,6 +25,7 @@ export {
25
25
  useResetTokensBalances,
26
26
  } from "./useTokensBalances"
27
27
  export * from "./useUpdateMezoId"
28
+ export * from "./useUpdateUserProfile"
28
29
  export * from "./useValidateMezoId"
29
30
  export * from "./useRewardsApiClient"
30
31
  export * from "./useWalletAccount"
@@ -0,0 +1,34 @@
1
+ import {
2
+ useQueryClient,
3
+ useMutation,
4
+ MutationOptions,
5
+ DefaultError,
6
+ } from "@tanstack/react-query"
7
+ import { useAuthApiClient } from "./useAuthApiClient"
8
+ import { QUERY_KEYS } from "./constants"
9
+ import type { UpdateAccountRequest, UpdateAccountResponse } from "../api"
10
+
11
+ export function useUpdateUserProfile(
12
+ mutationOptions: Omit<
13
+ MutationOptions<UpdateAccountResponse, DefaultError, UpdateAccountRequest>,
14
+ "mutationFn" | "mutationKey"
15
+ > = {},
16
+ ) {
17
+ const queryClient = useQueryClient()
18
+ const authApiClient = useAuthApiClient()
19
+
20
+ const { onSuccess: customOnSuccess, ...restMutationOptions } = mutationOptions
21
+
22
+ const { mutate, mutateAsync, ...rest } = useMutation({
23
+ mutationFn: (updates: UpdateAccountRequest) =>
24
+ authApiClient.updateAccount(updates),
25
+ onSuccess: (data, variables, context) => {
26
+ queryClient.resetQueries({ queryKey: [QUERY_KEYS.ACCOUNT] })
27
+
28
+ if (customOnSuccess) customOnSuccess(data, variables, context)
29
+ },
30
+ ...restMutationOptions,
31
+ })
32
+
33
+ return { updateProfile: mutate, updateProfileAsync: mutateAsync, ...rest }
34
+ }
@@ -2,18 +2,12 @@ import { OrangeKitConnector } from "@mezo-org/orangekit"
2
2
  import { useQuery } from "@tanstack/react-query"
3
3
  import { useMemo } from "react"
4
4
  import { Address } from "viem"
5
- import { Connector, useAccount, UseAccountReturnType } from "wagmi"
5
+ import { useAccount, UseAccountReturnType } from "wagmi"
6
6
 
7
- type UseWalletAccountReturn = Omit<
8
- UseAccountReturnType,
9
- "isConnected" | "address" | "connector" | "chainId"
10
- > & {
7
+ type UseWalletAccountReturn = Omit<UseAccountReturnType, "address"> & {
11
8
  accountAddress?: Address
12
9
  walletAddress?: string
13
- isConnected: boolean
14
10
  networkFamily: "bitcoin" | "evm"
15
- connector?: Connector
16
- chainId?: number
17
11
  }
18
12
 
19
13
  export function useWalletAccount(): UseWalletAccountReturn {
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./components"
1
2
  export {
2
3
  unisatWalletMezoMainnet,
3
4
  unisatWalletMezoTestnet,
@@ -3,6 +3,7 @@ import { create } from "zustand"
3
3
  export enum DropdownView {
4
4
  ROOT = "ROOT",
5
5
  RECEIVE = "RECEIVE",
6
+ SETTINGS = "SETTINGS",
6
7
  }
7
8
 
8
9
  interface DropdownStore {
@@ -0,0 +1,97 @@
1
+ import { validateEmail, validateTelegram } from "./validation"
2
+
3
+ describe("validateEmail", () => {
4
+ it("returns null for empty string", () => {
5
+ expect(validateEmail("")).toBeNull()
6
+ })
7
+
8
+ it("returns null for valid email", () => {
9
+ expect(validateEmail("user@example.com")).toBeNull()
10
+ })
11
+
12
+ it("returns null for valid email with subdomain", () => {
13
+ expect(validateEmail("user@mail.example.com")).toBeNull()
14
+ })
15
+
16
+ it("returns null for valid email with plus addressing", () => {
17
+ expect(validateEmail("user+tag@example.com")).toBeNull()
18
+ })
19
+
20
+ it("returns error for email without @", () => {
21
+ expect(validateEmail("userexample.com")).toBe(
22
+ "Please enter a valid email address",
23
+ )
24
+ })
25
+
26
+ it("returns error for email without domain", () => {
27
+ expect(validateEmail("user@")).toBe("Please enter a valid email address")
28
+ })
29
+
30
+ it("returns error for email without TLD", () => {
31
+ expect(validateEmail("user@example")).toBe(
32
+ "Please enter a valid email address",
33
+ )
34
+ })
35
+
36
+ it("returns error for email with spaces", () => {
37
+ expect(validateEmail("user @example.com")).toBe(
38
+ "Please enter a valid email address",
39
+ )
40
+ })
41
+ })
42
+
43
+ describe("validateTelegram", () => {
44
+ it("returns null for empty string", () => {
45
+ expect(validateTelegram("")).toBeNull()
46
+ })
47
+
48
+ it("returns null for valid handle without @", () => {
49
+ expect(validateTelegram("username")).toBeNull()
50
+ })
51
+
52
+ it("returns null for valid handle with @", () => {
53
+ expect(validateTelegram("@username")).toBeNull()
54
+ })
55
+
56
+ it("returns null for handle with numbers", () => {
57
+ expect(validateTelegram("user123")).toBeNull()
58
+ })
59
+
60
+ it("returns null for handle with underscores", () => {
61
+ expect(validateTelegram("user_name")).toBeNull()
62
+ })
63
+
64
+ it("returns null for exactly 5 character handle", () => {
65
+ expect(validateTelegram("abcde")).toBeNull()
66
+ })
67
+
68
+ it("returns error for handle shorter than 5 characters", () => {
69
+ expect(validateTelegram("abcd")).toBe(
70
+ "Telegram handle must be at least 5 characters",
71
+ )
72
+ })
73
+
74
+ it("returns error for handle with @ that is too short", () => {
75
+ expect(validateTelegram("@abcd")).toBe(
76
+ "Telegram handle must be at least 5 characters",
77
+ )
78
+ })
79
+
80
+ it("returns error for handle with special characters", () => {
81
+ expect(validateTelegram("user-name")).toBe(
82
+ "Telegram handle can only contain letters, numbers, and underscores",
83
+ )
84
+ })
85
+
86
+ it("returns error for handle with spaces", () => {
87
+ expect(validateTelegram("user name")).toBe(
88
+ "Telegram handle can only contain letters, numbers, and underscores",
89
+ )
90
+ })
91
+
92
+ it("returns error for handle with dots", () => {
93
+ expect(validateTelegram("user.name")).toBe(
94
+ "Telegram handle can only contain letters, numbers, and underscores",
95
+ )
96
+ })
97
+ })
@@ -0,0 +1,35 @@
1
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
2
+
3
+ /**
4
+ * Validates an email address.
5
+ * @param value - The email address to validate
6
+ * @returns An error message if invalid, null if valid or empty
7
+ */
8
+ export function validateEmail(value: string): string | null {
9
+ if (!value) {
10
+ return null
11
+ }
12
+ if (!EMAIL_REGEX.test(value)) {
13
+ return "Please enter a valid email address"
14
+ }
15
+ return null
16
+ }
17
+
18
+ /**
19
+ * Validates a Telegram handle.
20
+ * @param value - The Telegram handle to validate (with or without @ prefix)
21
+ * @returns An error message if invalid, null if valid or empty
22
+ */
23
+ export function validateTelegram(value: string): string | null {
24
+ if (!value) {
25
+ return null
26
+ }
27
+ const handle = value.startsWith("@") ? value.slice(1) : value
28
+ if (handle.length < 5) {
29
+ return "Telegram handle must be at least 5 characters"
30
+ }
31
+ if (!/^[a-zA-Z0-9_]+$/.test(handle)) {
32
+ return "Telegram handle can only contain letters, numbers, and underscores"
33
+ }
34
+ return null
35
+ }