feat: implement check-in submission logic with hook and validation

This commit is contained in:
2026-02-13 14:06:30 +08:00
parent 170afb4a3b
commit b4e32d5a6d
6 changed files with 281 additions and 3 deletions

View File

@@ -0,0 +1,32 @@
# Draft: Implement Check-in Logic
## Requirements (User)
- **Input**: 6-digit number from scanner.
- **Action**: Call `/event/checkin/submit` (`postEventCheckinSubmit`).
- **Feedback**: Toaster (success/failure) using `sonner`.
## Research Questions
1. [Resolved] API Client: `postEventCheckinSubmit` exists.
2. [Pending] API Parameters: Need to verify `PostEventCheckinSubmitData`.
3. [Resolved] Toaster Library: `sonner` (`toast.success`, `toast.error`).
## Technical Decisions
- **Logic Placement**: `CheckinScannerNavContainer`.
- **State Management**: `useMutation` from `@tanstack/react-query`.
- **Validation**: Regex `^\d{6}$` for 6-digit number.
- **Error Handling**: `onError` in mutation -> `toast.error`.
- **Success Handling**: `onSuccess` in mutation -> `toast.success`.
## Code Snippets
```typescript
import { useMutation } from '@tanstack/react-query';
import { postEventCheckinSubmit } from '@/client/sdk.gen';
import { toast } from 'sonner';
// In container
const { mutate } = useMutation({
mutationFn: (code: string) => postEventCheckinSubmit({ body: { code } }),
onSuccess: () => toast.success('签到成功'),
onError: () => toast.error('签到失败'),
});
```

View File

@@ -0,0 +1,9 @@
### useCheckinSubmit Hook
- Created `src/hooks/data/useCheckinSubmit.ts` using `postEventCheckinSubmitMutation` from `@/client/@tanstack/react-query.gen`.
- Integrated `sonner` for success and error toasts.
- Followed the pattern from `useJoinEvent.ts`.
### CheckinScannerNav Implementation
- `CheckinScannerNavView` validation logic implemented with regex `^\d{6}$`.
- `CheckinScannerNavContainer` connects the hook to the view.
- Type checking passed with `bun tsc -b`.

View File

