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 ( +