@huma-finance/widgets 0.0.62-beta.627 → 0.0.62-beta.630

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.
@@ -8,6 +8,11 @@ import {
8
8
  NotifiFrontendClient,
9
9
  newFrontendClient,
10
10
  } from '@notifi-network/notifi-frontend-client'
11
+ import {
12
+ FtuStage,
13
+ useNotifiFrontendClientContext,
14
+ useNotifiUserSettingContext,
15
+ } from '@notifi-network/notifi-react'
11
16
 
12
17
  export const useDoesChainSupportNotifi = (chainId: number | undefined) => {
13
18
  const [notifiChainSupported, setNotifiChainSupported] = useState(false)
@@ -54,32 +59,31 @@ export const useNotifiClient = (
54
59
  return { notifiClient }
55
60
  }
56
61
 
62
+ // Check if user is authenticated
63
+ // If not authenticated or expired, have user login
64
+ // After login, check FTU stage
65
+ // If FTU stage is not Done, go to signup flow. Otherwise, skip to next step
66
+
57
67
  export const useIsFirstTimeNotifiUser = (
58
68
  account: string | undefined,
59
69
  chainId: number | undefined,
60
70
  ) => {
61
71
  const [isFirstTimeNotifiUser, setIsFirstTimeNotifiUser] = useState(false)
62
- const { notifiClient } = useNotifiClient(account, chainId)
72
+ const {
73
+ frontendClientStatus: { isInitialized, isAuthenticated, isExpired },
74
+ } = useNotifiFrontendClientContext()
75
+ const { ftuStage, isLoading: isLoadingFtu } = useNotifiUserSettingContext()
63
76
  const { notifiChainSupported } = useDoesChainSupportNotifi(chainId)
64
77
 
65
78
  useEffect(() => {
66
79
  const checkIsFirstTimeNotifiUser = async () => {
67
- if (notifiClient != null && notifiChainSupported) {
68
- const { userState } = notifiClient
69
- // If the user is not authenticated or expired, they are a first time user
70
- if (
71
- userState == null ||
72
- (userState.status !== 'authenticated' &&
73
- userState.status !== 'expired')
74
- ) {
80
+ if (isInitialized && notifiChainSupported) {
81
+ // If the user is not authenticated or expired, they are possibly a first time user.
82
+ if (!isAuthenticated || isExpired) {
75
83
  setIsFirstTimeNotifiUser(true)
76
- } else if (userState.status === 'authenticated') {
77
- // If a user is authenticated, check if they have a target group (AKA email)
78
- // associated with their wallet. If they do not, show them the notifi onboarding flow.
79
- const targetGroups = await notifiClient.getTargetGroups()
80
- const emailTargets = targetGroups[0]?.emailTargets?.[0]
81
-
82
- if (emailTargets == null || emailTargets.emailAddress == null) {
84
+ } else if (isAuthenticated) {
85
+ // If a user is authenticated, check if they've completed all the FTU (First Time User) stages.
86
+ if (ftuStage !== FtuStage.Done) {
83
87
  setIsFirstTimeNotifiUser(true)
84
88
  }
85
89
  }
@@ -87,7 +91,14 @@ export const useIsFirstTimeNotifiUser = (
87
91
  }
88
92
 
89
93
  checkIsFirstTimeNotifiUser()
90
- }, [notifiChainSupported, notifiClient])
94
+ }, [
95
+ ftuStage,
96
+ isAuthenticated,
97
+ isExpired,
98
+ isInitialized,
99
+ isLoadingFtu,
100
+ notifiChainSupported,
101
+ ])
91
102
 
92
103
  return { isFirstTimeNotifiUser }
93
104
  }
package/src/index.tsx CHANGED
@@ -93,6 +93,7 @@ import {
93
93
  SolanaPayment,
94
94
  SolanaPaymentProps,
95
95
  } from './components/CreditLine/solanaPayment'
96
+ import { NotifiContextWrapper } from './components/Notifi/NotifiContextWrapper'
96
97
 
97
98
  /**
98
99
  * Mapping of your JSON-RPC connections indexed by chainId
@@ -136,7 +137,11 @@ function Widget(props: WCProps<WidgetProps>) {
136
137
  <ReduxProvider store={store}>
137
138
  <Web3Provider {...(props as Web3Props)} defaultChainId={chainId}>
138
139
  <AtomProvider>
139
- <ChainSupportProvider>{children}</ChainSupportProvider>
140
+ <ChainSupportProvider>
141
+ <NotifiContextWrapper chainId={chainId}>
142
+ {children}
143
+ </NotifiContextWrapper>
144
+ </ChainSupportProvider>
140
145
  </AtomProvider>
141
146
  </Web3Provider>
142
147
  </ReduxProvider>
@@ -667,3 +672,5 @@ export function SolanaPaymentWidget(props: SolanaPaymentWidgetProps) {
667
672
  </SolanaWidget>
668
673
  )
669
674
  }
675
+
676
+ export * from './components/Notifi/NotifiContextWrapper'
@@ -3,7 +3,7 @@ export const coreThemeConstants = {
3
3
  md: 4,
4
4
  },
5
5
  fontFamily: [
6
- 'Roboto',
6
+ 'Red-Hat-Display',
7
7
  '-apple-system',
8
8
  'BlinkMacSystemFont',
9
9
  '"Segoe UI"',
@@ -1,128 +0,0 @@
1
- import { gql, useQuery } from '@apollo/client'
2
- import {
3
- CreditEvent,
4
- CreditEventText,
5
- downScale,
6
- isEmpty,
7
- PoolInfoType,
8
- timeUtil,
9
- } from '@huma-finance/shared'
10
- import { useMQ } from '@huma-finance/web-shared'
11
- import { Box, css, useTheme } from '@mui/material'
12
- import { useWeb3React } from '@web3-react/core'
13
- import { useEffect, useState } from 'react'
14
-
15
- import { HumaTable } from './HumaTable'
16
-
17
- const GET_ACTIVITY = gql`
18
- query GetCreditLineActivity($pool: String!, $owner: String!, $event: [Int]!) {
19
- creditEvents(
20
- where: { pool: $pool, owner: $owner, event_in: $event }
21
- orderBy: timestamp
22
- orderDirection: desc
23
- ) {
24
- id
25
- amount
26
- timestamp
27
- owner
28
- pool
29
- event
30
- }
31
- }
32
- `
33
-
34
- type Props = {
35
- poolInfo: PoolInfoType
36
- targetEvents: CreditEvent[]
37
- }
38
-
39
- type ActivityDataType = {
40
- id: string
41
- owner: string
42
- pool: string
43
- timestamp: string
44
- amount: string
45
- event: number
46
- eventText: string | undefined
47
- }
48
-
49
- const columns = [
50
- { title: 'Action', dataIndex: 'eventText' },
51
- {
52
- title: 'Amount',
53
- action: (record: { amount: string }) => {
54
- if (isEmpty(record.amount) || Number.isNaN(record.amount)) {
55
- return ''
56
- }
57
- return Number(record.amount).toFixed(2)
58
- },
59
- },
60
- { title: 'Date', dataIndex: 'timestamp' },
61
- ]
62
-
63
- export function Activity({
64
- poolInfo,
65
- targetEvents,
66
- }: Props): React.ReactElement | null {
67
- const theme = useTheme()
68
- const { account } = useWeb3React()
69
- const { decimals } = poolInfo.poolUnderlyingToken
70
- const [events, setEvents] = useState<ActivityDataType[] | undefined>()
71
- const { data } = useQuery<{
72
- creditEvents: ActivityDataType[]
73
- }>(GET_ACTIVITY, {
74
- variables: {
75
- pool: poolInfo.pool,
76
- owner: account,
77
- event: targetEvents,
78
- },
79
- })
80
- const { isSmSize } = useMQ()
81
-
82
- const getPadding = () => {
83
- if (isSmSize) {
84
- return theme.spacing(2, 3)
85
- }
86
- return theme.spacing(4, 6)
87
- }
88
-
89
- useEffect(() => {
90
- if (!data?.creditEvents) {
91
- return
92
- }
93
- const result = data.creditEvents.map((item) => {
94
- const event = JSON.parse(JSON.stringify(item)) as ActivityDataType
95
- event.eventText = CreditEventText[item.event]
96
- event.amount = downScale(event.amount, decimals)
97
- event.timestamp = timeUtil.timestampToLL(Number(event.timestamp))
98
- return event
99
- })
100
- setEvents(result)
101
- }, [data?.creditEvents, decimals])
102
-
103
- const styles = {
104
- wrapper: css`
105
- margin-top: ${theme.spacing(9)};
106
- `,
107
- title: css`
108
- font-weight: 800;
109
- font-size: 24px;
110
- color: ${theme.palette.text.primary};
111
- margin-bottom: ${theme.spacing(2)};
112
- `,
113
- tableWrapper: css`
114
- box-shadow: 0px 4px 40px 8px rgba(0, 0, 0, 0.04);
115
- border-radius: 24px;
116
- padding: ${getPadding()};
117
- `,
118
- }
119
-
120
- return (
121
- <Box css={styles.wrapper}>
122
- <Box css={styles.title}>Recent Activity</Box>
123
- <Box css={styles.tableWrapper}>
124
- <HumaTable columns={columns} rows={events} hideRowsPerPage={isSmSize} />
125
- </Box>
126
- </Box>
127
- )
128
- }
@@ -1,143 +0,0 @@
1
- import { Box, css, LinearProgress, useTheme } from '@mui/material'
2
- import { useWeb3React } from '@web3-react/core'
3
- import {
4
- AccountStats,
5
- downScale,
6
- formatMoney,
7
- hasRFActiveLoan,
8
- PoolInfoType,
9
- timestampToLL,
10
- } from '@huma-finance/shared'
11
- import { useMQ, usePoolUnderlyingToken } from '@huma-finance/web-shared'
12
- import React, { useEffect, useState } from 'react'
13
-
14
- import { ColumnType } from '../../utilTypes'
15
- import { HumaTable } from '../HumaTable'
16
- import { ReceiptIcon } from '../icons'
17
-
18
- type Props<T> = {
19
- title: 'Streams' | 'Invoices'
20
- poolInfo: PoolInfoType
21
- loading: boolean
22
- accountStats: AccountStats
23
- handlePayManually: (item: T) => void
24
- getColumns: (handlePayManually: (item: T) => void) => ColumnType<T>[]
25
- setItem?: (
26
- accountStats: AccountStats,
27
- ) => Promise<{ name?: string; payer?: string }>
28
- }
29
-
30
- export function FactoredList<T>({
31
- title,
32
- poolInfo,
33
- loading,
34
- accountStats,
35
- handlePayManually,
36
- getColumns,
37
- setItem,
38
- }: Props<T>): React.ReactElement {
39
- const theme = useTheme()
40
- const { isMdSize } = useMQ()
41
- const { isActive, chainId } = useWeb3React()
42
- const { decimals } = usePoolUnderlyingToken(
43
- poolInfo.poolName,
44
- poolInfo.poolType,
45
- chainId,
46
- )
47
- const [factoredItem, setFactoredItem] = useState<Partial<T>>()
48
- const { creditRecord, receivableInfo } = accountStats
49
- const hasActiveLoan = hasRFActiveLoan(creditRecord, receivableInfo)
50
-
51
- useEffect(() => {
52
- const fetchData = async () => {
53
- if (creditRecord && receivableInfo?.receivableParam.gt(0) && decimals) {
54
- const id = receivableInfo.receivableParam.toString()
55
- const dueDate = timestampToLL(creditRecord.dueDate.toNumber())
56
- const { receivableAmount } = receivableInfo
57
- const { totalDue } = creditRecord
58
- let factoredItem = {
59
- id,
60
- due: dueDate,
61
- icon: <ReceiptIcon css={{ width: '24px' }} />,
62
- amount: formatMoney(downScale(receivableAmount.toString(), decimals)),
63
- factored: formatMoney(downScale(totalDue.toString(), decimals)),
64
- remained: formatMoney(
65
- downScale(receivableAmount.sub(totalDue).toString(), decimals),
66
- ),
67
- } as T
68
- if (setItem) {
69
- const customProps = await setItem(accountStats)
70
- factoredItem = { ...factoredItem, ...customProps }
71
- }
72
- setFactoredItem(factoredItem)
73
- }
74
- }
75
- fetchData()
76
- }, [accountStats, creditRecord, decimals, receivableInfo, setItem])
77
-
78
- const getPadding = () => {
79
- if (isMdSize) {
80
- return theme.spacing(0, 1, 1, 1)
81
- }
82
- return theme.spacing(0, 2, 2, 2)
83
- }
84
-
85
- const styles = {
86
- title: css`
87
- font-weight: 800;
88
- color: ${theme.palette.text.primary};
89
- font-size: 24px;
90
- margin-bottom: ${theme.spacing(2)};
91
- `,
92
- description: css`
93
- font-weight: 400;
94
- color: ${theme.palette.text.primary};
95
- font-size: 1rem;
96
- `,
97
- tableWrapper: css`
98
- margin-top: ${theme.spacing(3)};
99
- padding: ${getPadding()};
100
- background: linear-gradient(180deg, #ffffff 0%, #ffffff 100%);
101
- border: 1px solid #ffffff;
102
- border-radius: 24px;
103
- position: relative;
104
- `,
105
- progress: css`
106
- width: calc(100% - 30px);
107
- position: absolute;
108
- top: 0;
109
- left: ${theme.spacing(2)};
110
- `,
111
- }
112
-
113
- return (
114
- <Box>
115
- <Box css={styles.title}>Factored {title}</Box>
116
- <Box css={styles.description} marginBottom={theme.spacing(6)}>
117
- {!isActive && (
118
- <Box component='span'>
119
- Please connect wallet to check your factored {title.toLowerCase()}.
120
- </Box>
121
- )}
122
- {isActive && !hasActiveLoan && (
123
- <Box component='span'>
124
- You don’t have any factored {title.toLowerCase()}.
125
- </Box>
126
- )}
127
- {isActive && hasActiveLoan && factoredItem && (
128
- <Box css={styles.tableWrapper}>
129
- {loading && (
130
- <Box css={styles.progress}>
131
- <LinearProgress />
132
- </Box>
133
- )}
134
- <HumaTable
135
- columns={getColumns(handlePayManually)}
136
- rows={[factoredItem]}
137
- />
138
- </Box>
139
- )}
140
- </Box>
141
- </Box>
142
- )
143
- }
@@ -1,90 +0,0 @@
1
- import { Box, css, LinearProgress, useTheme } from '@mui/material'
2
- import { useWeb3React } from '@web3-react/core'
3
- import { useMQ } from '@huma-finance/web-shared'
4
- import React from 'react'
5
-
6
- import { ColumnType, WCProps } from '../../utilTypes'
7
- import { HumaTable } from '../HumaTable'
8
-
9
- type Props<T> = {
10
- items: T[] | undefined
11
- loading: boolean
12
- columns: ColumnType<T>[]
13
- title: string
14
- connectWalletDesc: string
15
- noItemDesc: string
16
- }
17
-
18
- export function UpcomingList<T>({
19
- items,
20
- loading,
21
- columns,
22
- title,
23
- connectWalletDesc,
24
- noItemDesc,
25
- children,
26
- }: WCProps<Props<T>>): React.ReactElement {
27
- const theme = useTheme()
28
- const { isActive } = useWeb3React()
29
- const { isMdSize, isSmSize } = useMQ()
30
-
31
- const getPadding = () => {
32
- if (isMdSize) {
33
- return theme.spacing(0, 1, 1, 1)
34
- }
35
- return theme.spacing(0, 2, 2, 2)
36
- }
37
-
38
- const styles = {
39
- title: css`
40
- font-weight: 800;
41
- color: ${theme.palette.text.primary};
42
- font-size: 24px;
43
- margin-bottom: ${theme.spacing(2)};
44
- `,
45
- description: css`
46
- font-weight: 400;
47
- color: ${theme.palette.text.primary};
48
- font-size: 1rem;
49
- `,
50
- tableWrapper: css`
51
- margin-top: ${theme.spacing(3)};
52
- padding: ${getPadding()};
53
- background: linear-gradient(180deg, #ffffff 0%, #ffffff 100%);
54
- border: 1px solid #ffffff;
55
- border-radius: 24px;
56
- position: relative;
57
- `,
58
- progress: css`
59
- width: calc(100% - 30px);
60
- position: absolute;
61
- top: 0;
62
- left: ${theme.spacing(2)};
63
- `,
64
- }
65
-
66
- return (
67
- <Box>
68
- <Box css={styles.title}>{title}</Box>
69
- <Box css={styles.description} marginBottom={theme.spacing(6)}>
70
- {!isActive && <Box component='span'>{connectWalletDesc}</Box>}
71
- {isActive && !items?.length && <Box>{noItemDesc}</Box>}
72
- {isActive && !!items?.length && (
73
- <Box css={styles.tableWrapper}>
74
- {loading && (
75
- <Box css={styles.progress}>
76
- <LinearProgress />
77
- </Box>
78
- )}
79
- <HumaTable
80
- columns={columns}
81
- rows={items}
82
- hideRowsPerPage={isSmSize}
83
- />
84
- </Box>
85
- )}
86
- {children}
87
- </Box>
88
- </Box>
89
- )
90
- }
@@ -1,2 +0,0 @@
1
- export * from './UpcomingList'
2
- export * from './FactoredList'
@@ -1,203 +0,0 @@
1
- import { Box, css, Skeleton, TablePagination, useTheme } from '@mui/material'
2
- import Table from '@mui/material/Table'
3
- import TableBody from '@mui/material/TableBody'
4
- import TableCell from '@mui/material/TableCell'
5
- import TableContainer from '@mui/material/TableContainer'
6
- import TableHead from '@mui/material/TableHead'
7
- import TableRow from '@mui/material/TableRow'
8
- import * as React from 'react'
9
-
10
- import { useElementDimensions } from '../hooks/useElementDimensions'
11
- import { ColumnType } from '../utilTypes'
12
- import { SeparatorIcon } from './icons'
13
-
14
- type Props<T> = {
15
- columns: ColumnType<T>[]
16
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
- rows?: any[]
18
- hideRowsPerPage?: boolean
19
- }
20
-
21
- // @TODO: Move HumaTable along with Superfluid widget to Dapp package
22
- export function HumaTable<T>({
23
- columns,
24
- rows,
25
- hideRowsPerPage = false,
26
- }: Props<T>): React.ReactElement | null {
27
- const theme = useTheme()
28
- const ref = React.useRef()
29
- const [page, setPage] = React.useState(0)
30
- const [rowsPerPage, setRowsPerPage] = React.useState(5)
31
- const noPagination = rows ? rows.length <= 5 : true
32
- const rowPerPageOptions = hideRowsPerPage ? [] : [5, 10, 25]
33
- const { width } = useElementDimensions(ref)
34
- const isLoading = !columns || !rows
35
-
36
- const styles = {
37
- tableHeader: css`
38
- font-weight: 400;
39
- ${theme.cssMixins.rowVCentered}
40
- font-size: 14px;
41
- color: rgba(0, 0, 0, 0.87);
42
- `,
43
- tableBody: css`
44
- font-weight: 400;
45
- font-size: 14px;
46
- color: rgba(0, 0, 0, 0.87);
47
- `,
48
- empty: css`
49
- display: flex;
50
- flex-direction: column;
51
- align-items: center;
52
- margin-top: ${theme.spacing(2)};
53
- font-weight: 400;
54
- font-size: 14px;
55
- `,
56
- }
57
-
58
- const handleChangePage = (event: unknown, newPage: number) => {
59
- setPage(newPage)
60
- }
61
-
62
- const handleChangeRowsPerPage = (
63
- event: React.ChangeEvent<HTMLInputElement>,
64
- ) => {
65
- setRowsPerPage(parseInt(event.target.value, 10))
66
- setPage(0)
67
- }
68
-
69
- const getTableHeader = () => (
70
- <TableHead>
71
- <TableRow>
72
- {columns.map((column, index) => (
73
- <TableCell key={column.title} width={column.width}>
74
- <Box css={styles.tableHeader}>
75
- {index !== 0 && <SeparatorIcon />}
76
- <Box component='span' sx={{ marginLeft: index ? '12px' : '0' }}>
77
- {column.title}
78
- </Box>
79
- </Box>
80
- </TableCell>
81
- ))}
82
- </TableRow>
83
- </TableHead>
84
- )
85
-
86
- if (isLoading) {
87
- return (
88
- <Box ref={ref}>
89
- <Box sx={{ width }}>
90
- <Skeleton />
91
- <Skeleton />
92
- <Skeleton />
93
- </Box>
94
- </Box>
95
- )
96
- }
97
-
98
- if (rows.length === 0) {
99
- return (
100
- <TableContainer>
101
- <Table aria-label='simple table'>{getTableHeader()}</Table>
102
- <Box css={styles.empty}>
103
- <Box>No Data</Box>
104
- </Box>
105
- </TableContainer>
106
- )
107
- }
108
-
109
- return (
110
- <>
111
- <TableContainer>
112
- <Table aria-label='simple table'>
113
- {getTableHeader()}
114
- <TableBody>
115
- {rows
116
- .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
117
- .map((row) => (
118
- <TableRow key={row.id || row.name}>
119
- {columns.map((column, index) => (
120
- <TableCell
121
- // eslint-disable-next-line react/no-array-index-key
122
- key={`${column.dataIndex}-${index}`}
123
- component='th'
124
- scope='row'
125
- css={styles.tableBody}
126
- width={column.width}
127
- sx={column.style}
128
- >
129
- {column.dataIndex && (
130
- <Box
131
- component='span'
132
- sx={{
133
- display: 'flex',
134
- alignItems: 'center',
135
- marginLeft: theme.spacing(index ? 1 : 0),
136
- }}
137
- >
138
- {row[column.dataIndex]}
139
- </Box>
140
- )}
141
- {column.action && (
142
- <Box
143
- component='span'
144
- sx={{
145
- display: 'flex',
146
- alignItems: 'center',
147
- marginLeft: theme.spacing(index ? 1 : 0),
148
- lineHeight: 0,
149
- height: 0,
150
- }}
151
- >
152
- {column.action(row)}
153
- </Box>
154
- )}
155
- </TableCell>
156
- ))}
157
- </TableRow>
158
- ))}
159
- </TableBody>
160
- </Table>
161
- </TableContainer>
162
- {!noPagination && (
163
- <TablePagination
164
- size='small'
165
- sx={{
166
- '.MuiTablePagination-selectLabel': {
167
- fontSize: '12px',
168
- },
169
- '.MuiTablePagination-select': {
170
- fontSize: '12px',
171
- },
172
- '.MuiTablePagination-displayedRows': {
173
- fontSize: '12px',
174
- },
175
- '.MuiTablePagination-menuItem': {
176
- fontSize: '14px',
177
- },
178
- }}
179
- rowsPerPageOptions={rowPerPageOptions}
180
- component='div'
181
- count={rows ? rows.length : 0}
182
- rowsPerPage={rowsPerPage}
183
- page={page}
184
- onPageChange={handleChangePage}
185
- onRowsPerPageChange={handleChangeRowsPerPage}
186
- SelectProps={{
187
- MenuProps: {
188
- sx: {
189
- '.MuiTablePagination-menuItem': {
190
- padding: theme.spacing(1, 0),
191
- display: 'flex',
192
- justifyContent: 'center',
193
- fontSize: '14px',
194
- lineHeight: '1.2',
195
- },
196
- },
197
- },
198
- }}
199
- />
200
- )}
201
- </>
202
- )
203
- }