Skip to content

Commit

Permalink
Rewards
Browse files Browse the repository at this point in the history
  • Loading branch information
csansoon committed Sep 27, 2024
1 parent 9094fb5 commit 1e00d09
Show file tree
Hide file tree
Showing 45 changed files with 4,055 additions and 211 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
{
"mode": "auto"
}
]
],
"editor.formatOnSave": true
}
27 changes: 27 additions & 0 deletions apps/web/src/actions/rewards/claimRewardAction.ts
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 | undefined
})
13 changes: 13 additions & 0 deletions apps/web/src/actions/rewards/fetchClaimedRewardsAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use server'

import { ClaimedRewardsRepository } from '@latitude-data/core/repositories'

import { authProcedure } from '../procedures'

export const fetchClaimedRewardsAction = authProcedure
.createServerAction()
.handler(async ({ ctx }) => {
const claimedRewardsScope = new ClaimedRewardsRepository(ctx.workspace.id)
const result = await claimedRewardsScope.findAllValidOptimistic()
return result.unwrap()
})
17 changes: 17 additions & 0 deletions apps/web/src/actions/rewards/fetchPendingRewardClaimsAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use server'

import { findAllRewardClaimsPendingToValidate } from '@latitude-data/core/data-access'
import { UnauthorizedError } from '@latitude-data/core/lib/errors'

import { authProcedure } from '../procedures'

export const fetchPendingRewardClaimsAction = authProcedure
.createServerAction()
.handler(async ({ ctx }) => {
if (!ctx.user.admin) {
throw new UnauthorizedError('You must be an admin to see pending claims')
}

const result = await findAllRewardClaimsPendingToValidate()
return result.unwrap()
})
28 changes: 28 additions & 0 deletions apps/web/src/actions/rewards/updateRewardClaimValidityAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use server'

import { ClaimedReward } from '@latitude-data/core/browser'
import { UnauthorizedError } from '@latitude-data/core/lib/errors'
import { updateRewardClaim } from '@latitude-data/core/services/claimedRewards/update'
import { z } from 'zod'

import { authProcedure } from '../procedures'

export const updateRewardClaimValidityAction = authProcedure
.createServerAction()
.input(
z.object({
claimId: z.number(),
isValid: z.boolean().nullable(),
}),
)
.handler(async ({ input, ctx }) => {
if (!ctx.user.admin) {
throw new UnauthorizedError('You must be an admin to see pending claims')
}

const result = await updateRewardClaim({
claimId: input.claimId,
isValid: input.isValid,
})
return result.unwrap() as ClaimedReward | undefined
})
37 changes: 37 additions & 0 deletions apps/web/src/app/(admin)/backoffice/_components/BackofficeTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use client'

import { ReactNode } from 'react'

import { TabSelector } from '@latitude-data/web-ui'
import { useNavigate } from '$/hooks/useNavigate'
import { BackofficeRoutes, ROUTES } from '$/services/routes'
import { useSelectedLayoutSegment } from 'next/navigation'

export function BackofficeTabs({ children }: { children: ReactNode }) {
const router = useNavigate()
const selected = useSelectedLayoutSegment() as BackofficeRoutes
return (
<div className='flex flex-col w-full gap-4'>
<div className='w-full p-4 pb-0'>
<TabSelector
showSelectedOnSubroutes
options={[
{
label: 'Templates',
value: BackofficeRoutes.templates,
},
{
label: 'Rewards',
value: BackofficeRoutes.rewards,
},
]}
selected={selected}
onSelect={(value) => {
router.push(ROUTES.backoffice[value].root)
}}
/>
</div>
{children}
</div>
)
}
4 changes: 3 additions & 1 deletion apps/web/src/app/(admin)/backoffice/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { getSession } from '$/services/auth/getSession'
import { ROUTES } from '$/services/routes'
import { redirect } from 'next/navigation'

import { BackofficeTabs } from './_components/BackofficeTabs'

