-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
2,948 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() | ||
}) |
93 changes: 93 additions & 0 deletions
93
apps/web/src/components/layouts/AppLayout/Header/Rewards/Menu/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<Button variant='ghost' className='justify-start hover:bg-muted' fullWidth> | ||
<div className='flex flex-row w-full items-center gap-4 justify-between'> | ||
<div className='flex flex-row items-center gap-2'> | ||
{isLoading ? ( | ||
<Icon | ||
name='loader' | ||
color='foregroundMuted' | ||
className='animate-spin' | ||
/> | ||
) : ( | ||
<Icon | ||
name='check' | ||
color={isClaimed ? 'accent' : 'foregroundMuted'} | ||
className={cn({ 'opacity-50': !isClaimed })} | ||
/> | ||
)} | ||
<Text.H5M>{description}</Text.H5M> | ||
</div> | ||
<Text.H5M color='foregroundMuted'>+{runs} runs</Text.H5M> | ||
</div> | ||
</Button> | ||
) | ||
} | ||
|
||
export function RewardsMenu() { | ||
const { data: claimedRewards, isLoading } = useRewards() | ||
|
||
return ( | ||
<div className='flex flex-col p-4 gap-2'> | ||
<RewardItem | ||
description='Give us a Github star' | ||
type={RewardType.GithubStar} | ||
claimedRewards={claimedRewards} | ||
isLoading={isLoading} | ||
/> | ||
<RewardItem | ||
description='Follow us on X or LinkedIn' | ||
type={RewardType.Follow} | ||
claimedRewards={claimedRewards} | ||
isLoading={isLoading} | ||
/> | ||
<RewardItem | ||
description='Post on X or LinkedIn' | ||
type={RewardType.Post} | ||
claimedRewards={claimedRewards} | ||
isLoading={isLoading} | ||
/> | ||
<RewardItem | ||
description='Invite a company member' | ||
type={RewardType.Invite} | ||
claimedRewards={claimedRewards} | ||
isLoading={isLoading} | ||
/> | ||
<RewardItem | ||
description='Refer Latitude to a friend' | ||
type={RewardType.Referral} | ||
claimedRewards={claimedRewards} | ||
isLoading={isLoading} | ||
/> | ||
</div> | ||
) | ||
} |
22 changes: 22 additions & 0 deletions
22
apps/web/src/components/layouts/AppLayout/Header/Rewards/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<Popover.Root> | ||
<Popover.Trigger asChild> | ||
<Button size='small' >Rewards</Button> | ||
</Popover.Trigger> | ||
<Popover.Content | ||
side='bottom' | ||
sideOffset={8} | ||
align='center' | ||
className='bg-background rounded-md w-fill shadow-lg border border-border' | ||
> | ||
<RewardsMenu /> | ||
</Popover.Content> | ||
</Popover.Root> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import { useCallback } from 'react' | ||
|
||
import { ClaimedReward } from '@latitude-data/core/browser' | ||
import { useToast } from '@latitude-data/web-ui' | ||
import { claimRewardAction } from '$/actions/rewards/claimRewardAction' | ||
import { fetchClaimedRewardsAction } from '$/actions/rewards/fetchClaimedRewardsAction' | ||
import useLatitudeAction from '$/hooks/useLatitudeAction' | ||
import useSWR, { SWRConfiguration } from 'swr' | ||
|
||
import useWorkspaceUsage from './workspaceUsage' | ||
|
||
const EMPTY_ARRAY: ClaimedReward[] = [] | ||
|
||
export default function useRewards(opts?: SWRConfiguration) { | ||
const { mutate: mutateUsage } = useWorkspaceUsage() | ||
|
||
const { toast } = useToast() | ||
const { | ||
mutate, | ||
data = EMPTY_ARRAY, | ||
isLoading, | ||
error: swrError, | ||
} = useSWR<ClaimedReward[]>( | ||
['workspaceClaimedRewards'], | ||
useCallback(async () => { | ||
const [data, error] = await fetchClaimedRewardsAction() | ||
if (error) { | ||
toast({ | ||
title: 'Error', | ||
description: error.message, | ||
variant: 'destructive', | ||
}) | ||
} | ||
if (!data) return EMPTY_ARRAY | ||
|
||
return data | ||
}, []), | ||
opts, | ||
) | ||
|
||
const updateRewards = useCallback( | ||
(newReward: ClaimedReward) => { | ||
mutate( | ||
(prevRewards) => { | ||
if (!prevRewards) return [newReward] | ||
return [...prevRewards, newReward] | ||
}, | ||
{ revalidate: false }, | ||
) | ||
}, | ||
[mutate], | ||
) | ||
|
||
const increaseMaxUsage = useCallback( | ||
(increaseCount: number) => { | ||
mutateUsage( | ||
(prevUsage) => { | ||
if (!prevUsage) return prevUsage | ||
return { | ||
...prevUsage, | ||
max: prevUsage.max + increaseCount, | ||
} | ||
}, | ||
{ revalidate: false }, | ||
) | ||
}, | ||
[mutateUsage], | ||
) | ||
|
||
const { execute: claimReward } = useLatitudeAction(claimRewardAction, { | ||
onSuccess: ({ data: claimedReward }) => { | ||
updateRewards(claimedReward) | ||
increaseMaxUsage(claimedReward.value) | ||
}, | ||
}) | ||
|
||
return { data, isLoading, error: swrError, claimReward } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
DO $$ BEGIN | ||
CREATE TYPE "latitude"."reward_types" AS ENUM('github_star', 'follow', 'post', 'invite', 'referral'); | ||
EXCEPTION | ||
WHEN duplicate_object THEN null; | ||
END $$; | ||
--> statement-breakpoint | ||
CREATE TABLE IF NOT EXISTS "latitude"."claimed_rewards" ( | ||
"id" bigserial PRIMARY KEY NOT NULL, | ||
"workspace_id" bigint NOT NULL, | ||
"creator_id" text, | ||
"reward_type" "latitude"."reward_types" NOT NULL, | ||
"reference" text NOT NULL, | ||
"value" bigint NOT NULL, | ||
"accepted" boolean, | ||
"created_at" timestamp DEFAULT now() NOT NULL, | ||
"updated_at" timestamp DEFAULT now() NOT NULL | ||
); | ||
--> statement-breakpoint | ||
DO $$ BEGIN | ||
ALTER TABLE "latitude"."claimed_rewards" ADD CONSTRAINT "claimed_rewards_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "latitude"."workspaces"("id") ON DELETE no action ON UPDATE no action; | ||
EXCEPTION | ||
WHEN duplicate_object THEN null; | ||
END $$; | ||
--> statement-breakpoint | ||
DO $$ BEGIN | ||
ALTER TABLE "latitude"."claimed_rewards" ADD CONSTRAINT "claimed_rewards_creator_id_users_id_fk" FOREIGN KEY ("creator_id") REFERENCES "latitude"."users"("id") ON DELETE set null ON UPDATE no action; | ||
EXCEPTION | ||
WHEN duplicate_object THEN null; | ||
END $$; | ||
--> statement-breakpoint | ||
CREATE INDEX IF NOT EXISTS "claimed_rewards_workspace_id_idx" ON "latitude"."claimed_rewards" USING btree ("workspace_id");--> statement-breakpoint | ||
CREATE UNIQUE INDEX IF NOT EXISTS "workspace_id_reward_type_unique" ON "latitude"."claimed_rewards" USING btree ("workspace_id","reward_type"); |
Oops, something went wrong.