diff --git a/.vscode/settings.json b/.vscode/settings.json index 44a73ec3a..a84c425cc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,6 @@ { "mode": "auto" } - ] + ], + "editor.formatOnSave": true } diff --git a/apps/web/src/actions/rewards/claimRewardAction.ts b/apps/web/src/actions/rewards/claimRewardAction.ts new file mode 100644 index 000000000..3401b8d71 --- /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 | undefined + }) diff --git a/apps/web/src/actions/rewards/fetchClaimedRewardsAction.ts b/apps/web/src/actions/rewards/fetchClaimedRewardsAction.ts new file mode 100644 index 000000000..df5a73bac --- /dev/null +++ b/apps/web/src/actions/rewards/fetchClaimedRewardsAction.ts @@ -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() + }) diff --git a/apps/web/src/actions/rewards/fetchPendingRewardClaimsAction.ts b/apps/web/src/actions/rewards/fetchPendingRewardClaimsAction.ts new file mode 100644 index 000000000..5b349a383 --- /dev/null +++ b/apps/web/src/actions/rewards/fetchPendingRewardClaimsAction.ts @@ -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() + }) diff --git a/apps/web/src/actions/rewards/updateRewardClaimValidityAction.ts b/apps/web/src/actions/rewards/updateRewardClaimValidityAction.ts new file mode 100644 index 000000000..3a4e5a4e6 --- /dev/null +++ b/apps/web/src/actions/rewards/updateRewardClaimValidityAction.ts @@ -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 + }) diff --git a/apps/web/src/app/(admin)/backoffice/_components/BackofficeTabs.tsx b/apps/web/src/app/(admin)/backoffice/_components/BackofficeTabs.tsx new file mode 100644 index 000000000..32a4e06f6 --- /dev/null +++ b/apps/web/src/app/(admin)/backoffice/_components/BackofficeTabs.tsx @@ -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 ( +
+
+ { + router.push(ROUTES.backoffice[value].root) + }} + /> +
+ {children} +
+ ) +} diff --git a/apps/web/src/app/(admin)/backoffice/layout.tsx b/apps/web/src/app/(admin)/backoffice/layout.tsx index d7d3a7169..769697556 100644 --- a/apps/web/src/app/(admin)/backoffice/layout.tsx +++ b/apps/web/src/app/(admin)/backoffice/layout.tsx @@ -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, }: { @@ -19,7 +21,7 @@ export default async function AdminLayout({ return ( - {children} + {children} ) } diff --git a/apps/web/src/app/(admin)/backoffice/page.tsx b/apps/web/src/app/(admin)/backoffice/page.tsx index 3f4ed0088..88688c070 100644 --- a/apps/web/src/app/(admin)/backoffice/page.tsx +++ b/apps/web/src/app/(admin)/backoffice/page.tsx @@ -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 ( -
- - -
- ) -} - -function EvaluationTemplatesTable({ - evaluationTemplates, -}: { - evaluationTemplates: EvaluationTemplateWithCategory[] -}) { - const { destroy } = useEvaluationTemplates() - if (!evaluationTemplates.length) return null - - return ( - - - - Name - Category - Description - Configuration - - - - - {evaluationTemplates.map((template) => ( - - - {template.name} - - - {template.category} - - -
-
- {template.description} -
-
-
- - - {JSON.stringify(template.configuration)} - - - - - -
- ))} -
- - } - /> - ) -} - -function NewEvaluationTemplate() { - const { create } = useEvaluationTemplates() - - const handleSubmit = (ev: FormEvent) => { - 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 ( -
-
- - - - - -