feat: more achievemnt (#964)
* feat: achievemnt Signed-off-by: Innei <i@innei.in> * fix: comment Signed-off-by: Innei <tukon479@gmail.com> * feat: audit Signed-off-by: Innei <i@innei.in> * server config Signed-off-by: Innei <i@innei.in> * config Signed-off-by: Innei <i@innei.in> * chore: auto-fix linting and formatting issues * refactor: extract mint button Signed-off-by: Innei <tukon479@gmail.com> * feat: alpha tester Signed-off-by: Innei <tukon479@gmail.com> --------- Signed-off-by: Innei <i@innei.in> Signed-off-by: Innei <tukon479@gmail.com> Co-authored-by: Innei <Innei@users.noreply.github.com>
This commit is contained in:
parent
ad9a606b6b
commit
d18ed86224
|
|
@ -1,15 +1,20 @@
|
|||
import { DotLottieReact } from "@lottiefiles/dotlottie-react"
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
||||
import type { SingletonRefObject } from "foxact/use-singleton"
|
||||
import { useSingleton } from "foxact/use-singleton"
|
||||
import type { PrimitiveAtom } from "jotai"
|
||||
import { atom, useStore } from "jotai"
|
||||
import { nanoid } from "nanoid"
|
||||
import type { FC } from "react"
|
||||
import { useEffect } from "react"
|
||||
import { Trans } from "react-i18next"
|
||||
import { useEffect, useId, useMemo, useRef } from "react"
|
||||
import { Trans, useTranslation } from "react-i18next"
|
||||
|
||||
import { useServerConfigs } from "~/atoms/server-configs"
|
||||
import { Button } from "~/components/ui/button"
|
||||
import { styledButtonVariant } from "~/components/ui/button/variants"
|
||||
import { Input } from "~/components/ui/input"
|
||||
import { LoadingCircle, LoadingWithIcon } from "~/components/ui/loading"
|
||||
import { useCurrentModal, useModalStack } from "~/components/ui/modal"
|
||||
import { useI18n } from "~/hooks/common"
|
||||
import { apiClient } from "~/lib/api-fetch"
|
||||
import { Chain } from "~/lib/chain"
|
||||
|
|
@ -19,6 +24,14 @@ import achievementAnimationUri from "~/lottie/achievement.lottie?url"
|
|||
const absoluteachievementAnimationUri = new URL(achievementAnimationUri, import.meta.url).href
|
||||
enum AchievementsActionIdMap {
|
||||
FIRST_CLAIM_FEED = 0,
|
||||
FIRST_CREATE_LIST = 1,
|
||||
LIST_SUBSCRIBE_50 = 2,
|
||||
LIST_SUBSCRIBE_100 = 3,
|
||||
LIST_SUBSCRIBE_500 = 4,
|
||||
PRODUCT_HUNT_VOTE = 5,
|
||||
// TODO
|
||||
// FOLLOW_SPECIAL_FEED = 6,
|
||||
ALPHA_TESTER = 7,
|
||||
}
|
||||
|
||||
const achievementActionIdCopyMap: Record<
|
||||
|
|
@ -29,20 +42,46 @@ const achievementActionIdCopyMap: Record<
|
|||
title: "achievement.first_claim_feed",
|
||||
description: "achievement.first_claim_feed_description",
|
||||
},
|
||||
[AchievementsActionIdMap.FIRST_CREATE_LIST]: {
|
||||
title: "achievement.first_create_list",
|
||||
description: "achievement.first_create_list_description",
|
||||
},
|
||||
[AchievementsActionIdMap.LIST_SUBSCRIBE_50]: {
|
||||
title: "achievement.list_subscribe_50",
|
||||
description: "achievement.list_subscribe_50_description",
|
||||
},
|
||||
[AchievementsActionIdMap.LIST_SUBSCRIBE_100]: {
|
||||
title: "achievement.list_subscribe_100",
|
||||
description: "achievement.list_subscribe_100_description",
|
||||
},
|
||||
[AchievementsActionIdMap.LIST_SUBSCRIBE_500]: {
|
||||
title: "achievement.list_subscribe_500",
|
||||
description: "achievement.list_subscribe_500_description",
|
||||
},
|
||||
[AchievementsActionIdMap.PRODUCT_HUNT_VOTE]: {
|
||||
title: "achievement.product_hunt_vote",
|
||||
description: "achievement.product_hunt_vote_description",
|
||||
},
|
||||
// [AchievementsActionIdMap.FOLLOW_SPECIAL_FEED]: {
|
||||
// title: "achievement.follow_special_feed",
|
||||
// description: "achievement.follow_special_feed_description",
|
||||
// },
|
||||
[AchievementsActionIdMap.ALPHA_TESTER]: {
|
||||
title: "achievement.alpha_tester",
|
||||
description: "achievement.alpha_tester_description",
|
||||
},
|
||||
}
|
||||
|
||||
const _achievementType = apiClient.achievement.$get({
|
||||
query: {
|
||||
type: "all",
|
||||
},
|
||||
})
|
||||
type Achievement = Awaited<typeof _achievementType>["data"]
|
||||
export const AchievementModalContent: FC = () => {
|
||||
const jotaiStore = useStore()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const defaultAchievements = useSingleton(buildDefaultAchievements)
|
||||
|
||||
const _achievementType = apiClient.achievement.$get({
|
||||
query: {
|
||||
type: "all",
|
||||
},
|
||||
})
|
||||
type Achievement = Awaited<typeof _achievementType>["data"]
|
||||
const {
|
||||
data: achievements,
|
||||
isLoading,
|
||||
|
|
@ -66,6 +105,7 @@ export const AchievementModalContent: FC = () => {
|
|||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading) return
|
||||
if (!achievements) return
|
||||
let shouldPolling = false
|
||||
const pollingChain = new Chain()
|
||||
|
|
@ -85,32 +125,20 @@ export const AchievementModalContent: FC = () => {
|
|||
return () => {
|
||||
pollingChain.abort()
|
||||
}
|
||||
}, [achievements, refetch])
|
||||
}, [achievements, isLoading, refetch])
|
||||
|
||||
const { mutateAsync: mintAchievement, isPending: isMinting } = useMutation({
|
||||
mutationFn: async (actionId: number) => {
|
||||
return apiClient.achievement.$put({
|
||||
json: {
|
||||
actionId,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
const achievementsDataAtom = useSingleton(() => atom<typeof achievements>())
|
||||
const { mutateAsync: checkAchievement, isPending: checkPending } = useMutation({
|
||||
mutationFn: async (actionId: number) => {
|
||||
return apiClient.achievement.check.$post({
|
||||
json: {
|
||||
actionId,
|
||||
},
|
||||
})
|
||||
},
|
||||
onSuccess: () => {
|
||||
refetch()
|
||||
},
|
||||
})
|
||||
|
||||
const t = useI18n()
|
||||
|
||||
const sortedAchievements = useMemo(() => {
|
||||
return achievements?.sort((a, b) => {
|
||||
if (a.type === "received") return 1
|
||||
if (b.type === "received") return -1
|
||||
return 0
|
||||
})
|
||||
}, [achievements])
|
||||
|
||||
return (
|
||||
<div className="relative flex w-full grow flex-col items-center">
|
||||
<DotLottieReact
|
||||
|
|
@ -143,7 +171,7 @@ export const AchievementModalContent: FC = () => {
|
|||
<LoadingWithIcon icon={<i className="i-mgc-trophy-cute-re" />} size="large" />
|
||||
</div>
|
||||
) : (
|
||||
achievements?.map((achievement) => {
|
||||
sortedAchievements?.map((achievement) => {
|
||||
const copy = achievementActionIdCopyMap[achievement.actionId]
|
||||
if (!copy) return null
|
||||
|
||||
|
|
@ -156,7 +184,7 @@ export const AchievementModalContent: FC = () => {
|
|||
{achievement.power && (
|
||||
<span className="ml-2 inline-flex items-center gap-0.5 text-xs font-normal">
|
||||
<span className="font-medium opacity-80">{achievement.power}</span>
|
||||
<i className="i-mgc-power text-sm text-accent" />
|
||||
<i className="i-mgc-power scale-95 text-sm text-accent" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -164,7 +192,6 @@ export const AchievementModalContent: FC = () => {
|
|||
{t(copy.description)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{achievement.type === "checking" && (
|
||||
<div
|
||||
className={styledButtonVariant({
|
||||
|
|
@ -176,47 +203,9 @@ export const AchievementModalContent: FC = () => {
|
|||
<span className="select-none opacity-0">{t("words.mint")}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{achievement.type === "incomplete" && (
|
||||
<button
|
||||
type="button"
|
||||
className={styledButtonVariant({
|
||||
variant: "ghost",
|
||||
className: "relative hover:bg-transparent group cursor-pointer",
|
||||
})}
|
||||
onClick={() => {
|
||||
checkAchievement(achievement.actionId)
|
||||
}}
|
||||
>
|
||||
<div className="center absolute z-[1] opacity-0 duration-200 group-hover:opacity-100">
|
||||
<i
|
||||
className={cn(
|
||||
"i-mgc-refresh-2-cute-re size-5 text-accent",
|
||||
checkPending && "animate-spin",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="duration-200 group-hover:opacity-30">
|
||||
<span className="center relative ml-2 inline-flex w-24 -translate-y-1 flex-col *:!m-0">
|
||||
<small className="shrink-0 text-xs leading-tight text-muted-foreground">
|
||||
{achievement.progress} / {achievement.progressMax}
|
||||
</small>
|
||||
<span className="relative h-1 w-full overflow-hidden rounded-full bg-accent/10">
|
||||
<span
|
||||
className="absolute -left-3 top-0 inline-block h-1 rounded-full bg-accent"
|
||||
style={{
|
||||
width: `calc(${Math.min(
|
||||
(achievement.progress / achievement.progressMax) * 100,
|
||||
100,
|
||||
)}% + 0.75rem)`,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
<IncompleteButton achievement={achievement} refetch={refetch} />
|
||||
)}
|
||||
|
||||
{achievement.type === "received" && (
|
||||
<div
|
||||
className={styledButtonVariant({
|
||||
|
|
@ -229,37 +218,23 @@ export const AchievementModalContent: FC = () => {
|
|||
{t("achievement.all_done")}
|
||||
</div>
|
||||
)}
|
||||
{achievement.type === "completed" && (
|
||||
<Button
|
||||
isLoading={isMinting}
|
||||
variant="primary"
|
||||
onClick={async () => {
|
||||
const res = await mintAchievement(achievement.actionId)
|
||||
|
||||
const currentData = jotaiStore.get(achievementsDataAtom.current)
|
||||
if (!currentData) return
|
||||
let shouldInvalidate = false
|
||||
const newData = currentData.map((item) => {
|
||||
if (item.id === achievement.id && res.data.result) {
|
||||
shouldInvalidate = true
|
||||
return {
|
||||
...item,
|
||||
done: true,
|
||||
doneAt: new Date().toISOString(),
|
||||
type: "received",
|
||||
} as const
|
||||
}
|
||||
return item
|
||||
})
|
||||
jotaiStore.set(achievementsDataAtom.current, newData)
|
||||
|
||||
if (shouldInvalidate) {
|
||||
queryClient.invalidateQueries({ queryKey: ["achievements"] })
|
||||
}
|
||||
}}
|
||||
{achievement.type === "audit" && (
|
||||
<div
|
||||
className={styledButtonVariant({
|
||||
variant: "outline",
|
||||
className:
|
||||
"relative cursor-not-allowed !bg-zinc-100/50 gap-1 border-zinc-200 text-zinc-800 dark:text-foreground dark:!bg-zinc-100/5 dark:border-zinc-200/20",
|
||||
})}
|
||||
>
|
||||
{t("words.mint")}
|
||||
</Button>
|
||||
Validating...
|
||||
</div>
|
||||
)}
|
||||
{achievement.type === "completed" && (
|
||||
<MintButton
|
||||
achievementsDataAtom={achievementsDataAtom}
|
||||
achievement={achievement}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
|
|
@ -271,13 +246,216 @@ export const AchievementModalContent: FC = () => {
|
|||
}
|
||||
|
||||
const buildDefaultAchievements = () => {
|
||||
return [
|
||||
{
|
||||
return Object.keys(achievementActionIdCopyMap).map((key) => {
|
||||
return {
|
||||
id: nanoid(),
|
||||
actionId: AchievementsActionIdMap.FIRST_CLAIM_FEED,
|
||||
actionId: Number(key),
|
||||
type: "checking",
|
||||
progress: 0,
|
||||
progressMax: 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const MintButton: FC<{
|
||||
achievementsDataAtom: SingletonRefObject<PrimitiveAtom<Achievement | undefined>>
|
||||
achievement: Achievement[number]
|
||||
}> = ({ achievementsDataAtom, achievement }) => {
|
||||
const { mutateAsync: mintAchievement, isPending: isMinting } = useMutation({
|
||||
mutationFn: async (actionId: number) => {
|
||||
return apiClient.achievement.$put({
|
||||
json: {
|
||||
actionId,
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const jotaiStore = useStore()
|
||||
const queryClient = useQueryClient()
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<Button
|
||||
isLoading={isMinting}
|
||||
variant="primary"
|
||||
onClick={async () => {
|
||||
const res = await mintAchievement(achievement.actionId)
|
||||
|
||||
const currentData = jotaiStore.get(achievementsDataAtom.current)
|
||||
if (!currentData) return
|
||||
let shouldInvalidate = false
|
||||
const newData = currentData.map((item) => {
|
||||
if (item.id === achievement.id && res.data.result) {
|
||||
shouldInvalidate = true
|
||||
return {
|
||||
...item,
|
||||
done: true,
|
||||
doneAt: new Date().toISOString(),
|
||||
type: "received",
|
||||
} as const
|
||||
}
|
||||
return item
|
||||
})
|
||||
jotaiStore.set(achievementsDataAtom.current, newData)
|
||||
|
||||
if (shouldInvalidate) {
|
||||
queryClient.invalidateQueries({ queryKey: ["achievements"] })
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t("words.mint")}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
const IncompleteButton: FC<{
|
||||
achievement: Achievement[number]
|
||||
refetch: () => void
|
||||
}> = ({ achievement, refetch }) => {
|
||||
const { mutateAsync: checkAchievement, isPending: checkPending } = useMutation({
|
||||
mutationFn: async (actionId: number) => {
|
||||
return apiClient.achievement.check.$post({
|
||||
json: {
|
||||
actionId,
|
||||
},
|
||||
})
|
||||
},
|
||||
onSuccess: () => {
|
||||
refetch()
|
||||
},
|
||||
})
|
||||
|
||||
const { present } = useModalStack()
|
||||
const Content = useMemo(() => {
|
||||
switch (achievement.actionId) {
|
||||
case AchievementsActionIdMap.PRODUCT_HUNT_VOTE: {
|
||||
return (
|
||||
<Button
|
||||
onClick={() => {
|
||||
present({
|
||||
title: "Validate Your Vote",
|
||||
content: () => <VoteValidateModalContent refetch={refetch} />,
|
||||
})
|
||||
}}
|
||||
>
|
||||
Validate
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
<>
|
||||
<div className="center absolute z-[1] opacity-0 duration-200 group-hover:opacity-100">
|
||||
<i
|
||||
className={cn(
|
||||
"i-mgc-refresh-2-cute-re size-5 text-accent",
|
||||
checkPending && "animate-spin",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="duration-200 group-hover:opacity-30">
|
||||
<span className="center relative ml-2 inline-flex w-24 -translate-y-1 flex-col *:!m-0">
|
||||
<small className="shrink-0 text-xs leading-tight text-muted-foreground">
|
||||
{achievement.progress} / {achievement.progressMax}
|
||||
</small>
|
||||
<span className="relative h-1 w-full overflow-hidden rounded-full bg-accent/10">
|
||||
<span
|
||||
className="absolute -left-3 top-0 inline-block h-1 rounded-full bg-accent"
|
||||
style={{
|
||||
width: `calc(${Math.min(
|
||||
(achievement.progress / achievement.progressMax) * 100,
|
||||
100,
|
||||
)}% + 0.75rem)`,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [
|
||||
achievement.actionId,
|
||||
achievement.progress,
|
||||
achievement.progressMax,
|
||||
checkPending,
|
||||
present,
|
||||
refetch,
|
||||
])
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={styledButtonVariant({
|
||||
variant: "ghost",
|
||||
className: "relative hover:bg-transparent group cursor-pointer",
|
||||
})}
|
||||
onClick={() => {
|
||||
checkAchievement(achievement.actionId)
|
||||
}}
|
||||
>
|
||||
{Content}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
const VoteValidateModalContent: FC<{ refetch: () => void }> = ({ refetch }) => {
|
||||
const ref = useRef<HTMLInputElement>(null)
|
||||
const { dismiss } = useCurrentModal()
|
||||
const { present } = useModalStack()
|
||||
const { mutateAsync: audit, isPending } = useMutation({
|
||||
mutationFn: (username: string) => {
|
||||
return apiClient.achievement.audit.$post({
|
||||
json: {
|
||||
actionId: AchievementsActionIdMap.PRODUCT_HUNT_VOTE,
|
||||
payload: {
|
||||
username,
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
onSuccess: () => {
|
||||
dismiss()
|
||||
|
||||
refetch()
|
||||
present({
|
||||
title: "Thank you!",
|
||||
content: () => <div>Thank you for your vote. Please wait for our verification.</div>,
|
||||
clickOutsideToDismiss: true,
|
||||
})
|
||||
},
|
||||
})
|
||||
const { PRODUCT_HUNT_VOTE_URL } = useServerConfigs() || {}
|
||||
const id = useId()
|
||||
|
||||
return (
|
||||
<form
|
||||
className="flex flex-col gap-2"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
if (!ref.current?.value) return
|
||||
audit(ref.current.value)
|
||||
}}
|
||||
>
|
||||
<label htmlFor={id}>
|
||||
Please vote for {APP_NAME} on{" "}
|
||||
<a
|
||||
href={PRODUCT_HUNT_VOTE_URL}
|
||||
className="follow-link--underline"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Product Hunk!
|
||||
</a>{" "}
|
||||
Then fill in your username here.
|
||||
</label>
|
||||
<div>
|
||||
<Input ref={ref} autoFocus id={id} placeholder="Your Product Hunt username" />
|
||||
</div>
|
||||
|
||||
<div className="mt-2 flex justify-end">
|
||||
<Button isLoading={isPending} type="submit">
|
||||
Validate
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,22 @@
|
|||
{
|
||||
"achievement.all_done": "All done!",
|
||||
"achievement.alpha_tester": "Alpha Tester",
|
||||
"achievement.alpha_tester_description": "You are an alpha tester of Follow",
|
||||
"achievement.first_claim_feed": "Feed Owner",
|
||||
"achievement.first_claim_feed_description": "You own your feed on Follow",
|
||||
"achievement.first_create_list": "List Creator",
|
||||
"achievement.first_create_list_description": "You created your first list on Follow",
|
||||
"achievement.follow_special_feed": "Special Feed Follower",
|
||||
"achievement.follow_special_feed_description": "You followed the special feed on Follow",
|
||||
"achievement.list_subscribe_100": "100 List Subscriber",
|
||||
"achievement.list_subscribe_100_description": "100 subscribers subscribed to the list you created",
|
||||
"achievement.list_subscribe_50": "50 List Subscriber",
|
||||
"achievement.list_subscribe_500": "500 List Subscriber",
|
||||
"achievement.list_subscribe_500_description": "500 subscribers subscribed to the list you created",
|
||||
"achievement.list_subscribe_50_description": "50 subscribers subscribed to the list you created",
|
||||
"achievement.mint_more_power": "Be a hardcore player and earn more <power />",
|
||||
"achievement.product_hunt_vote": "Product Hunt Voter",
|
||||
"achievement.product_hunt_vote_description": "You voted for Hunk in Product Hunt",
|
||||
"activation.activate": "Activate",
|
||||
"activation.description": "During the public beta phase, current feature is limited for inactive users.",
|
||||
"activation.title": "Invitation Code",
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@
|
|||
"8003": "Feed already added to list",
|
||||
"9000": "Achievement not completed",
|
||||
"9001": "Achievement already received",
|
||||
"9002": "Achievement is under audit",
|
||||
"9003": "Achievement audit not needed",
|
||||
"10000": "Inbox not found",
|
||||
"10001": "Inbox already exists",
|
||||
"10002": "Inbox limit exceeded",
|
||||
|
|
|
|||
|
|
@ -52,14 +52,14 @@ declare const achievements: drizzle_orm_pg_core.PgTableWithColumns<{
|
|||
tableName: "achievements";
|
||||
dataType: "string";
|
||||
columnType: "PgText";
|
||||
data: "received" | "checking" | "completed" | "incomplete";
|
||||
data: "received" | "checking" | "completed" | "incomplete" | "audit";
|
||||
driverParam: string;
|
||||
notNull: true;
|
||||
hasDefault: false;
|
||||
isPrimaryKey: false;
|
||||
isAutoincrement: false;
|
||||
hasRuntimeDefault: false;
|
||||
enumValues: ["checking", "completed", "incomplete", "received"];
|
||||
enumValues: ["checking", "completed", "incomplete", "audit", "received"];
|
||||
baseColumn: never;
|
||||
generated: undefined;
|
||||
}, {}, {}>;
|
||||
|
|
@ -165,7 +165,7 @@ declare const achievements: drizzle_orm_pg_core.PgTableWithColumns<{
|
|||
declare const achievementsOpenAPISchema: zod.ZodObject<{
|
||||
id: zod.ZodString;
|
||||
userId: zod.ZodString;
|
||||
type: zod.ZodEnum<["checking", "completed", "incomplete", "received"]>;
|
||||
type: zod.ZodEnum<["checking", "completed", "incomplete", "audit", "received"]>;
|
||||
actionId: zod.ZodNumber;
|
||||
progress: zod.ZodNumber;
|
||||
progressMax: zod.ZodNumber;
|
||||
|
|
@ -173,7 +173,7 @@ declare const achievementsOpenAPISchema: zod.ZodObject<{
|
|||
doneAt: zod.ZodNullable<zod.ZodString>;
|
||||
tx: zod.ZodNullable<zod.ZodString>;
|
||||
}, zod.UnknownKeysParam, zod.ZodTypeAny, {
|
||||
type: "received" | "checking" | "completed" | "incomplete";
|
||||
type: "received" | "checking" | "completed" | "incomplete" | "audit";
|
||||
id: string;
|
||||
userId: string;
|
||||
actionId: number;
|
||||
|
|
@ -183,7 +183,7 @@ declare const achievementsOpenAPISchema: zod.ZodObject<{
|
|||
doneAt: string | null;
|
||||
tx: string | null;
|
||||
}, {
|
||||
type: "received" | "checking" | "completed" | "incomplete";
|
||||
type: "received" | "checking" | "completed" | "incomplete" | "audit";
|
||||
id: string;
|
||||
userId: string;
|
||||
actionId: number;
|
||||
|
|
@ -4487,6 +4487,7 @@ declare const _routes: hono_hono_base.HonoBase<Env, {
|
|||
INVITATION_PRICE: number;
|
||||
DAILY_POWER_SUPPLY: number;
|
||||
IS_RSS3_TESTNET: boolean;
|
||||
PRODUCT_HUNT_VOTE_URL: string;
|
||||
};
|
||||
};
|
||||
outputFormat: "json" | "text";
|
||||
|
|
@ -6717,7 +6718,7 @@ declare const _routes: hono_hono_base.HonoBase<Env, {
|
|||
output: {
|
||||
code: number;
|
||||
data: {
|
||||
type: "received" | "checking" | "completed" | "incomplete";
|
||||
type: "received" | "checking" | "completed" | "incomplete" | "audit";
|
||||
id: string;
|
||||
userId: string;
|
||||
actionId: number;
|
||||
|
|
@ -6769,6 +6770,21 @@ declare const _routes: hono_hono_base.HonoBase<Env, {
|
|||
status: 200;
|
||||
};
|
||||
};
|
||||
"/achievement/audit": {
|
||||
$post: {
|
||||
input: {
|
||||
json: {
|
||||
actionId: number;
|
||||
payload?: any;
|
||||
};
|
||||
};
|
||||
output: {
|
||||
code: number;
|
||||
};
|
||||
outputFormat: "json" | "text";
|
||||
status: 200;
|
||||
};
|
||||
};
|
||||
}, "/">;
|
||||
type AppType = typeof _routes;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue