Skip to content

Commit

Permalink
Rewards program invitations (#63)
Browse files Browse the repository at this point in the history
* Update enter program view

* Add invitation form

* Add invitation links view

* Add rewards invitation alias route

* Add invitations stub

* Update program auth

* Update rewards image

* Update colors

* Update invitations flow

* Fix review comments
  • Loading branch information
ardier16 authored Mar 19, 2024
1 parent e22fffb commit 56a34a3
Show file tree
Hide file tree
Showing 39 changed files with 622 additions and 227 deletions.
2 changes: 1 addition & 1 deletion .env-development
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ VITE_MODE=development
VITE_PORT=8000

VITE_APP_NAME=Rarime App
VITE_APP_DOMAIN=https://app.stage.rarime.com
VITE_APP_HOST_URL=https://app.stage.rarime.com
VITE_DEFAULT_CHAIN=SEPOLIA
VITE_API_URL=https://api.orgs.app.stage.rarime.com
2 changes: 1 addition & 1 deletion .env-production
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ VITE_MODE=production
VITE_PORT=8000

VITE_APP_NAME=Rarime App
VITE_APP_DOMAIN=https://app.rarime.com
VITE_APP_HOST_URL=https://app.rarime.com
VITE_DEFAULT_CHAIN=POLYGON
VITE_API_URL=https://api.orgs.app.rarime.com
7 changes: 7 additions & 0 deletions src/api/modules/points/enums/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,10 @@ export enum EventMetadataFrequencies {
Unlimited = 'unlimited',
Custom = 'custom',
}

export enum EventNames {
FreeWeekly = 'free_weekly',
GetPoh = 'get_poh',
RefferalCommon = 'referral_common',
BeReferred = 'be_referred',
}
7 changes: 6 additions & 1 deletion src/api/modules/points/helpers/balance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,12 @@ export const getLeaderboard = async () => {
}

export const getPointsBalance = async (did: string) => {
return api.get<Balance>(`${ApiServicePaths.Points}/v1/public/balances/${did}?rank=true`)
return api.get<Balance>(`${ApiServicePaths.Points}/v1/public/balances/${did}`, {
query: {
rank: true,
referral_codes: true,
},
})
}

// Withdrawals
Expand Down
21 changes: 16 additions & 5 deletions src/api/modules/points/helpers/events.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { JsonApiResponse } from '@distributedlab/jac'

import { api } from '@/api/clients'
import { ApiServicePaths } from '@/enums'
import { identityStore } from '@/store'

import { EventMetadataFrequencies, EventsRequestFilters, EventStatuses } from '../enums'
import { Event, EventsMeta, EventsRequestQueryParams } from '../types/events'
import { EventsMeta, EventsRequestQueryParams, PointsEvent } from '../types/events'

export const EVENTS_MOCK: Event[] = [
export const EVENTS_MOCK: PointsEvent[] = [
{
id: '1',
type: 'event',
Expand Down Expand Up @@ -215,7 +218,7 @@ export const EVENTS_MOCK: Event[] = [

export const getEvents = async (query: EventsRequestQueryParams) => {
const statuses = query.filter?.[EventsRequestFilters.Status]
return api.get<Event[], EventsMeta>(`${ApiServicePaths.Points}/v1/public/events`, {
return api.get<PointsEvent[], EventsMeta>(`${ApiServicePaths.Points}/v1/public/events`, {
query: {
...query,
filter: {
Expand All @@ -229,11 +232,19 @@ export const getEvents = async (query: EventsRequestQueryParams) => {
}

export const getEventById = async (id: string) => {
return api.get<Event>(`${ApiServicePaths.Points}/v1/public/events/${id}`)
// TODO: Uncomment when the endpoint is ready
// return api.get<Event>(`${ApiServicePaths.Points}/v1/public/events/${id}`)
const res = await getEvents({
filter: { [EventsRequestFilters.Did]: identityStore.userDid },
})
return {
...res,
data: res.data.find(event => event.id === id),
} as JsonApiResponse<PointsEvent>
}

export const claimEvent = async (id: string) => {
return api.patch<Event>(`${ApiServicePaths.Points}/v1/public/events/${id}`, {
return api.patch<PointsEvent>(`${ApiServicePaths.Points}/v1/public/events/${id}`, {
body: {
data: {
id,
Expand Down
1 change: 1 addition & 0 deletions src/api/modules/points/types/balance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type Balance = {
is_disabled?: boolean
is_verified?: boolean
rank: number
referral_codes?: string[]
}

export type Withdrawal = {
Expand Down
3 changes: 2 additions & 1 deletion src/api/modules/points/types/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ export type EventMetadata = {
dynamic?: Record<string, unknown>
}

export type Event = {
// We use `PointsEvent` instead of `Event` to avoid name conflict with global `Event` type
export type PointsEvent = {
id: string
type: 'event'
status: EventStatuses
Expand Down
2 changes: 1 addition & 1 deletion src/assets/icons/cardholder-fill-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/assets/icons/copy-simple-icon.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: 4 additions & 0 deletions src/assets/icons/discord-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions src/assets/icons/telegram-icon.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: 4 additions & 0 deletions src/assets/icons/twitter-x-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const SUPPORTED_CHAINS_DETAILS: SupportedChainsDetails = {

export type Config = {
APP_NAME: string
APP_HOST_URL: string
API_URL: string
BUILD_VERSION: string
SUPPORTED_CHAINS_DETAILS: SupportedChainsDetails
Expand All @@ -22,6 +23,7 @@ const FALLBACK_DEFAULT_CHAIN = Object.entries(FALLBACK_SUPPORTED_CHAINS)[0][0]

export const config: Config = {
APP_NAME: import.meta.env.VITE_APP_NAME,
APP_HOST_URL: import.meta.env.VITE_APP_HOST_URL,
API_URL: import.meta.env.VITE_API_URL,
BUILD_VERSION: packageJson.version || import.meta.env.VITE_APP_BUILD_VERSION,
SUPPORTED_CHAINS_DETAILS,
Expand Down
3 changes: 3 additions & 0 deletions src/enums/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export enum Icons {
Check = 'check',
Close = 'close',
CopySimple = 'copy-simple',
Discord = 'discord',
DotsSixVertical = 'dots-six-vertical',
DotsThreeOutline = 'dots-three-outline',
Facebook = 'facebook',
Expand Down Expand Up @@ -75,12 +76,14 @@ export enum Icons {
SuitcaseSimple = 'suitcase-simple',
Sun = 'sun',
Swap = 'swap',
Telegram = 'telegram',
ThumbsDown = 'thumbs-down',
ThumbsUp = 'thumbs-up',
TrashSimple = 'trash-simple',
TrophyFill = 'trophy-fill',
Trophy = 'trophy',
Twitter = 'twitter',
TwitterX = 'twitter-x',
UserCircle = 'user-circle',
UserFocus = 'user-focus',
User = 'user',
Expand Down
2 changes: 2 additions & 0 deletions src/enums/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ export enum RoutePaths {
RewardsEarnHistory = '/rewards/earn-history',
RewardsLeaderboard = '/rewards/leaderboard',
RewardsAbout = '/rewards/about',

RewardsInvitationAlias = '/r/:code',
}
4 changes: 4 additions & 0 deletions src/hooks/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
useForm as useFormHook,
UseFormHandleSubmit,
UseFormRegister,
UseFormSetError,
} from 'react-hook-form'
import * as Yup from 'yup'

Expand All @@ -21,6 +22,7 @@ export type Form<T extends FieldValues> = {
formErrors: FieldErrorsImpl<T>
register: UseFormRegister<T>
handleSubmit: UseFormHandleSubmit<T>
setError: UseFormSetError<T>
control: Control<T>
}

Expand All @@ -35,6 +37,7 @@ export const useForm = <T extends Yup.AnyObjectSchema, R extends FieldValues>(
register,
handleSubmit,
watch,
setError,
formState: { errors },
} = useFormHook<R>({
mode: 'onTouched',
Expand Down Expand Up @@ -66,6 +69,7 @@ export const useForm = <T extends Yup.AnyObjectSchema, R extends FieldValues>(
formErrors: errors,
register,
handleSubmit,
setError,
control,
}
}
4 changes: 2 additions & 2 deletions src/pages/Rewards/pages/EarnHistory/components/EventItem.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Stack, Typography, useTheme } from '@mui/material'

import { Event } from '@/api/modules/points'
import { PointsEvent } from '@/api/modules/points'
import { Icons } from '@/enums'
import { formatDateDMY } from '@/helpers'
import RewardChip from '@/pages/Rewards/components/RewardChip'
import { UiIcon } from '@/ui'

interface Props {
event: Event
event: PointsEvent
}

export default function EventItem({ event }: Props) {
Expand Down
72 changes: 48 additions & 24 deletions src/pages/Rewards/pages/EventId/components/EventView.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { Box, Button, Divider, Stack, Typography, useTheme } from '@mui/material'

import { Event } from '@/api/modules/points'
import { EventNames, PointsEvent } from '@/api/modules/points'
import { MarkdownViewer } from '@/common'
import { Icons } from '@/enums'
import { formatDateTime } from '@/helpers'
import RewardChip from '@/pages/Rewards/components/RewardChip'
import { UiIcon } from '@/ui'

import InvitationLinks from './InvitationLinks'

interface Props {
event: Event
event: PointsEvent
}

export default function EventView({ event }: Props) {
Expand All @@ -27,30 +31,50 @@ export default function EventView({ event }: Props) {
)}
</Stack>
</Stack>
<Box
component='img'
src={event.meta.static.image_url}
sx={{
bgcolor: palette.action.active,
borderRadius: 2,
objectFit: 'cover',
width: spacing(21),
height: spacing(21),
}}
/>
{event.meta.static.image_url ? (
<Box
component='img'
src={event.meta.static.image_url}
sx={{
bgcolor: palette.action.active,
borderRadius: 2,
objectFit: 'cover',
width: spacing(21),
height: spacing(21),
}}
/>
) : (
<Stack
bgcolor={palette.action.active}
color={palette.text.primary}
borderRadius={2}
width={spacing(21)}
height={spacing(21)}
justifyContent='center'
alignItems='center'
>
<UiIcon name={Icons.Gift} size={10} />
</Stack>
)}
</Stack>
<Divider />
<MarkdownViewer>{event?.meta.static.description ?? ''}</MarkdownViewer>
{event.meta.static.action_url && (
<Button
component='a'
href={event.meta.static.action_url}
target='_blank'
rel='noreferrer noopener'
sx={{ width: spacing(40) }}
>
{`Let's Start`}
</Button>
{event.meta.static.name === EventNames.RefferalCommon ? (
<InvitationLinks event={event} />
) : (
<>
<MarkdownViewer>{event?.meta.static.description ?? ''}</MarkdownViewer>
{event.meta.static.action_url && (
<Button
component='a'
href={event.meta.static.action_url}
target='_blank'
rel='noreferrer noopener'
sx={{ width: spacing(40) }}
>
{`Let's Start`}
</Button>
)}
</>
)}
</Stack>
)
Expand Down
59 changes: 59 additions & 0 deletions src/pages/Rewards/pages/EventId/components/InvitationLinks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { CircularProgress, Stack, Typography, useTheme } from '@mui/material'
import { useEffect, useMemo } from 'react'

import { PointsEvent } from '@/api/modules/points'
import { NoDataView } from '@/common'
import { rewardsStore, useRewardsState } from '@/store'

import ReferralLink from './ReferralLink'

interface Props {
event: PointsEvent
}

const MAX_REFERRAL_CODES = 5

export default function InvitationLinks({ event }: Props) {
const { balance } = useRewardsState()
const { palette } = useTheme()

const referralCodes = useMemo(() => {
return balance?.referral_codes ?? []
}, [balance])

useEffect(() => {
rewardsStore.loadBalance()
}, [])

return balance ? (
referralCodes.length ? (
<Stack spacing={5}>
<Stack spacing={2}>
<Typography variant='subtitle3'>
Invited {MAX_REFERRAL_CODES - referralCodes.length}/{MAX_REFERRAL_CODES}
</Typography>
<Typography variant='body3' color={palette.text.secondary}>
STUB: Invite friends and earn RMO
</Typography>
</Stack>
<Stack spacing={2}>
{new Array(MAX_REFERRAL_CODES).fill(null).map((_, index) => (
<ReferralLink
key={index}
index={index}
reward={Math.floor(event.meta.static.reward / MAX_REFERRAL_CODES)}
code={referralCodes[index]}
isUsed={index >= referralCodes.length}
/>
))}
</Stack>
</Stack>
) : (
<NoDataView title='No invitation links' />
)
) : (
<Stack alignItems='center' p={20}>
<CircularProgress color='secondary' />
</Stack>
)
}
Loading

0 comments on commit 56a34a3

Please sign in to comment.