Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix] [Frontend] [Relay] Improve check in flow #517

Merged
merged 11 commits into from
Sep 25, 2024
2 changes: 2 additions & 0 deletions packages/frontend/src/constants/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const SERVER = process.env.REACT_APP_SERVER ?? 'http://localhost:8000'
export const KEY_SERVER = `${SERVER}/build/`
export const NUM_EPOCH_KEY_NONCE_PER_EPOCH = 3
export const CHECKED_IN_AT = 'checked-in-at'
export const DISCARDED_CHECK_IN_AT = 'discarded-check-in-at'
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ function ActionLink({
if (
action.type === ActionType.ReportComment ||
action.type === ActionType.ReportPost ||
action.type === ActionType.Adjudicate
action.type === ActionType.Adjudicate ||
action.type === ActionType.CheckIn
) {
return <span className="text-white">-</span>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function CheckInNotification() {
const [isOpenCheckIn, toggleCheckIn] = useToggle(false)
const [isOpenDiscardCheckIn, toggleDiscardCheckIn] = useToggle(false)

const { isOpen } = useNotifyCheckIn()
const { isOpen, discardCheckIn } = useNotifyCheckIn()

if (!isOpen) {
return null
Expand All @@ -28,6 +28,9 @@ export default function CheckInNotification() {
<DiscardCheckIn
open={isOpenDiscardCheckIn}
onClose={() => toggleDiscardCheckIn(false)}
onConfirm={() => {
discardCheckIn()
}}
onCheckIn={() => {
toggleDiscardCheckIn(false)
toggleCheckIn(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@ import {
DialogBackdrop,
DialogPanel,
} from '@headlessui/react'
import { useNotifyCheckIn } from '../../hooks/useNotifyCheckIn/useNotifyCheckIn'

export default function DiscardCheckIn({
open = false,
onClose = () => {},
onCheckIn = () => {},
}: {
interface DiscardCheckInProps {
open?: boolean
onClose?: () => void
onConfirm?: () => void
onCheckIn?: () => void
}) {
const { discard } = useNotifyCheckIn()
}

export default function DiscardCheckIn({
open = false,
onClose = () => {},
onConfirm = () => {},
onCheckIn = () => {},
}: DiscardCheckInProps) {
return (
<Dialog className="relative z-50" open={open} onClose={onClose}>
<DialogBackdrop className="fixed inset-0 bg-black/70" />
Expand All @@ -37,7 +38,7 @@ export default function DiscardCheckIn({
<div className="flex gap-4">
<button
className="flex-1 text-lg font-bold text-white btn btn-primary"
onClick={discard}
onClick={onConfirm}
>
確認放棄
</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MutationKeys } from '@/constants/queryKeys'
import { MutationKeys, QueryKeys } from '@/constants/queryKeys'
import {
ActionType,
addAction,
Expand All @@ -8,10 +8,12 @@ import {
useUserState,
useUserStateTransition,
} from '@/features/core'
import { useMutation } from '@tanstack/react-query'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { AdjudicateFormValues } from '../../components/Adjudicate/AdjudicateForm'

export function useAdjudicate() {
const queryClient = useQueryClient()

const { userState } = useUserState()

const { stateTransition } = useUserStateTransition()
Expand Down Expand Up @@ -41,6 +43,10 @@ export function useAdjudicate() {
if (context?.actionId) {
succeedActionById(context.actionId)
}

await queryClient.invalidateQueries({
queryKey: [QueryKeys.PendingReports],
})
},
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '@/features/core'
import { getEpochKeyNonce } from '@/utils/helpers/getEpochKeyNonce'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { useNotifyCheckIn } from '../useNotifyCheckIn/useNotifyCheckIn'

export function useCheckIn() {
const queryClient = useQueryClient()
Expand All @@ -24,6 +25,8 @@ export function useCheckIn() {

const actionCount = useActionCount()

const { startCheckIn, failCheckIn } = useNotifyCheckIn()

return useMutation({
mutationKey: [MutationKeys.CheckIn],
mutationFn: async () => {
Expand Down Expand Up @@ -52,10 +55,12 @@ export function useCheckIn() {
}
},
onMutate: (_variables) => {
startCheckIn()
const actionId = addAction(ActionType.CheckIn, undefined)
return { actionId }
},
onError: (_error, _variables, context) => {
failCheckIn()
if (context?.actionId) {
failActionById(context.actionId)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,57 @@
import { CHECKED_IN_AT, DISCARDED_CHECK_IN_AT } from '@/constants/config'
import { useReputationScore } from '@/features/reporting'
import { useLocalStorage } from '@uidotdev/usehooks'
import dayjs from 'dayjs'
import { useEffect, useMemo } from 'react'

const DISCARDED_AT = 'discarded-checkin-at'
import { useEffect } from 'react'

export function useNotifyCheckIn() {
const { reputationScore } = useReputationScore()

const [discardedAt, saveDiscardedAt] = useLocalStorage<string | null>(
DISCARDED_AT,
const [checkedInAt, setCheckedInAt] = useLocalStorage<string | null>(
CHECKED_IN_AT,
null,
)

const isOpen = useMemo(
() => !!reputationScore && reputationScore < 0 && !discardedAt,
[reputationScore, discardedAt],
const [discardedAt, setDiscardedAt] = useLocalStorage<string | null>(
DISCARDED_CHECK_IN_AT,
null,
)

const discard = () => {
saveDiscardedAt(new Date().toISOString())
const isOpen =
!!reputationScore && reputationScore < 0 && !checkedInAt && !discardedAt

const startCheckIn = () => {
setCheckedInAt(new Date().toISOString())
}

const failCheckIn = () => {
setCheckedInAt(null)
}

const discardCheckIn = () => {
setDiscardedAt(new Date().toISOString())
}

useEffect(() => {
if (!checkedInAt || dayjs().isSame(dayjs(checkedInAt), 'day')) {
return
}

setCheckedInAt(null)
}, [checkedInAt, setCheckedInAt])

useEffect(() => {
if (!discardedAt || dayjs().isSame(dayjs(discardedAt), 'day')) {
return
}

saveDiscardedAt(null)
}, [discardedAt, saveDiscardedAt])
setDiscardedAt(null)
}, [discardedAt, setDiscardedAt])

return {
isOpen,
discard,
startCheckIn,
failCheckIn,
discardCheckIn,
}
}
4 changes: 2 additions & 2 deletions packages/frontend/src/routes/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
AdjudicationNotification,
CheckInNotification,
} from '@/features/reporting'
import { useBackgroundReputationClaim } from '@/features/reporting/hooks/useBackgroundReputationClaim/useBackgroundReputationClaim'
import { MobileBottomNav } from '@/features/shared'
import { ForbidActionDialog } from '@/features/shared/components/Dialog/ForbidActionDialog'
import {
Expand All @@ -27,13 +28,12 @@ import {
useMatch,
useNavigate,
} from 'react-router-dom'
import { useBackgroundReputationClaim } from '@/features/reporting/hooks/useBackgroundReputationClaim/useBackgroundReputationClaim'

function NotificationContainer({ children }: { children: React.ReactNode }) {
return (
<div
id="notifications"
className="fixed z-20 right-4 bottom-28 lg:right-10 lg:bottom-20"
className="fixed z-20 right-4 bottom-28 lg:left-10 lg:right-auto lg:bottom-20"
>
{children}
</div>
Expand Down
6 changes: 5 additions & 1 deletion packages/relay/src/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,11 @@ const _schema = [
['epochKey', 'String'],
['score', 'Int'],
['type', 'Int'],
['reportId', 'String'],
{
name: 'reportId',
type: 'String',
optional: true,
},
{
name: 'report',
type: 'Object',
Expand Down
11 changes: 3 additions & 8 deletions packages/relay/src/routes/checkinRoute.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { DB } from 'anondb/node'
import { Express } from 'express'
import { createCheckReputationMiddleware } from '../middlewares/CheckReputationMiddleware'
import { reputationService } from '../services/ReputationService'
import { UnirepSocialSynchronizer } from '../services/singletons/UnirepSocialSynchronizer'
import { errorHandler } from '../services/utils/ErrorHandler'
import ProofHelper from '../services/utils/ProofHelper'
import TransactionManager from '../services/utils/TransactionManager'
import { Errors } from '../types'

export default (
Expand All @@ -21,17 +20,13 @@ export default (

const { publicSignals, proof } = req.body

const epochKeyProof = await ProofHelper.getAndVerifyEpochKeyProof(
const txHash = await reputationService.claimCheckInReputation(
publicSignals,
proof,
db,
synchronizer
)

const txHash = await TransactionManager.callContract(
'claimDailyLoginRep',
[epochKeyProof.publicSignals, epochKeyProof.proof]
)

res.json({ txHash })
})
)
Expand Down
39 changes: 38 additions & 1 deletion packages/relay/src/services/ReputationService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { DB } from 'anondb/node'
import { ReputationHistory } from '../types/Reputation'
import { Groth16Proof, PublicSignals } from 'snarkjs'
import {
RepChangeType,
ReputationHistory,
ReputationType,
} from '../types/Reputation'
import { UnirepSocialSynchronizer } from './singletons/UnirepSocialSynchronizer'
import ProofHelper from './utils/ProofHelper'
import TransactionManager from './utils/TransactionManager'

export class ReputationService {
async findManyReputationHistory(
Expand All @@ -24,6 +32,35 @@ export class ReputationService {

return reputations
}

async claimCheckInReputation(
publicSignals: PublicSignals,
proof: Groth16Proof,
db: DB,
synchronizer: UnirepSocialSynchronizer
) {
const epochKeyProof = await ProofHelper.getAndVerifyEpochKeyProof(
publicSignals,
proof,
synchronizer
)

const txHash = await TransactionManager.callContract(
'claimDailyLoginRep',
[epochKeyProof.publicSignals, epochKeyProof.proof]
)

db.create('ReputationHistory', {
transactionHash: txHash,
epoch: Number(epochKeyProof.epoch),
epochKey: String(epochKeyProof.epochKey),
score: RepChangeType.CHECK_IN_REP,
type: ReputationType.CHECK_IN,
reportId: null,
})

return txHash
}
}

export const reputationService = new ReputationService()
1 change: 1 addition & 0 deletions packages/relay/src/types/Reputation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export enum RepChangeType {
RESPONDENT_REP = 5,
FAILED_REPORTER_REP = 1,
ADJUDICATOR_REP = 1,
CHECK_IN_REP = 1,
}

// Reputation user type
Expand Down
Loading