export default async function AdminLayout({
children,
}: {
Expand All @@ -19,7 +21,7 @@ export default async function AdminLayout({

return (
<SessionProvider currentUser={user} workspace={workspace}>
{children}
<BackofficeTabs>{children}</BackofficeTabs>
</SessionProvider>
)
}
198 changes: 4 additions & 194 deletions apps/web/src/app/(admin)/backoffice/page.tsx
Original file line number Diff line number Diff line change
@@ -1,196 +1,6 @@
'use client'
import { BackofficeRoutes, ROUTES } from '$/services/routes'
import { redirect } from 'next/navigation'

import { FormEvent } from 'react'

import {
EvaluationResultableType,
EvaluationTemplateWithCategory,
} from '@latitude-data/core/browser'
import {
Button,
FormField,
FormWrapper,
Icon,
Input,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
TableWithHeader,
TabSelector,
Text,
TextArea,
} from '@latitude-data/web-ui'
import useEvaluationTemplates from '$/stores/evaluationTemplates'

import { useEvaluationConfiguration } from '../../(private)/evaluations/_components/CreateEvaluationModal'

export default function AdminPage() {
const { data: evaluationTemplates } = useEvaluationTemplates()

return (
<div className='max-w-[1250px] m-auto py-8 flex flex-col gap-8'>
<NewEvaluationTemplate />
<EvaluationTemplatesTable evaluationTemplates={evaluationTemplates} />
</div>
)
}

function EvaluationTemplatesTable({
evaluationTemplates,
}: {
evaluationTemplates: EvaluationTemplateWithCategory[]
}) {
const { destroy } = useEvaluationTemplates()
if (!evaluationTemplates.length) return null

return (
<TableWithHeader
title='Evaluation Templates'
table={
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Category</TableHead>
<TableHead>Description</TableHead>
<TableHead>Configuration</TableHead>
<TableHead />
</TableRow>
</TableHeader>
<TableBody>
{evaluationTemplates.map((template) => (
<TableRow
key={template.id}
className='cursor-pointer border-b-[0.5px] h-12 max-h-12 border-border'
>
<TableCell>
<Text.H4 noWrap>{template.name}</Text.H4>
</TableCell>
<TableCell>
<Text.H4 noWrap>{template.category}</Text.H4>
</TableCell>
<TableCell>
<div className='flex flex-row gap-1 justify-between items-center'>
<div className='flex-auto'>
<Text.H4>{template.description}</Text.H4>
</div>
</div>
</TableCell>
<TableCell>
<Text.H4 noWrap>
{JSON.stringify(template.configuration)}
</Text.H4>
</TableCell>
<TableCell>
<Button
variant='outline'
onClick={() => destroy({ id: template.id })}
>
<Icon name='trash' />
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
}
/>
)
}

function NewEvaluationTemplate() {
const { create } = useEvaluationTemplates()

const handleSubmit = (ev: FormEvent<HTMLFormElement>) => {
ev.preventDefault() // This line prevents the default form submission

const formData = new FormData(ev.currentTarget)
const name = formData.get('name') as string
const description = formData.get('description') as string
const prompt = formData.get('prompt') as string

create({
name,
description,
prompt,
configuration,
})
}

const {
configuration,
handleTypeChange,
handleRangeFromChange,
handleRangeToChange,
} = useEvaluationConfiguration()

return (
<div className='w-full flex flex-col gap-4'>
<form onSubmit={handleSubmit}>
<FormWrapper>
<FormField label='Name'>
<Input
required
name='name'
placeholder='Enter title'
className='w-full'
/>
</FormField>
<FormField label='Description'>
<TextArea
required
name='description'
minRows={4}
maxRows={6}
placeholder='Describe what is the purpose of this template'
className='w-full'
/>
</FormField>
<FormField label='Prompt'>
<TextArea
required
name='prompt'
minRows={4}
maxRows={6}
placeholder='Write your template prompt'
className='w-full'
/>
</FormField>
<TabSelector
options={[
{ label: 'Text', value: EvaluationResultableType.Text },
{ label: 'Number', value: EvaluationResultableType.Number },
{ label: 'Boolean', value: EvaluationResultableType.Boolean },
]}
onSelect={handleTypeChange}
selected={configuration.type}
/>
{configuration.type === EvaluationResultableType.Number && (
<FormField label='Range'>
<div className='flex flex-row items-center flex-1 gap-4'>
<Input
type='number'
min={0}
value={configuration.detail?.range.from.toString() || ''}
placeholder='From'
onChange={handleRangeFromChange}
/>
<Input
type='number'
min={0}
value={configuration.detail?.range.to.toString() || ''}
placeholder='To'
onChange={handleRangeToChange}
/>
</div>
</FormField>
)}
<Button type='submit'>Create Template</Button>
</FormWrapper>
</form>
</div>
)
export default async function AdminPage() {
redirect(ROUTES.backoffice[BackofficeRoutes.templates].root)
}
Loading

0 comments on commit 1e00d09

Please sign in to comment.