diff --git a/apps/web/src/actions/rewards/claimRewardAction.ts b/apps/web/src/actions/rewards/claimRewardAction.ts new file mode 100644 index 000000000..3e93061b4 --- /dev/null +++ b/apps/web/src/actions/rewards/claimRewardAction.ts @@ -0,0 +1,27 @@ +'use server' + +import { ClaimedReward, RewardType } from '@latitude-data/core/browser' +import { claimReward } from '@latitude-data/core/services/claimedRewards/claim' +import { z } from 'zod' + +import { authProcedure } from '../procedures' + +export const claimRewardAction = authProcedure + .createServerAction() + .input( + z.object({ + type: z.enum(Object.values(RewardType) as [string, ...string[]]), + reference: z.string(), + }), + ) + .handler(async ({ input, ctx }) => { + const workspace = ctx.workspace + const user = ctx.user + const result = await claimReward({ + workspace, + user, + type: input.type as RewardType, + reference: input.reference, + }) + return result.unwrap() as ClaimedReward + }) diff --git a/apps/web/src/actions/rewards/fetchClaimedRewardsAction.ts b/apps/web/src/actions/rewards/fetchClaimedRewardsAction.ts new file mode 100644 index 000000000..c1600475c --- /dev/null +++ b/apps/web/src/actions/rewards/fetchClaimedRewardsAction.ts @@ -0,0 +1,12 @@ +'use server' + +import { authProcedure } from '../procedures' +import { ClaimedRewardsRepository } from '@latitude-data/core/repositories' + +export const fetchClaimedRewardsAction = authProcedure + .createServerAction() + .handler(async ({ ctx }) => { + const claimedRewardsScope = new ClaimedRewardsRepository(ctx.workspace.id) + const result = await claimedRewardsScope.findAllValidOptimistic() + return result.unwrap() + }) diff --git a/apps/web/src/components/layouts/AppLayout/Header/Rewards/Menu/index.tsx b/apps/web/src/components/layouts/AppLayout/Header/Rewards/Menu/index.tsx new file mode 100644 index 000000000..400b027aa --- /dev/null +++ b/apps/web/src/components/layouts/AppLayout/Header/Rewards/Menu/index.tsx @@ -0,0 +1,93 @@ +'use client' + +import { useMemo } from 'react' + +import { + ClaimedReward, + REWARD_VALUES, + RewardType, +} from '@latitude-data/core/browser' +import { Button, cn, Icon, Text } from '@latitude-data/web-ui' +import useRewards from '$/stores/rewards' + +function RewardItem({ + description, + type, + claimedRewards, + isLoading, +}: { + description: string + type: RewardType + claimedRewards: ClaimedReward[] + isLoading: boolean +}) { + const isClaimed = useMemo(() => { + if (isLoading || !claimedRewards) return false + return claimedRewards.some((r) => r.reward_type === type) + }, [isLoading, claimedRewards, type]) + + const runs = useMemo(() => `${REWARD_VALUES[type] / 1000}k`, [type]) + + return ( + + ) +} + +export function RewardsMenu() { + const { data: claimedRewards, isLoading } = useRewards() + + return ( +
+ + + + + +
+ ) +} diff --git a/apps/web/src/components/layouts/AppLayout/Header/Rewards/index.tsx b/apps/web/src/components/layouts/AppLayout/Header/Rewards/index.tsx new file mode 100644 index 000000000..947a37b77 --- /dev/null +++ b/apps/web/src/components/layouts/AppLayout/Header/Rewards/index.tsx @@ -0,0 +1,22 @@ +import { Button } from '@latitude-data/web-ui' +import Popover from 'node_modules/@latitude-data/web-ui/src/ds/atoms/Popover' + +import { RewardsMenu } from './Menu' + +export function RewardsButton() { + return ( + + + + + + + + + ) +} diff --git a/apps/web/src/components/layouts/AppLayout/Header/index.tsx b/apps/web/src/components/layouts/AppLayout/Header/index.tsx index eb2310276..1342f0459 100644 --- a/apps/web/src/components/layouts/AppLayout/Header/index.tsx +++ b/apps/web/src/components/layouts/AppLayout/Header/index.tsx @@ -11,6 +11,7 @@ import { Fragment } from 'react/jsx-runtime' import AvatarDropdown from './AvatarDropdown' import { UsageIndicator } from './UsageIndicator' +import { RewardsButton } from './Rewards' function BreadcrumbSeparator() { return ( @@ -112,6 +113,7 @@ export default function AppHeader({