Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
F
frontend
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
vicotor
frontend
Commits
490d61b0
Commit
490d61b0
authored
Jul 15, 2024
by
Max Alekseenko
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
implement rating change
parent
dc28dff6
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
125 additions
and
137 deletions
+125
-137
marketplace.ts
types/client/marketplace.ts
+6
-13
MarketplaceAppCard.tsx
ui/marketplace/MarketplaceAppCard.tsx
+4
-6
MarketplaceAppModal.pw.tsx
ui/marketplace/MarketplaceAppModal.pw.tsx
+4
-1
MarketplaceAppModal.tsx
ui/marketplace/MarketplaceAppModal.tsx
+4
-6
MarketplaceAppTopBar.tsx
ui/marketplace/MarketplaceAppTopBar.tsx
+1
-2
MarketplaceList.tsx
ui/marketplace/MarketplaceList.tsx
+4
-5
PopoverContent.tsx
ui/marketplace/Rating/PopoverContent.tsx
+30
-60
Rating.tsx
ui/marketplace/Rating/Rating.tsx
+10
-8
TriggerButton.tsx
ui/marketplace/Rating/TriggerButton.tsx
+4
-2
useRatings.tsx
ui/marketplace/Rating/useRatings.tsx
+56
-31
useMarketplaceApps.tsx
ui/marketplace/useMarketplaceApps.tsx
+2
-3
No files found.
types/client/marketplace.ts
View file @
490d61b0
...
@@ -26,10 +26,14 @@ export type MarketplaceAppOverview = MarketplaceAppPreview & MarketplaceAppSocia
...
@@ -26,10 +26,14 @@ export type MarketplaceAppOverview = MarketplaceAppPreview & MarketplaceAppSocia
site
?:
string
;
site
?:
string
;
}
}
export
type
AppRating
=
{
recordId
:
string
;
value
:
number
;
}
export
type
MarketplaceAppWithSecurityReport
=
MarketplaceAppOverview
&
{
export
type
MarketplaceAppWithSecurityReport
=
MarketplaceAppOverview
&
{
securityReport
?:
MarketplaceAppSecurityReport
;
securityReport
?:
MarketplaceAppSecurityReport
;
rating
?:
number
;
rating
?:
AppRating
;
ratingRecordId
?:
string
;
}
}
export
enum
MarketplaceCategory
{
export
enum
MarketplaceCategory
{
...
@@ -65,14 +69,3 @@ export type MarketplaceAppSecurityReportRaw = {
...
@@ -65,14 +69,3 @@ export type MarketplaceAppSecurityReportRaw = {
[
chainId
:
string
]:
MarketplaceAppSecurityReport
;
[
chainId
:
string
]:
MarketplaceAppSecurityReport
;
};
};
}
}
export
type
UserRatings
=
{
[
appId
:
string
]:
number
;
};
export
type
AppRatings
=
{
[
appId
:
string
]:
{
recordId
:
string
;
rating
:
number
;
};
};
ui/marketplace/MarketplaceAppCard.tsx
View file @
490d61b0
...
@@ -2,16 +2,16 @@ import { IconButton, Image, Link, LinkBox, Skeleton, useColorModeValue, chakra,
...
@@ -2,16 +2,16 @@ import { IconButton, Image, Link, LinkBox, Skeleton, useColorModeValue, chakra,
import
type
{
MouseEvent
}
from
'
react
'
;
import
type
{
MouseEvent
}
from
'
react
'
;
import
React
,
{
useCallback
}
from
'
react
'
;
import
React
,
{
useCallback
}
from
'
react
'
;
import
type
{
MarketplaceAppWithSecurityReport
,
ContractListTypes
}
from
'
types/client/marketplace
'
;
import
type
{
MarketplaceAppWithSecurityReport
,
ContractListTypes
,
AppRating
}
from
'
types/client/marketplace
'
;
import
useIsMobile
from
'
lib/hooks/useIsMobile
'
;
import
useIsMobile
from
'
lib/hooks/useIsMobile
'
;
import
type
{
EventTypes
,
EventPayload
}
from
'
lib/mixpanel/index
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
import
AppSecurityReport
from
'
./AppSecurityReport
'
;
import
AppSecurityReport
from
'
./AppSecurityReport
'
;
import
MarketplaceAppCardLink
from
'
./MarketplaceAppCardLink
'
;
import
MarketplaceAppCardLink
from
'
./MarketplaceAppCardLink
'
;
import
MarketplaceAppIntegrationIcon
from
'
./MarketplaceAppIntegrationIcon
'
;
import
MarketplaceAppIntegrationIcon
from
'
./MarketplaceAppIntegrationIcon
'
;
import
Rating
from
'
./Rating/Rating
'
;
import
Rating
from
'
./Rating/Rating
'
;
import
type
{
RateFunction
}
from
'
./Rating/useRatings
'
;
interface
Props
extends
MarketplaceAppWithSecurityReport
{
interface
Props
extends
MarketplaceAppWithSecurityReport
{
onInfoClick
:
(
id
:
string
)
=>
void
;
onInfoClick
:
(
id
:
string
)
=>
void
;
...
@@ -21,8 +21,8 @@ interface Props extends MarketplaceAppWithSecurityReport {
...
@@ -21,8 +21,8 @@ interface Props extends MarketplaceAppWithSecurityReport {
onAppClick
:
(
event
:
MouseEvent
,
id
:
string
)
=>
void
;
onAppClick
:
(
event
:
MouseEvent
,
id
:
string
)
=>
void
;
className
?:
string
;
className
?:
string
;
showContractList
:
(
id
:
string
,
type
:
ContractListTypes
)
=>
void
;
showContractList
:
(
id
:
string
,
type
:
ContractListTypes
)
=>
void
;
userRating
:
number
|
undefined
;
userRating
?:
AppRating
;
rateApp
:
(
appId
:
string
,
recordId
:
string
|
undefined
,
rating
:
number
,
source
:
EventPayload
<
EventTypes
.
APP_FEEDBACK
>
[
'
Source
'
])
=>
void
;
rateApp
:
RateFunction
;
isSendingRating
:
boolean
;
isSendingRating
:
boolean
;
isRatingLoading
:
boolean
;
isRatingLoading
:
boolean
;
canRate
:
boolean
|
undefined
;
canRate
:
boolean
|
undefined
;
...
@@ -47,7 +47,6 @@ const MarketplaceAppCard = ({
...
@@ -47,7 +47,6 @@ const MarketplaceAppCard = ({
className
,
className
,
showContractList
,
showContractList
,
rating
,
rating
,
ratingRecordId
,
userRating
,
userRating
,
rateApp
,
rateApp
,
isSendingRating
,
isSendingRating
,
...
@@ -175,7 +174,6 @@ const MarketplaceAppCard = ({
...
@@ -175,7 +174,6 @@ const MarketplaceAppCard = ({
<
Rating
<
Rating
appId=
{
id
}
appId=
{
id
}
rating=
{
rating
}
rating=
{
rating
}
recordId=
{
ratingRecordId
}
userRating=
{
userRating
}
userRating=
{
userRating
}
rate=
{
rateApp
}
rate=
{
rateApp
}
isSending=
{
isSendingRating
}
isSending=
{
isSendingRating
}
...
...
ui/marketplace/MarketplaceAppModal.pw.tsx
View file @
490d61b0
...
@@ -15,7 +15,10 @@ const props = {
...
@@ -15,7 +15,10 @@ const props = {
data
:
{
data
:
{
...
appsMock
[
0
],
...
appsMock
[
0
],
securityReport
:
securityReportsMock
[
0
].
chainsData
[
'
1
'
],
securityReport
:
securityReportsMock
[
0
].
chainsData
[
'
1
'
],
rating
:
4.3
,
rating
:
{
recordId
:
'
test
'
,
value
:
4.3
,
},
}
as
MarketplaceAppWithSecurityReport
,
}
as
MarketplaceAppWithSecurityReport
,
isFavorite
:
false
,
isFavorite
:
false
,
userRating
:
undefined
,
userRating
:
undefined
,
...
...
ui/marketplace/MarketplaceAppModal.tsx
View file @
490d61b0
...
@@ -4,13 +4,12 @@ import {
...
@@ -4,13 +4,12 @@ import {
}
from
'
@chakra-ui/react
'
;
}
from
'
@chakra-ui/react
'
;
import
React
,
{
useCallback
}
from
'
react
'
;
import
React
,
{
useCallback
}
from
'
react
'
;
import
type
{
MarketplaceAppWithSecurityReport
}
from
'
types/client/marketplace
'
;
import
type
{
MarketplaceAppWithSecurityReport
,
AppRating
}
from
'
types/client/marketplace
'
;
import
{
ContractListTypes
}
from
'
types/client/marketplace
'
;
import
{
ContractListTypes
}
from
'
types/client/marketplace
'
;
import
config
from
'
configs/app
'
;
import
config
from
'
configs/app
'
;
import
useIsMobile
from
'
lib/hooks/useIsMobile
'
;
import
useIsMobile
from
'
lib/hooks/useIsMobile
'
;
import
{
nbsp
}
from
'
lib/html-entities
'
;
import
{
nbsp
}
from
'
lib/html-entities
'
;
import
type
{
EventTypes
,
EventPayload
}
from
'
lib/mixpanel/index
'
;
import
*
as
mixpanel
from
'
lib/mixpanel/index
'
;
import
*
as
mixpanel
from
'
lib/mixpanel/index
'
;
import
type
{
IconName
}
from
'
ui/shared/IconSvg
'
;
import
type
{
IconName
}
from
'
ui/shared/IconSvg
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
...
@@ -18,6 +17,7 @@ import IconSvg from 'ui/shared/IconSvg';
...
@@ -18,6 +17,7 @@ import IconSvg from 'ui/shared/IconSvg';
import
AppSecurityReport
from
'
./AppSecurityReport
'
;
import
AppSecurityReport
from
'
./AppSecurityReport
'
;
import
MarketplaceAppModalLink
from
'
./MarketplaceAppModalLink
'
;
import
MarketplaceAppModalLink
from
'
./MarketplaceAppModalLink
'
;
import
Rating
from
'
./Rating/Rating
'
;
import
Rating
from
'
./Rating/Rating
'
;
import
type
{
RateFunction
}
from
'
./Rating/useRatings
'
;
const
feature
=
config
.
features
.
marketplace
;
const
feature
=
config
.
features
.
marketplace
;
const
isRatingEnabled
=
feature
.
isEnabled
&&
feature
.
rating
;
const
isRatingEnabled
=
feature
.
isEnabled
&&
feature
.
rating
;
...
@@ -28,8 +28,8 @@ type Props = {
...
@@ -28,8 +28,8 @@ type Props = {
onFavoriteClick
:
(
id
:
string
,
isFavorite
:
boolean
,
source
:
'
App modal
'
)
=>
void
;
onFavoriteClick
:
(
id
:
string
,
isFavorite
:
boolean
,
source
:
'
App modal
'
)
=>
void
;
data
:
MarketplaceAppWithSecurityReport
;
data
:
MarketplaceAppWithSecurityReport
;
showContractList
:
(
id
:
string
,
type
:
ContractListTypes
,
hasPreviousStep
:
boolean
)
=>
void
;
showContractList
:
(
id
:
string
,
type
:
ContractListTypes
,
hasPreviousStep
:
boolean
)
=>
void
;
userRating
:
number
|
undefined
;
userRating
?:
AppRating
;
rateApp
:
(
appId
:
string
,
recordId
:
string
|
undefined
,
rating
:
number
,
source
:
EventPayload
<
EventTypes
.
APP_FEEDBACK
>
[
'
Source
'
])
=>
void
;
rateApp
:
RateFunction
;
isSendingRating
:
boolean
;
isSendingRating
:
boolean
;
isRatingLoading
:
boolean
;
isRatingLoading
:
boolean
;
canRate
:
boolean
|
undefined
;
canRate
:
boolean
|
undefined
;
...
@@ -64,7 +64,6 @@ const MarketplaceAppModal = ({
...
@@ -64,7 +64,6 @@ const MarketplaceAppModal = ({
categories
,
categories
,
securityReport
,
securityReport
,
rating
,
rating
,
ratingRecordId
,
}
=
data
;
}
=
data
;
const
socialLinks
=
[
const
socialLinks
=
[
...
@@ -175,7 +174,6 @@ const MarketplaceAppModal = ({
...
@@ -175,7 +174,6 @@ const MarketplaceAppModal = ({
<
Rating
<
Rating
appId=
{
id
}
appId=
{
id
}
rating=
{
rating
}
rating=
{
rating
}
recordId=
{
ratingRecordId
}
userRating=
{
userRating
}
userRating=
{
userRating
}
rate=
{
rateApp
}
rate=
{
rateApp
}
isSending=
{
isSendingRating
}
isSending=
{
isSendingRating
}
...
...
ui/marketplace/MarketplaceAppTopBar.tsx
View file @
490d61b0
...
@@ -89,8 +89,7 @@ const MarketplaceAppTopBar = ({ appId, data, isLoading, securityReport }: Props)
...
@@ -89,8 +89,7 @@ const MarketplaceAppTopBar = ({ appId, data, isLoading, securityReport }: Props)
)
}
)
}
<
Rating
<
Rating
appId=
{
appId
}
appId=
{
appId
}
rating=
{
ratings
?.[
appId
]?.
rating
}
rating=
{
ratings
[
appId
]
}
recordId=
{
ratings
?.[
appId
]?.
recordId
}
userRating=
{
userRatings
[
appId
]
}
userRating=
{
userRatings
[
appId
]
}
rate=
{
rateApp
}
rate=
{
rateApp
}
isSending=
{
isSendingRating
}
isSending=
{
isSendingRating
}
...
...
ui/marketplace/MarketplaceList.tsx
View file @
490d61b0
...
@@ -2,13 +2,13 @@ import { Grid } from '@chakra-ui/react';
...
@@ -2,13 +2,13 @@ import { Grid } from '@chakra-ui/react';
import
React
,
{
useCallback
}
from
'
react
'
;
import
React
,
{
useCallback
}
from
'
react
'
;
import
type
{
MouseEvent
}
from
'
react
'
;
import
type
{
MouseEvent
}
from
'
react
'
;
import
type
{
MarketplaceAppWithSecurityReport
,
ContractListTypes
,
UserRatings
}
from
'
types/client/marketplace
'
;
import
type
{
MarketplaceAppWithSecurityReport
,
ContractListTypes
,
AppRating
}
from
'
types/client/marketplace
'
;
import
type
{
EventTypes
,
EventPayload
}
from
'
lib/mixpanel/index
'
;
import
*
as
mixpanel
from
'
lib/mixpanel/index
'
;
import
*
as
mixpanel
from
'
lib/mixpanel/index
'
;
import
EmptySearchResult
from
'
./EmptySearchResult
'
;
import
EmptySearchResult
from
'
./EmptySearchResult
'
;
import
MarketplaceAppCard
from
'
./MarketplaceAppCard
'
;
import
MarketplaceAppCard
from
'
./MarketplaceAppCard
'
;
import
type
{
RateFunction
}
from
'
./Rating/useRatings
'
;
type
Props
=
{
type
Props
=
{
apps
:
Array
<
MarketplaceAppWithSecurityReport
>
;
apps
:
Array
<
MarketplaceAppWithSecurityReport
>
;
...
@@ -19,8 +19,8 @@ type Props = {
...
@@ -19,8 +19,8 @@ type Props = {
selectedCategoryId
?:
string
;
selectedCategoryId
?:
string
;
onAppClick
:
(
event
:
MouseEvent
,
id
:
string
)
=>
void
;
onAppClick
:
(
event
:
MouseEvent
,
id
:
string
)
=>
void
;
showContractList
:
(
id
:
string
,
type
:
ContractListTypes
)
=>
void
;
showContractList
:
(
id
:
string
,
type
:
ContractListTypes
)
=>
void
;
userRatings
:
UserRatings
;
userRatings
:
Record
<
string
,
AppRating
>
;
rateApp
:
(
appId
:
string
,
recordId
:
string
,
rating
:
number
,
source
:
EventPayload
<
EventTypes
.
APP_FEEDBACK
>
[
'
Source
'
])
=>
void
;
rateApp
:
RateFunction
;
isSendingRating
:
boolean
;
isSendingRating
:
boolean
;
isRatingLoading
:
boolean
;
isRatingLoading
:
boolean
;
canRate
:
boolean
|
undefined
;
canRate
:
boolean
|
undefined
;
...
@@ -68,7 +68,6 @@ const MarketplaceList = ({
...
@@ -68,7 +68,6 @@ const MarketplaceList = ({
securityReport=
{
app
.
securityReport
}
securityReport=
{
app
.
securityReport
}
showContractList=
{
showContractList
}
showContractList=
{
showContractList
}
rating=
{
app
.
rating
}
rating=
{
app
.
rating
}
ratingRecordId=
{
app
.
ratingRecordId
}
userRating=
{
userRatings
[
app
.
id
]
}
userRating=
{
userRatings
[
app
.
id
]
}
rateApp=
{
rateApp
}
rateApp=
{
rateApp
}
isSendingRating=
{
isSendingRating
}
isSendingRating=
{
isSendingRating
}
...
...
ui/marketplace/Rating/PopoverContent.tsx
View file @
490d61b0
import
{
Text
,
Flex
,
Spinner
,
Button
}
from
'
@chakra-ui/react
'
;
import
{
Text
,
Flex
,
Spinner
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
React
from
'
react
'
;
import
{
mdash
}
from
'
lib/html-entities
'
;
import
type
{
AppRating
}
from
'
types/client/marketplace
'
;
import
type
{
EventTypes
,
EventPayload
}
from
'
lib/mixpanel/index
'
;
import
type
{
EventTypes
,
EventPayload
}
from
'
lib/mixpanel/index
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
import
Stars
from
'
./Stars
'
;
import
Stars
from
'
./Stars
'
;
import
type
{
RateFunction
}
from
'
./useRatings
'
;
const
ratingDescriptions
=
[
'
Terrible
'
,
'
Poor
'
,
'
Average
'
,
'
Very good
'
,
'
Outstanding
'
];
const
ratingDescriptions
=
[
'
Terrible
'
,
'
Poor
'
,
'
Average
'
,
'
Very good
'
,
'
Outstanding
'
];
type
Props
=
{
type
Props
=
{
appId
:
string
;
appId
:
string
;
r
ecordId
?:
str
ing
;
r
ating
?:
AppRat
ing
;
userRating
:
number
|
undefined
;
userRating
?:
AppRating
;
rate
:
(
appId
:
string
,
recordId
:
string
|
undefined
,
rating
:
number
,
source
:
EventPayload
<
EventTypes
.
APP_FEEDBACK
>
[
'
Source
'
])
=>
void
;
rate
:
RateFunction
;
isSending
?:
boolean
;
isSending
?:
boolean
;
source
:
EventPayload
<
EventTypes
.
APP_FEEDBACK
>
[
'
Source
'
];
source
:
EventPayload
<
EventTypes
.
APP_FEEDBACK
>
[
'
Source
'
];
};
};
const
PopoverContent
=
({
appId
,
r
ecordId
,
userRating
,
rate
,
isSending
,
source
}:
Props
)
=>
{
const
PopoverContent
=
({
appId
,
r
ating
,
userRating
,
rate
,
isSending
,
source
}:
Props
)
=>
{
const
[
hovered
,
setHovered
]
=
React
.
useState
(
-
1
);
const
[
hovered
,
setHovered
]
=
React
.
useState
(
-
1
);
const
[
selected
,
setSelected
]
=
React
.
useState
(
-
1
);
const
filledIndex
=
React
.
useMemo
(()
=>
{
if
(
hovered
>=
0
)
{
return
hovered
;
}
return
userRating
?.
value
?
userRating
?.
value
-
1
:
-
1
;
},
[
userRating
,
hovered
]);
const
handleMouseOverFactory
=
React
.
useCallback
((
index
:
number
)
=>
()
=>
{
const
handleMouseOverFactory
=
React
.
useCallback
((
index
:
number
)
=>
()
=>
{
setHovered
(
index
);
setHovered
(
index
);
},
[]);
},
[]);
const
handleSelectFactory
=
React
.
useCallback
((
index
:
number
)
=>
()
=>
{
setSelected
(
index
);
},
[]);
const
handleMouseOut
=
React
.
useCallback
(()
=>
{
const
handleMouseOut
=
React
.
useCallback
(()
=>
{
setHovered
(
-
1
);
setHovered
(
-
1
);
},
[]);
},
[]);
const
handleRate
=
React
.
useCallback
(()
=>
{
const
handleRateFactory
=
React
.
useCallback
((
index
:
number
)
=>
()
=>
{
if
(
selected
<
0
)
{
rate
(
appId
,
rating
?.
recordId
,
userRating
?.
recordId
,
index
+
1
,
source
);
return
;
},
[
appId
,
rating
,
rate
,
userRating
,
source
]);
}
rate
(
appId
,
recordId
,
selected
+
1
,
source
);
},
[
appId
,
recordId
,
selected
,
rate
,
source
]);
if
(
userRating
)
{
return
(
<>
<
Flex
alignItems=
"center"
>
<
IconSvg
name=
"verified"
color=
"green.400"
boxSize=
"30px"
mr=
{
1
}
ml=
"-5px"
/>
<
Text
fontWeight=
"500"
fontSize=
"xs"
lineHeight=
"30px"
variant=
"secondary"
>
App is already rated by you
</
Text
>
</
Flex
>
<
Flex
alignItems=
"center"
h=
"32px"
>
<
IconSvg
name=
"star_filled"
color=
"yellow.400"
boxSize=
{
5
}
mr=
{
1
}
/>
<
Text
fontSize=
"md"
fontWeight=
"500"
mr=
{
3
}
>
{
userRating
.
toFixed
(
1
)
}
</
Text
>
<
Text
fontSize=
"md"
>
{
mdash
}
{
ratingDescriptions
[
userRating
-
1
]
}
</
Text
>
</
Flex
>
</>
);
}
if
(
isSending
)
{
if
(
isSending
)
{
return
(
return
(
...
@@ -85,25 +53,27 @@ const PopoverContent = ({ appId, recordId, userRating, rate, isSending, source }
...
@@ -85,25 +53,27 @@ const PopoverContent = ({ appId, recordId, userRating, rate, isSending, source }
return
(
return
(
<>
<>
<
Text
fontWeight=
"500"
fontSize=
"xs"
lineHeight=
"30px"
variant=
"secondary"
>
<
Flex
alignItems=
"center"
>
How was your experience?
{
userRating
&&
(
</
Text
>
<
IconSvg
name=
"verified"
color=
"green.400"
boxSize=
"30px"
mr=
{
1
}
ml=
"-5px"
/>
)
}
<
Text
fontWeight=
"500"
fontSize=
"xs"
lineHeight=
"30px"
variant=
"secondary"
>
{
userRating
?
'
App is already rated by you
'
:
'
How was your experience?
'
}
</
Text
>
</
Flex
>
<
Flex
alignItems=
"center"
h=
"32px"
>
<
Flex
alignItems=
"center"
h=
"32px"
>
<
Stars
<
Stars
filledIndex=
{
hovered
>=
0
?
hovered
:
selected
}
filledIndex=
{
filledIndex
}
onMouseOverFactory=
{
handleMouseOverFactory
}
onMouseOverFactory=
{
handleMouseOverFactory
}
onMouseOut=
{
handleMouseOut
}
onMouseOut=
{
handleMouseOut
}
onClickFactory=
{
handle
Select
Factory
}
onClickFactory=
{
handle
Rate
Factory
}
/>
/>
{
(
hovered
>=
0
||
selected
>=
0
)
&&
(
{
(
filledIndex
>=
0
)
&&
(
<
Text
fontSize=
"md"
ml=
{
2
}
>
<
Text
fontSize=
"md"
ml=
{
2
}
>
{
ratingDescriptions
[
hovered
>=
0
?
hovered
:
selected
]
}
{
ratingDescriptions
[
filledIndex
]
}
</
Text
>
</
Text
>
)
}
)
}
</
Flex
>
</
Flex
>
<
Button
size=
"sm"
px=
{
4
}
mt=
{
3
}
onClick=
{
handleRate
}
isDisabled=
{
selected
<
0
}
>
Rate it
</
Button
>
</>
</>
);
);
};
};
...
...
ui/marketplace/Rating/Rating.tsx
View file @
490d61b0
import
{
Text
,
PopoverTrigger
,
PopoverBody
,
PopoverContent
,
useDisclosure
,
Skeleton
,
useOutsideClick
,
Box
}
from
'
@chakra-ui/react
'
;
import
{
Text
,
PopoverTrigger
,
PopoverBody
,
PopoverContent
,
useDisclosure
,
Skeleton
,
useOutsideClick
,
Box
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
React
from
'
react
'
;
import
type
{
AppRating
}
from
'
types/client/marketplace
'
;
import
config
from
'
configs/app
'
;
import
config
from
'
configs/app
'
;
import
type
{
EventTypes
,
EventPayload
}
from
'
lib/mixpanel/index
'
;
import
type
{
EventTypes
,
EventPayload
}
from
'
lib/mixpanel/index
'
;
import
Popover
from
'
ui/shared/chakra/Popover
'
;
import
Popover
from
'
ui/shared/chakra/Popover
'
;
...
@@ -8,16 +10,16 @@ import Popover from 'ui/shared/chakra/Popover';
...
@@ -8,16 +10,16 @@ import Popover from 'ui/shared/chakra/Popover';
import
Content
from
'
./PopoverContent
'
;
import
Content
from
'
./PopoverContent
'
;
import
Stars
from
'
./Stars
'
;
import
Stars
from
'
./Stars
'
;
import
TriggerButton
from
'
./TriggerButton
'
;
import
TriggerButton
from
'
./TriggerButton
'
;
import
type
{
RateFunction
}
from
'
./useRatings
'
;
const
feature
=
config
.
features
.
marketplace
;
const
feature
=
config
.
features
.
marketplace
;
const
isEnabled
=
feature
.
isEnabled
&&
feature
.
rating
;
const
isEnabled
=
feature
.
isEnabled
&&
feature
.
rating
;
type
Props
=
{
type
Props
=
{
appId
:
string
;
appId
:
string
;
rating
?:
number
;
rating
?:
AppRating
;
recordId
?:
string
;
userRating
?:
AppRating
;
userRating
:
number
|
undefined
;
rate
:
RateFunction
;
rate
:
(
appId
:
string
,
recordId
:
string
|
undefined
,
rating
:
number
,
source
:
EventPayload
<
EventTypes
.
APP_FEEDBACK
>
[
'
Source
'
])
=>
void
;
isSending
?:
boolean
;
isSending
?:
boolean
;
isLoading
?:
boolean
;
isLoading
?:
boolean
;
fullView
?:
boolean
;
fullView
?:
boolean
;
...
@@ -26,7 +28,7 @@ type Props = {
...
@@ -26,7 +28,7 @@ type Props = {
};
};
const
Rating
=
({
const
Rating
=
({
appId
,
rating
,
recordId
,
userRating
,
rate
,
appId
,
rating
,
userRating
,
rate
,
isSending
,
isLoading
,
fullView
,
canRate
,
source
,
isSending
,
isLoading
,
fullView
,
canRate
,
source
,
}:
Props
)
=>
{
}:
Props
)
=>
{
const
{
isOpen
,
onToggle
,
onClose
}
=
useDisclosure
();
const
{
isOpen
,
onToggle
,
onClose
}
=
useDisclosure
();
...
@@ -47,8 +49,8 @@ const Rating = ({
...
@@ -47,8 +49,8 @@ const Rating = ({
>
>
{
fullView
&&
(
{
fullView
&&
(
<>
<>
<
Stars
filledIndex=
{
(
rating
||
0
)
-
1
}
/>
<
Stars
filledIndex=
{
(
rating
?.
value
||
0
)
-
1
}
/>
<
Text
fontSize=
"md"
ml=
{
1
}
>
{
rating
}
</
Text
>
<
Text
fontSize=
"md"
ml=
{
1
}
>
{
rating
?.
value
}
</
Text
>
</>
</>
)
}
)
}
<
Box
ref=
{
popoverRef
}
>
<
Box
ref=
{
popoverRef
}
>
...
@@ -66,7 +68,7 @@ const Rating = ({
...
@@ -66,7 +68,7 @@ const Rating = ({
<
PopoverBody
p=
{
4
}
>
<
PopoverBody
p=
{
4
}
>
<
Content
<
Content
appId=
{
appId
}
appId=
{
appId
}
r
ecordId=
{
recordId
}
r
ating=
{
rating
}
userRating=
{
userRating
}
userRating=
{
userRating
}
rate=
{
rate
}
rate=
{
rate
}
isSending=
{
isSending
}
isSending=
{
isSending
}
...
...
ui/marketplace/Rating/TriggerButton.tsx
View file @
490d61b0
import
{
Button
,
chakra
,
useColorModeValue
,
Tooltip
,
useDisclosure
}
from
'
@chakra-ui/react
'
;
import
{
Button
,
chakra
,
useColorModeValue
,
Tooltip
,
useDisclosure
}
from
'
@chakra-ui/react
'
;
import
React
from
'
react
'
;
import
React
from
'
react
'
;
import
type
{
AppRating
}
from
'
types/client/marketplace
'
;
import
useIsMobile
from
'
lib/hooks/useIsMobile
'
;
import
useIsMobile
from
'
lib/hooks/useIsMobile
'
;
import
usePreventFocusAfterModalClosing
from
'
lib/hooks/usePreventFocusAfterModalClosing
'
;
import
usePreventFocusAfterModalClosing
from
'
lib/hooks/usePreventFocusAfterModalClosing
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
import
IconSvg
from
'
ui/shared/IconSvg
'
;
type
Props
=
{
type
Props
=
{
rating
?:
number
;
rating
?:
AppRating
;
fullView
?:
boolean
;
fullView
?:
boolean
;
isActive
:
boolean
;
isActive
:
boolean
;
onClick
:
()
=>
void
;
onClick
:
()
=>
void
;
...
@@ -76,7 +78,7 @@ const TriggerButton = (
...
@@ -76,7 +78,7 @@ const TriggerButton = (
)
}
)
}
{
(
rating
&&
!
fullView
)
?
(
{
(
rating
&&
!
fullView
)
?
(
<
chakra
.
span
color=
{
textColor
}
transition=
"inherit"
>
<
chakra
.
span
color=
{
textColor
}
transition=
"inherit"
>
{
rating
}
{
rating
.
value
}
</
chakra
.
span
>
</
chakra
.
span
>
)
:
(
)
:
(
'
Rate it!
'
'
Rate it!
'
...
...
ui/marketplace/Rating/useRatings.tsx
View file @
490d61b0
...
@@ -2,7 +2,7 @@ import Airtable from 'airtable';
...
@@ -2,7 +2,7 @@ import Airtable from 'airtable';
import
{
useEffect
,
useState
,
useCallback
}
from
'
react
'
;
import
{
useEffect
,
useState
,
useCallback
}
from
'
react
'
;
import
{
useAccount
}
from
'
wagmi
'
;
import
{
useAccount
}
from
'
wagmi
'
;
import
type
{
UserRatings
,
AppRatings
}
from
'
types/client/marketplace
'
;
import
type
{
AppRating
}
from
'
types/client/marketplace
'
;
import
config
from
'
configs/app
'
;
import
config
from
'
configs/app
'
;
import
useApiQuery
from
'
lib/api/useApiQuery
'
;
import
useApiQuery
from
'
lib/api/useApiQuery
'
;
...
@@ -16,6 +16,28 @@ const base = (feature.isEnabled && feature.rating) ?
...
@@ -16,6 +16,28 @@ const base = (feature.isEnabled && feature.rating) ?
new
Airtable
({
apiKey
:
feature
.
rating
.
airtableApiKey
}).
base
(
feature
.
rating
.
airtableBaseId
)
:
new
Airtable
({
apiKey
:
feature
.
rating
.
airtableApiKey
}).
base
(
feature
.
rating
.
airtableBaseId
)
:
undefined
;
undefined
;
export
type
RateFunction
=
(
appId
:
string
,
appRecordId
:
string
|
undefined
,
userRecordId
:
string
|
undefined
,
rating
:
number
,
source
:
EventPayload
<
EventTypes
.
APP_FEEDBACK
>
[
'
Source
'
],
)
=>
void
;
function
formatRatings
(
data
:
Airtable
.
Records
<
Airtable
.
FieldSet
>
)
{
return
data
.
reduce
((
acc
:
Record
<
string
,
AppRating
>
,
record
)
=>
{
const
fields
=
record
.
fields
as
{
appId
:
string
;
rating
:
number
};
if
(
!
fields
.
appId
||
typeof
fields
.
rating
!==
'
number
'
)
{
return
acc
;
}
acc
[
fields
.
appId
]
=
{
recordId
:
record
.
id
,
value
:
fields
.
rating
,
};
return
acc
;
},
{});
}
export
default
function
useRatings
()
{
export
default
function
useRatings
()
{
const
{
address
}
=
useAccount
();
const
{
address
}
=
useAccount
();
const
toast
=
useToast
();
const
toast
=
useToast
();
...
@@ -29,8 +51,8 @@ export default function useRatings() {
...
@@ -29,8 +51,8 @@ export default function useRatings() {
},
},
});
});
const
[
ratings
,
setRatings
]
=
useState
<
AppRatings
>
({});
const
[
ratings
,
setRatings
]
=
useState
<
Record
<
string
,
AppRating
>
>
({});
const
[
userRatings
,
setUserRatings
]
=
useState
<
UserRatings
>
({});
const
[
userRatings
,
setUserRatings
]
=
useState
<
Record
<
string
,
AppRating
>
>
({});
const
[
isRatingLoading
,
setIsRatingLoading
]
=
useState
<
boolean
>
(
false
);
const
[
isRatingLoading
,
setIsRatingLoading
]
=
useState
<
boolean
>
(
false
);
const
[
isUserRatingLoading
,
setIsUserRatingLoading
]
=
useState
<
boolean
>
(
false
);
const
[
isUserRatingLoading
,
setIsUserRatingLoading
]
=
useState
<
boolean
>
(
false
);
const
[
isSending
,
setIsSending
]
=
useState
<
boolean
>
(
false
);
const
[
isSending
,
setIsSending
]
=
useState
<
boolean
>
(
false
);
...
@@ -41,14 +63,7 @@ export default function useRatings() {
...
@@ -41,14 +63,7 @@ export default function useRatings() {
return
;
return
;
}
}
const
data
=
await
base
(
'
apps_ratings
'
).
select
({
fields
:
[
'
appId
'
,
'
rating
'
]
}).
all
();
const
data
=
await
base
(
'
apps_ratings
'
).
select
({
fields
:
[
'
appId
'
,
'
rating
'
]
}).
all
();
const
ratings
=
data
.
reduce
((
acc
:
AppRatings
,
record
)
=>
{
const
ratings
=
formatRatings
(
data
);
const
fields
=
record
.
fields
as
{
appId
:
string
;
rating
:
number
};
acc
[
fields
.
appId
]
=
{
recordId
:
record
.
id
,
rating
:
fields
.
rating
,
};
return
acc
;
},
{});
setRatings
(
ratings
);
setRatings
(
ratings
);
},
[]);
},
[]);
...
@@ -64,20 +79,13 @@ export default function useRatings() {
...
@@ -64,20 +79,13 @@ export default function useRatings() {
useEffect
(()
=>
{
useEffect
(()
=>
{
async
function
fetchUserRatings
()
{
async
function
fetchUserRatings
()
{
setIsUserRatingLoading
(
true
);
setIsUserRatingLoading
(
true
);
let
userRatings
=
{}
as
UserRatings
;
let
userRatings
=
{}
as
Record
<
string
,
AppRating
>
;
if
(
address
&&
base
)
{
if
(
address
&&
base
)
{
const
data
=
await
base
(
'
users_ratings
'
).
select
({
const
data
=
await
base
(
'
users_ratings
'
).
select
({
filterByFormula
:
`address = "
${
address
}
"`
,
filterByFormula
:
`address = "
${
address
}
"`
,
fields
:
[
'
appId
'
,
'
rating
'
],
fields
:
[
'
appId
'
,
'
rating
'
],
}).
all
();
}).
all
();
userRatings
=
data
.
reduce
((
acc
:
UserRatings
,
record
)
=>
{
userRatings
=
formatRatings
(
data
);
const
fields
=
record
.
fields
as
{
appId
:
string
;
rating
:
number
};
if
(
!
fields
.
appId
||
typeof
fields
.
rating
!==
'
number
'
)
{
return
acc
;
}
acc
[
fields
.
appId
]
=
fields
.
rating
;
return
acc
;
},
{});
}
}
setUserRatings
(
userRatings
);
setUserRatings
(
userRatings
);
setIsUserRatingLoading
(
false
);
setIsUserRatingLoading
(
false
);
...
@@ -93,16 +101,18 @@ export default function useRatings() {
...
@@ -93,16 +101,18 @@ export default function useRatings() {
const
rateApp
=
useCallback
(
async
(
const
rateApp
=
useCallback
(
async
(
appId
:
string
,
appId
:
string
,
recordId
:
string
|
undefined
,
appRecordId
:
string
|
undefined
,
userRecordId
:
string
|
undefined
,
rating
:
number
,
rating
:
number
,
source
:
EventPayload
<
EventTypes
.
APP_FEEDBACK
>
[
'
Source
'
],
source
:
EventPayload
<
EventTypes
.
APP_FEEDBACK
>
[
'
Source
'
],
)
=>
{
)
=>
{
setIsSending
(
true
);
setIsSending
(
true
);
try
{
try
{
if
(
!
address
||
!
base
)
{
if
(
!
address
||
!
base
)
{
throw
new
Error
(
'
Address is missing
'
);
throw
new
Error
(
'
Address is missing
'
);
}
}
let
appRecordId
=
recordId
;
if
(
!
appRecordId
)
{
if
(
!
appRecordId
)
{
const
records
=
await
base
(
'
apps_ratings
'
).
create
([
{
fields
:
{
appId
}
}
]);
const
records
=
await
base
(
'
apps_ratings
'
).
create
([
{
fields
:
{
appId
}
}
]);
appRecordId
=
records
[
0
].
id
;
appRecordId
=
records
[
0
].
id
;
...
@@ -110,22 +120,36 @@ export default function useRatings() {
...
@@ -110,22 +120,36 @@ export default function useRatings() {
throw
new
Error
(
'
Record ID is missing
'
);
throw
new
Error
(
'
Record ID is missing
'
);
}
}
}
}
await
base
(
'
users_ratings
'
).
create
([
{
if
(
!
userRecordId
)
{
fields
:
{
const
userRecords
=
await
base
(
'
users_ratings
'
).
create
([
address
,
{
appRecordId
:
[
appRecordId
],
fields
:
{
rating
,
address
,
appRecordId
:
[
appRecordId
],
rating
,
},
},
},
]);
userRecordId
=
userRecords
[
0
].
id
;
}
else
{
await
base
(
'
users_ratings
'
).
update
(
userRecordId
,
{
rating
});
}
setUserRatings
({
...
userRatings
,
[
appId
]:
{
recordId
:
userRecordId
,
value
:
rating
,
},
},
]);
});
setUserRatings
({
...
userRatings
,
[
appId
]:
rating
});
fetchRatings
();
toast
({
toast
({
status
:
'
success
'
,
status
:
'
success
'
,
title
:
'
Awesome! Thank you 💜
'
,
title
:
'
Awesome! Thank you 💜
'
,
description
:
'
Your rating improves the service
'
,
description
:
'
Your rating improves the service
'
,
});
});
fetchRatings
();
mixpanel
.
logEvent
(
mixpanel
.
logEvent
(
mixpanel
.
EventTypes
.
APP_FEEDBACK
,
mixpanel
.
EventTypes
.
APP_FEEDBACK
,
{
Action
:
'
Rating
'
,
Source
:
source
,
AppId
:
appId
,
Score
:
rating
},
{
Action
:
'
Rating
'
,
Source
:
source
,
AppId
:
appId
,
Score
:
rating
},
...
@@ -137,6 +161,7 @@ export default function useRatings() {
...
@@ -137,6 +161,7 @@ export default function useRatings() {
description
:
'
Please try again later
'
,
description
:
'
Please try again later
'
,
});
});
}
}
setIsSending
(
false
);
setIsSending
(
false
);
},
[
address
,
userRatings
,
fetchRatings
,
toast
]);
},
[
address
,
userRatings
,
fetchRatings
,
toast
]);
...
...
ui/marketplace/useMarketplaceApps.tsx
View file @
490d61b0
...
@@ -97,8 +97,7 @@ export default function useMarketplaceApps(
...
@@ -97,8 +97,7 @@ export default function useMarketplaceApps(
data
?.
map
((
app
)
=>
({
data
?.
map
((
app
)
=>
({
...
app
,
...
app
,
securityReport
:
securityReports
?.[
app
.
id
],
securityReport
:
securityReports
?.[
app
.
id
],
rating
:
ratings
?.[
app
.
id
]?.
rating
,
rating
:
ratings
?.[
app
.
id
],
ratingRecordId
:
ratings
?.[
app
.
id
]?.
recordId
,
})),
})),
[
data
,
securityReports
,
ratings
]);
[
data
,
securityReports
,
ratings
]);
...
@@ -110,7 +109,7 @@ export default function useMarketplaceApps(
...
@@ -110,7 +109,7 @@ export default function useMarketplaceApps(
return
(
b
.
securityReport
?.
overallInfo
.
securityScore
||
0
)
-
(
a
.
securityReport
?.
overallInfo
.
securityScore
||
0
);
return
(
b
.
securityReport
?.
overallInfo
.
securityScore
||
0
)
-
(
a
.
securityReport
?.
overallInfo
.
securityScore
||
0
);
}
}
if
(
sorting
===
'
rating
'
)
{
if
(
sorting
===
'
rating
'
)
{
return
(
b
.
rating
||
0
)
-
(
a
.
rating
||
0
);
return
(
b
.
rating
?.
value
||
0
)
-
(
a
.
rating
?.
value
||
0
);
}
}
return
0
;
return
0
;
})
||
[];
})
||
[];
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment