Skip to content

Commit

Permalink
dynamic reward setup flow from role editor (#1222)
Browse files Browse the repository at this point in the history
* feat(AddRequirement): support for displaying provided dynamic value during setup

* feat(DynamicRewardModal): support value provider requirements

* extract & dynamic import DataProviderRequirement from new component

* feat: dynamic setup button & base value selector WIP

* feat: BaseValueModal with Add requirement btn

* refactor(AddRoleRewardModal): WIP

* feat(DynamicRewardModal): reward amount calculation breakdown (#1213)

* feat(DynamicRewardModal): reward amount calculation breakdown

* feat(PointsReward): calculate dynamic reward amount

* fix(PointsReward): add optional chaining

* refactor(PointsReward): batch score calculation into memo fn with early return

* fix(PointsReward): "some" fallback if the role is not held

* cleanup: remove useMembership changes, use existing prop

* abstract into useDynamicRewardUserAmount hook

* DynamicRewardCalculationTable: add not connected fallback

* UI: move DynamicTag to same row if there's enough space & modal info footer whitespace

* fix(CalculationTable): handle fallback if connected but there's no provided value

---------

Co-authored-by: valid <valid@zgen.hu>

* feat: warning on linked requirement delete (#1214)

* feat: warning on linked requirement delete

* refactor: extract ExistingRequirementDeleteAlert & useHasLinkedReward

---------

Co-authored-by: valid <valid@zgen.hu>

* feat: submit dynamic point reward

* fix: show images on conversion step & minor fixes

* feat: EditDynamicRewardModal

* fix: display conversion value flooring warning message only when necessary

* fix: round down point amount

* feat(InformationModal): info modal in dynamic reward setup

* fix(InformationModal): light mode for illustration

* fix: lint error

* refactor: use AddRewardContext instead of jotai atom for target role

* dynamic points setup into SetPointsAmount

* UI: whitespace impros & shadow to border

* cleanup(AddPointsPanel): variables order

* UI(DynamicRewardModal): footerBg impros

* fix(AddPointsPanel): disable continue btn if baseValue is not set

* fix: minor copy change

* fix(BaseValueModal): better copy about base values provided by reqs

* pass targetRoleId to provider as prop, adjust disabled tooltip copy

* feat(RolePlatforms): display dynamic tag on edit role panel

---------

Co-authored-by: valid <valid@zgen.hu>
  • Loading branch information
FBalint and dovalid authored May 23, 2024
1 parent 4397a54 commit 7f21a62
Show file tree
Hide file tree
Showing 41 changed files with 1,729 additions and 301 deletions.
165 changes: 165 additions & 0 deletions public/img/dynamic_illustration.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
165 changes: 165 additions & 0 deletions public/img/dynamic_illustration_light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions src/components/[guild]/AccessHub/hooks/useEditRolePlatform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ const useEditRolePlatform = ({
return {
...role,
rolePlatforms: role.rolePlatforms.map((rp) => {
if (rp.id !== rolePlatformId) return rp
return response
if (rp.id !== rolePlatformId) return { ...rp, roleId: role.id }
return { ...response, roleId: role.id }
}),
}
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,8 @@ const useSubmitAddReward = () => {
// We'll be able to send additional params here, like capacity & time
roleId: +roleId,
/**
* Temporary for POINTS rewards, because they can be added to
* multiple roles and this field has a unique constraint in
* the DB
* Temporary for POINTS rewards, because they can be added to multiple
* roles and this field has a unique constraint in the DB
*/
platformRoleId: roleId,
...data.rolePlatforms[0],
Expand Down
7 changes: 6 additions & 1 deletion src/components/[guild]/AddRewardContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const AddRewardContext = createContext<{
setSelection: (newSelection: PlatformName) => void
step: string
setStep: (newStep: string) => void
targetRoleId?: number
activeTab: RoleTypeToAddTo
setActiveTab: Dispatch<SetStateAction<RoleTypeToAddTo>>
shouldShowCloseAlert: boolean
Expand All @@ -35,7 +36,10 @@ const AddRewardContext = createContext<{
setIsBackButtonDisabled: Dispatch<SetStateAction<boolean>>
}>(undefined)

const AddRewardProvider = ({ children }: PropsWithChildren<unknown>) => {
const AddRewardProvider = ({
targetRoleId,
children,
}: PropsWithChildren<{ targetRoleId?: number }>) => {
const modalRef = useRef(null)
const { isOpen, onOpen, onClose } = useDisclosure()
const scrollToTop = () => modalRef.current?.scrollTo({ top: 0 })
Expand Down Expand Up @@ -92,6 +96,7 @@ const AddRewardProvider = ({ children }: PropsWithChildren<unknown>) => {
setSelection,
step,
setStep,
targetRoleId,
activeTab,
setActiveTab,
shouldShowCloseAlert,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Box, Circle, Flex, HStack, Icon, SimpleGrid, Text } from "@chakra-ui/react"
import { Lightning } from "phosphor-react"
import { REQUIREMENT_PROVIDED_VALUES } from "requirements/requirements"
import { RequirementProps } from "./Requirement"
import { useRequirementContext } from "./RequirementContext"
import { RequirementImage, RequirementImageCircle } from "./RequirementImage"

const DataProviderRequirement = ({
image,
isImageLoading,
children,
rightElement,
}: RequirementProps): JSX.Element => {
const requirement = useRequirementContext()

const ProvidedValueDisplay = REQUIREMENT_PROVIDED_VALUES[requirement?.type]

return (
<SimpleGrid
spacing={4}
w="full"
py={2}
templateColumns={`auto 1fr ${rightElement ? "auto" : ""}`}
alignItems="center"
>
<Box mt="3px" alignSelf={"start"} position={"relative"}>
<RequirementImageCircle isImageLoading={isImageLoading}>
<RequirementImage image={requirement?.data?.customImage || image} />
</RequirementImageCircle>

<Circle
position="absolute"
right={-1}
bottom={0}
bgColor={"white"}
size={5}
overflow="hidden"
>
<Icon boxSize={3} as={Lightning} weight="fill" color="green.500" />
</Circle>
</Box>

<Flex alignSelf={"center"} flexDir={"column"} justifyContent={"center"} ml={1}>
{ProvidedValueDisplay && <ProvidedValueDisplay requirement={requirement} />}

<HStack gap={1} alignItems={"center"}>
<Text fontSize={"sm"} color={"GrayText"}>
Via:{" "}
</Text>
<Text
fontWeight={"medium"}
sx={{ fontSize: "sm", "& *": { fontSize: "inherit" } }}
>
{requirement?.data?.customName || children}
</Text>
</HStack>
</Flex>
{rightElement}
</SimpleGrid>
)
}

export default DataProviderRequirement
11 changes: 11 additions & 0 deletions src/components/[guild]/Requirements/components/Requirement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import {
VStack,
} from "@chakra-ui/react"
import Visibility from "components/[guild]/Visibility"
import dynamic from "next/dynamic"
import React, { ComponentType, PropsWithChildren } from "react"
import { useFormContext } from "react-hook-form"
import { Visibility as VisibilityType } from "types"
import { useRequirementContext } from "./RequirementContext"
import { RequirementImage, RequirementImageCircle } from "./RequirementImage"
import ResetRequirementButton from "./ResetRequirementButton"
import ViewOriginalPopover from "./ViewOriginalPopover"
const DataProviderRequirement = dynamic(() => import("./DataProviderRequirement"))

export type RequirementProps = PropsWithChildren<{
isImageLoading?: boolean
Expand All @@ -25,6 +27,7 @@ export type RequirementProps = PropsWithChildren<{
imageWrapper?: ComponentType<unknown>
childrenWrapper?: ComponentType<unknown>
showViewOriginal?: boolean
dynamicDisplay?: boolean
}>

const Requirement = ({
Expand All @@ -36,10 +39,18 @@ const Requirement = ({
imageWrapper,
childrenWrapper,
showViewOriginal,
dynamicDisplay,
}: RequirementProps): JSX.Element => {
const requirement = useRequirementContext()
const { setValue } = useFormContext() ?? {}

if (dynamicDisplay)
return (
<DataProviderRequirement
{...{ isImageLoading, image, rightElement, children }}
/>
)

const ChildrenWrapper = childrenWrapper ?? Box
const ImageWrapper = imageWrapper ?? React.Fragment

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Icon, Tag, Tooltip, useDisclosure } from "@chakra-ui/react"
import useGuildPermission from "components/[guild]/hooks/useGuildPermission"
import useRequirements from "components/[guild]/hooks/useRequirements"
import { Lightning, Warning } from "phosphor-react"
import DynamicRewardModal from "platforms/Token/DynamicRewardModal"
import { Rest, RolePlatform } from "types"

const DynamicTag = ({
rolePlatform,
...rest
}: { rolePlatform: RolePlatform } & Rest) => {
const { isAdmin } = useGuildPermission()

const { onOpen, isOpen, onClose } = useDisclosure()

const { data: requirements } = useRequirements(rolePlatform.roleId)

const dynamicAmount: any = rolePlatform.dynamicAmount
const requirementId =
dynamicAmount?.operation?.input?.[0]?.requirementId ||
dynamicAmount?.operation?.input?.requirementId

const linkedRequirement = requirements?.find((req) => req.id === requirementId)

return (
<>
<Tooltip label="Show details" hasArrow>
<Tag
fontWeight="semibold"
_hover={{ cursor: "pointer" }}
onClick={onOpen}
w="fit-content"
{...rest}
>
<Icon
boxSize={"13px"}
weight="fill"
color="green.500"
as={Lightning}
mr={1}
/>
Dynamic
</Tag>
</Tooltip>

{isAdmin && !linkedRequirement && (
<Tooltip
hasArrow
label="Dynamic rewards need a base value for reward amount calculation from a requirement. Edit the reward to set one!"
>
<Tag colorScheme={"orange"} w="fit-content">
<Icon as={Warning} mr={1} /> Missing linked requirement!
</Tag>
</Tooltip>
)}

<DynamicRewardModal
onClose={onClose}
isOpen={isOpen}
rolePlatform={rolePlatform}
linkedRequirement={linkedRequirement}
></DynamicRewardModal>
</>
)
}

export default DynamicTag
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ const useEditRole = (roleId: number, onSuccess?: () => void) => {
const updatedRolePlatformsById = mapToObject(successfulRolePlatformUpdates)

const createdRolePlatformsToMutate = successfulRolePlatformCreations.map(
({ createdGuildPlatform: _, ...rest }) => rest
({ createdGuildPlatform: _, ...rest }) => ({ roleId: roleId, ...rest })
)

const createdGuildPlatforms = successfulRolePlatformCreations
Expand Down
5 changes: 3 additions & 2 deletions src/components/[guild]/RoleCard/components/Reward.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Stack,
Text,
Tooltip,
Wrap,
useColorModeValue,
} from "@chakra-ui/react"
import usePlatformAccessButton from "components/[guild]/AccessHub/components/usePlatformAccessButton"
Expand Down Expand Up @@ -170,10 +171,10 @@ const RewardDisplay = ({
{icon}

<Stack w="full" spacing={0.5}>
<HStack spacing={0}>
<Wrap spacingY={0.5}>
<Text maxW="calc(100% - var(--chakra-sizes-12))">{label}</Text>
{rightElement}
</HStack>
</Wrap>

{children}
</Stack>
Expand Down
70 changes: 41 additions & 29 deletions src/components/[guild]/RolePlatforms/RolePlatforms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import {
PlatformName,
PlatformType,
RoleFormType,
RolePlatform,
} from "types"
import AvailabilitySetup from "../AddRewardButton/components/AvailabilitySetup"
import { AddRewardProvider, useAddRewardContext } from "../AddRewardContext"
import DynamicTag from "../RoleCard/components/DynamicReward/DynamicTag"
import SetVisibility from "../SetVisibility"
import useVisibilityModalProps from "../SetVisibility/hooks/useVisibilityModalProps"
import useGuild from "../hooks/useGuild"
Expand Down Expand Up @@ -91,7 +93,7 @@ const RolePlatforms = ({ roleId }: Props) => {
}

const RolePlatformsWrapper = (props: Props): JSX.Element => (
<AddRewardProvider>
<AddRewardProvider targetRoleId={props.roleId}>
<RolePlatforms {...props} />
</AddRewardProvider>
)
Expand Down Expand Up @@ -201,34 +203,44 @@ const RolePlatformCard = ({
)
}
contentRow={
CAPACITY_TIME_PLATFORMS.includes(type) || isLegacyContractCallReward ? (
<AvailabilitySetup
platformType={type}
rolePlatform={rolePlatform}
defaultValues={{
capacity: rolePlatform.capacity,
startTime: rolePlatform.startTime,
endTime: rolePlatform.endTime,
}}
onDone={({ capacity, startTime, endTime }) => {
setValue(`rolePlatforms.${index}.capacity`, capacity, {
shouldDirty: true,
})
setValue(`rolePlatforms.${index}.startTime`, startTime, {
shouldDirty: true,
})
setValue(`rolePlatforms.${index}.endTime`, endTime, {
shouldDirty: true,
})
}}
/>
) : type === "CONTRACT_CALL" ? (
<NftAvailabilityTags
guildPlatform={guildPlatform}
rolePlatform={rolePlatform}
mt={1}
/>
) : null
<>
{CAPACITY_TIME_PLATFORMS.includes(type) || isLegacyContractCallReward ? (
<AvailabilitySetup
platformType={type}
rolePlatform={rolePlatform}
defaultValues={{
capacity: rolePlatform.capacity,
startTime: rolePlatform.startTime,
endTime: rolePlatform.endTime,
}}
onDone={({ capacity, startTime, endTime }) => {
setValue(`rolePlatforms.${index}.capacity`, capacity, {
shouldDirty: true,
})
setValue(`rolePlatforms.${index}.startTime`, startTime, {
shouldDirty: true,
})
setValue(`rolePlatforms.${index}.endTime`, endTime, {
shouldDirty: true,
})
}}
/>
) : type === "CONTRACT_CALL" ? (
<NftAvailabilityTags
guildPlatform={guildPlatform}
rolePlatform={rolePlatform}
mt={1}
/>
) : null}
{!!rolePlatform.dynamicAmount && (
<DynamicTag
rolePlatform={
{ ...rolePlatform, guildPlatform: guildPlatform } as RolePlatform
}
mt={1}
/>
)}
</>
}
/>
</RolePlatformProvider>
Expand Down
Loading

0 comments on commit 7f21a62

Please sign in to comment.