@@ -0,0 +1,204 @@
# Plan: Implement Check-in Logic
## TL;DR
> **Quick Summary**: Connect the scanner to the backend check-in API. When a 6-digit code is scanned, submit it to `/event/checkin/submit`. Show success/error toasts.
>
> **Deliverables**:
> - Updated `CheckinScannerNavContainer` with mutation logic.
> - Integration with `sonner` for user feedback.
> - Proper parameter mapping (`checkin_code`).
>
> **Estimated Effort**: Short
> **Parallel Execution**: NO - sequential implementation.
> **Critical Path**: Implement Mutation → Update View Integration
---
## Context
### Original Request
"扫码器扫到的如果是6位数字使用/event/checkin/submit接口进行签到成功/失败都弹出toaster提示。"
### Interview Summary
**Key Discussions**:
- **API**: `postEventCheckinSubmit` is the correct client function.
- **Parameters**: API expects `checkin_code` in the body.
- **Input**: "6位数字" implies regex validation `^\d{6}$`.
- **Feedback**: Use `sonner` (`toast.success`, `toast.error`).
**Metis Review Findings**:
- **Critical Fix**: Ensure parameter name is `checkin_code`, not `code`.
- **UX**: Disable scanning while `isPending` to prevent double submissions.
- **Error Handling**: Use generic "签到失败" for errors unless specific message available.
---
## Work Objectives
### Core Objective
Make the check-in scanner functional by connecting it to the backend.
### Concrete Deliverables
- `src/components/checkin/checkin-scanner-nav.container.tsx`: Updated with `useMutation`.
- `src/components/checkin/checkin-scanner-nav.view.tsx`: Updated to receive `isPending` prop and handle scan events.
### Definition of Done
- [ ] Scanning "123456" calls API with `{"checkin_code": "123456"}`.
- [ ] Success response shows "签到成功" toast.
- [ ] Error response shows "签到失败" toast.
- [ ] Scanner ignores non-6-digit inputs.
- [ ] Scanner pauses/ignores input while API is pending.
### Must Have
- Regex validation: `^\d{6}$`.
- `checkin_code` parameter mapping.
- Toaster feedback.
### Must NOT Have (Guardrails)
- Do NOT change the existing permission logic in the container.
- Do NOT remove the Dialog wrapping.
---
## Verification Strategy (MANDATORY)
> **UNIVERSAL RULE: ZERO HUMAN INTERVENTION**
> ALL tasks in this plan MUST be verifiable WITHOUT any human action.
### Test Decision
- **Infrastructure exists**: YES (Playwright).
- **Automated tests**: YES (Playwright API mocking).
### Agent-Executed QA Scenarios (MANDATORY)
**Scenario 1: Successful Check-in**
- **Tool**: Playwright
- **Steps**:
1. Mock `/event/checkin/submit` to return 200 OK.
2. Simulate scan event with "123456".
3. Assert API called with correct body.
4. Assert "签到成功" toast visible.
**Scenario 2: Failed Check-in**
- **Tool**: Playwright
- **Steps**:
1. Mock `/event/checkin/submit` to return 400 Bad Request.
2. Simulate scan event with "123456".
3. Assert "签到失败" toast visible.
**Scenario 3: Invalid Input**
- **Tool**: Playwright (if view exposes logic) or Unit Test
- **Steps**:
1. Simulate scan event with "ABC".
2. Assert API NOT called.
---
## Execution Strategy
### Parallel Execution Waves
```
Wave 1:
├── Task 1: Create Data Hook
└── Task 2: Implement Container & View Logic
```
---
## TODOs
- [x] 1. Create Data Hook
**What to do**:
- Create `src/hooks/data/useCheckinSubmit.ts`.
- Import `useMutation` from `@tanstack/react-query`.
- Import `postEventCheckinSubmitMutation` from `@/client/@tanstack/react-query.gen`.
- Import `toast` from `sonner`.
- Export `useCheckinSubmit` hook that returns the mutation.
- Use `...postEventCheckinSubmitMutation()` pattern.
- On success: `toast.success('签到成功')`.
- On error: `toast.error('签到失败')`.
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: [`frontend-ui-ux`]
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 1
**References**:
- `src/hooks/data/useJoinEvent.ts` (Pattern reference)
- `src/client/@tanstack/react-query.gen.ts`
**Acceptance Criteria**:
- [ ] Hook uses `@hey-api` pattern.
- [ ] Toasts are configured.
- [x] 2. Implement Container & View Logic
**What to do**:
- Update `src/components/checkin/checkin-scanner-nav.container.tsx`:
- Import `useCheckinSubmit` from `@/hooks/data/useCheckinSubmit`.
- Use the hook to get `mutate` and `isPending`.
- Pass `handleScan` (wrapper calling mutate with `{ body: { checkin_code: code } }`) and `isPending` to View.
- Update `src/components/checkin/checkin-scanner-nav.view.tsx`:
- Accept `onScan` and `isPending` props.
- Inside internal `handleScan`, check regex `^\d{6}$`.
- If valid and !isPending, call prop `onScan`.
**Recommended Agent Profile**:
- **Category**: `visual-engineering`
- **Skills**: [`frontend-ui-ux`]
**Parallelization**:
- **Can Run In Parallel**: YES
- **Parallel Group**: Wave 1
- **References**:
- `src/hooks/data/useCheckinSubmit.ts` (Dependency)
**Acceptance Criteria**:
- [ ] Container uses the new hook.
- [ ] View logic validates regex.
- [ ] 2. Update Playwright Verification
**What to do**:
- Update `tests/checkin-scanner.spec.ts`.
- Add test case for successful check-in (mock API success).
- Add test case for failed check-in (mock API failure).
- Verify toaster appearance.
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: [`playwright`]
**Parallelization**:
- **Can Run In Parallel**: NO
- **Parallel Group**: Wave 1
- **Blocked By**: Task 1
**References**:
- `tests/checkin-scanner.spec.ts`
**Acceptance Criteria**:
- [ ] Tests pass.
**Agent-Executed QA Scenarios**:
```
Scenario: Run Updated Tests
Tool: Bash
Steps:
1. npx playwright test tests/checkin-scanner.spec.ts
```
---
## Success Criteria
### Final Checklist
- [ ] API integration complete.
- [ ] Regex validation matches `^\d{6}$`.
- [ ] User feedback (toasts) functional.

View File

@@ -1,12 +1,19 @@
import { useUserInfo } from '@/hooks/data/useUserInfo';
import { useCheckinSubmit } from '@/hooks/data/useCheckinSubmit';
import { CheckinScannerNavView } from './checkin-scanner-nav.view';
export function CheckinScannerNavContainer() {
const { data } = useUserInfo();
const { mutate, isPending } = useCheckinSubmit();
if ((data.data?.permission_level ?? 0) <= 20) {
return null;
}
return <CheckinScannerNavView />;
return (
<CheckinScannerNavView
onScan={(code) => mutate({ body: { checkin_code: code } })}
isPending={isPending}
/>
);
}

View File

@@ -4,11 +4,22 @@ import { Dialog, DialogTrigger } from '@/components/ui/dialog';
import { SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar';
import { CheckinScannerDialogView } from './checkin-scanner.dialog.view';
export function CheckinScannerNavView() {
interface CheckinScannerNavViewProps {
onScan: (code: string) => void;
isPending: boolean;
}
export function CheckinScannerNavView({ onScan, isPending }: CheckinScannerNavViewProps) {
const [open, setOpen] = useState(false);
const handleScan = (value: string) => {
console.log('Scanned:', value);
if (isPending) return;
if (!/^\d{6}$/.test(value)) {
return;
}
onScan(value);
setOpen(false);
};

View File

@@ -0,0 +1,15 @@
import { useMutation } from '@tanstack/react-query';
import { toast } from 'sonner';
import { postEventCheckinSubmitMutation } from '@/client/@tanstack/react-query.gen';
export function useCheckinSubmit() {
return useMutation({
...postEventCheckinSubmitMutation(),
onSuccess: () => {
toast.success('签到成功');
},
onError: () => {
toast.error('签到失败');
},
});
}