@@ -10,20 +10,15 @@ import { mutatePrefix } from "@/lib/swr/mutate";
1010import { useApiMutation } from "@/lib/swr/use-api-mutation" ;
1111import useProgram from "@/lib/swr/use-program" ;
1212import useWorkspace from "@/lib/swr/use-workspace" ;
13- import { BountyProps } from "@/lib/types" ;
14- import {
15- bountyPerformanceConditionSchema ,
16- createBountySchema ,
17- } from "@/lib/zod/schemas/bounties" ;
18- import { BountyLogic } from "@/ui/partners/bounties/bounty-logic" ;
13+ import { BountyFormData , BountyProps } from "@/lib/types" ;
14+ import { bountyPerformanceConditionSchema } from "@/lib/zod/schemas/bounties" ;
1915import { GroupsMultiSelect } from "@/ui/partners/groups/groups-multi-select" ;
2016import {
2117 ProgramSheetAccordion ,
2218 ProgramSheetAccordionContent ,
2319 ProgramSheetAccordionItem ,
2420 ProgramSheetAccordionTrigger ,
2521} from "@/ui/partners/program-sheet-accordion" ;
26- import { AmountInput } from "@/ui/shared/amount-input" ;
2722import { X } from "@/ui/shared/icons" ;
2823import { MaxCharactersCounter } from "@/ui/shared/max-characters-counter" ;
2924import {
@@ -38,29 +33,19 @@ import {
3833 Sheet ,
3934 SmartDateTimePicker ,
4035 Switch ,
41- ToggleGroup ,
4236 useRouterStuff ,
4337} from "@dub/ui" ;
4438import { cn , formatDate } from "@dub/utils" ;
4539import { Dispatch , SetStateAction , useMemo , useState } from "react" ;
46- import {
47- Controller ,
48- FormProvider ,
49- useForm ,
50- useFormContext ,
51- } from "react-hook-form" ;
40+ import { Controller , FormProvider , useForm } from "react-hook-form" ;
5241import { toast } from "sonner" ;
53- import * as z from "zod/v4 " ;
42+ import { BountyCriteriaSection } from "./bounty-criteria-section " ;
5443import { useConfirmCreateBountyModal } from "./confirm-create-bounty-modal" ;
5544
56- type BountySheetProps = {
45+ interface BountySheetProps {
5746 setIsOpen : Dispatch < SetStateAction < boolean > > ;
5847 bounty ?: BountyProps ;
59- } ;
60-
61- type FormData = z . infer < typeof createBountySchema > ;
62-
63- export const useAddEditBountyForm = ( ) => useFormContext < FormData > ( ) ;
48+ }
6449
6550const BOUNTY_TYPES : CardSelectorOption [ ] = [
6651 {
@@ -75,18 +60,6 @@ const BOUNTY_TYPES: CardSelectorOption[] = [
7560 } ,
7661] ;
7762
78- // Only valid for submission bounties
79- const REWARD_TYPES = [
80- {
81- value : "flat" ,
82- label : "Flat rate" ,
83- } ,
84- {
85- value : "custom" ,
86- label : "Custom" ,
87- } ,
88- ] ;
89-
9063const ACCORDION_ITEMS = [
9164 "bounty-type" ,
9265 "bounty-details" ,
@@ -146,7 +119,7 @@ function BountySheetContent({ setIsOpen, bounty }: BountySheetProps) {
146119 bounty ? ( bounty . rewardAmount ? "flat" : "custom" ) : "flat" ,
147120 ) ;
148121
149- const form = useForm < FormData > ( {
122+ const form = useForm < BountyFormData > ( {
150123 defaultValues : {
151124 name : bounty ?. name || undefined ,
152125 description : bounty ?. description || undefined ,
@@ -645,7 +618,7 @@ function BountySheetContent({ setIsOpen, bounty }: BountySheetProps) {
645618 < CardSelector
646619 options = { BOUNTY_TYPES }
647620 value = { watch ( "type" ) }
648- onChange = { ( value : FormData [ "type" ] ) =>
621+ onChange = { ( value : BountyFormData [ "type" ] ) =>
649622 setValue ( "type" , value )
650623 }
651624 name = "bounty-type"
@@ -825,7 +798,6 @@ function BountySheetContent({ setIsOpen, bounty }: BountySheetProps) {
825798 </ div >
826799 </ div >
827800 </ div >
828-
829801 </ >
830802 ) }
831803
@@ -884,122 +856,10 @@ function BountySheetContent({ setIsOpen, bounty }: BountySheetProps) {
884856 </ ProgramSheetAccordionContent >
885857 </ ProgramSheetAccordionItem >
886858
887- < ProgramSheetAccordionItem value = "bounty-criteria" >
888- < ProgramSheetAccordionTrigger >
889- Criteria
890- </ ProgramSheetAccordionTrigger >
891- < ProgramSheetAccordionContent >
892- < div className = "space-y-6" >
893- < p className = "text-content-default text-sm" >
894- Set the reward and completion criteria.
895- </ p >
896-
897- { type === "submission" && (
898- < ToggleGroup
899- className = "flex w-full items-center gap-1 rounded-md border border-neutral-200 bg-neutral-100 p-1"
900- optionClassName = "h-8 flex items-center justify-center rounded-md flex-1 text-sm"
901- indicatorClassName = "bg-white border-none rounded-md"
902- options = { REWARD_TYPES }
903- selected = { rewardType }
904- selectAction = { ( id : RewardType ) => setRewardType ( id ) }
905- />
906- ) }
907-
908- { ( rewardType === "flat" || type === "performance" ) && (
909- < div >
910- < label
911- htmlFor = "rewardAmount"
912- className = "text-sm font-medium text-neutral-800"
913- >
914- Reward
915- </ label >
916- < div className = "mt-2" >
917- < Controller
918- name = "rewardAmount"
919- control = { control }
920- rules = { {
921- required : true ,
922- min : 0 ,
923- } }
924- render = { ( { field } ) => (
925- < AmountInput
926- { ...field }
927- id = "rewardAmount"
928- amountType = "flat"
929- placeholder = "200"
930- error = { errors . rewardAmount ?. message }
931- value = {
932- field . value == null || isNaN ( field . value )
933- ? ""
934- : field . value
935- }
936- onChange = { ( e ) => {
937- const val = e . target . value ;
938-
939- field . onChange (
940- val === "" ? null : parseFloat ( val ) ,
941- ) ;
942- } }
943- />
944- ) }
945- />
946- </ div >
947- </ div >
948- ) }
949-
950- { rewardType === "custom" && type === "submission" && (
951- < div >
952- < label
953- htmlFor = "rewardDescription"
954- className = "text-sm font-medium text-neutral-800"
955- >
956- Reward
957- </ label >
958- < div className = "mt-2" >
959- < input
960- id = "rewardDescription"
961- type = "text"
962- maxLength = { 100 }
963- className = { cn (
964- "block w-full rounded-md border-neutral-300 px-3 py-2 text-neutral-900 placeholder-neutral-400 focus:border-neutral-500 focus:outline-none focus:ring-neutral-500 sm:text-sm" ,
965- errors . rewardDescription &&
966- "border-red-600 focus:border-red-500 focus:ring-red-600" ,
967- ) }
968- placeholder = "Earn an additional 10% if you hit your revenue goal"
969- { ...register ( "rewardDescription" , {
970- setValueAs : ( value ) =>
971- value === "" ? null : value ,
972- } ) }
973- />
974- < div className = "mt-1 text-left" >
975- < span className = "text-xs text-neutral-400" >
976- { rewardDescription ?. length || 0 } /100
977- </ span >
978- </ div >
979- </ div >
980- </ div >
981- ) }
982-
983- { type === "performance" && (
984- < div >
985- < span className = "text-sm font-medium text-neutral-800" >
986- Logic
987- </ span >
988- < BountyLogic className = "mt-2" />
989- </ div >
990- ) }
991-
992- { rewardType === "custom" && type === "submission" && (
993- < div className = "gap-4 rounded-lg bg-orange-50 px-4 py-2.5 text-center" >
994- < span className = "text-sm font-medium text-orange-800" >
995- When reviewing these submissions, a custom reward
996- amount will be required to approve.
997- </ span >
998- </ div >
999- ) }
1000- </ div >
1001- </ ProgramSheetAccordionContent >
1002- </ ProgramSheetAccordionItem >
859+ < BountyCriteriaSection
860+ rewardType = { rewardType }
861+ setRewardType = { setRewardType }
862+ />
1003863
1004864 { type === "submission" && (
1005865 < ProgramSheetAccordionItem value = "submission-requirements" >
0 commit comments