Non-quad tip UI (WIP)
This commit is contained in:
		
							parent
							
								
									67d0a6c0c2
								
							
						
					
					
						commit
						4f28743252
					
				|  | @ -1,3 +1,4 @@ | |||
| import { XIcon } from '@heroicons/react/outline' | ||||
| import { | ||||
|   ChevronDoubleRightIcon, | ||||
|   ChevronLeftIcon, | ||||
|  | @ -7,7 +8,7 @@ import clsx from 'clsx' | |||
| import { Comment } from 'common/comment' | ||||
| import { User } from 'common/user' | ||||
| import { formatMoney } from 'common/util/format' | ||||
| import { debounce, sumBy } from 'lodash' | ||||
| import { debounce, sum, sumBy } from 'lodash' | ||||
| import { useEffect, useMemo, useRef, useState } from 'react' | ||||
| import { CommentTips } from 'web/hooks/use-tip-txns' | ||||
| import { useUser } from 'web/hooks/use-user' | ||||
|  | @ -16,33 +17,24 @@ import { track } from 'web/lib/service/analytics' | |||
| import { Row } from './layout/row' | ||||
| import { Tooltip } from './tooltip' | ||||
| 
 | ||||
| // xth triangle number * 5  =  5 + 10 + 15 + ... + (x * 5)
 | ||||
| const quad = (x: number) => (5 / 2) * x * (x + 1) | ||||
| 
 | ||||
| // inverse (see https://math.stackexchange.com/questions/2041988/how-to-get-inverse-of-formula-for-sum-of-integers-from-1-to-nsee )
 | ||||
| const invQuad = (y: number) => Math.sqrt((2 / 5) * y + 1 / 4) - 1 / 2 | ||||
| 
 | ||||
| export function Tipper(prop: { comment: Comment; tips: CommentTips }) { | ||||
|   const { comment, tips } = prop | ||||
| 
 | ||||
|   const me = useUser() | ||||
|   const myId = me?.id ?? '' | ||||
|   const savedTip = tips[myId] as number | undefined | ||||
|   const savedTip = tips[myId] ?? 0 | ||||
| 
 | ||||
|   // optimistically increase the tip count, but debounce the update
 | ||||
|   const [localTip, setLocalTip] = useState(savedTip ?? 0) | ||||
|   const [localTip, setLocalTip] = useState(savedTip) | ||||
|   // listen for user being set
 | ||||
|   const initialized = useRef(false) | ||||
|   useEffect(() => { | ||||
|     if (savedTip && !initialized.current) { | ||||
|       setLocalTip(savedTip) | ||||
|     if (tips[myId] && !initialized.current) { | ||||
|       setLocalTip(tips[myId]) | ||||
|       initialized.current = true | ||||
|     } | ||||
|   }, [savedTip]) | ||||
|   }, [tips, myId]) | ||||
| 
 | ||||
|   const score = useMemo(() => { | ||||
|     const tipVals = Object.values({ ...tips, [myId]: localTip }) | ||||
|     return sumBy(tipVals, invQuad) | ||||
|   }, [localTip, tips, myId]) | ||||
|   const total = sum(Object.values(tips)) - savedTip + localTip | ||||
| 
 | ||||
|   // declare debounced function only on first render
 | ||||
|   const [saveTip] = useState(() => | ||||
|  | @ -80,87 +72,33 @@ export function Tipper(prop: { comment: Comment; tips: CommentTips }) { | |||
| 
 | ||||
|   const changeTip = (tip: number) => { | ||||
|     setLocalTip(tip) | ||||
|     me && saveTip(me, tip - (savedTip ?? 0)) | ||||
|     me && saveTip(me, tip - savedTip) | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <Row className="items-center gap-0.5"> | ||||
|       <DownTip | ||||
|         value={localTip} | ||||
|         onChange={changeTip} | ||||
|         disabled={!me || localTip <= 0} | ||||
|       /> | ||||
|       <span className="font-bold">{Math.floor(score)} </span> | ||||
|       <UpTip | ||||
|         value={localTip} | ||||
|         onChange={changeTip} | ||||
|         disabled={!me || me.id === comment.userId} | ||||
|       /> | ||||
|       {localTip === 0 ? ( | ||||
|         '' | ||||
|       ) : ( | ||||
|         <span | ||||
|           className={clsx( | ||||
|             'font-semibold', | ||||
|             localTip > 0 ? 'text-primary' : 'text-red-400' | ||||
|           )} | ||||
|         > | ||||
|           ({formatMoney(localTip)} tip) | ||||
|         </span> | ||||
|     <Row className="items-center gap-2"> | ||||
|       {total > 0 && <span className="font-normal">{total}</span>} | ||||
| 
 | ||||
|       <button | ||||
|         className="font-bold disabled:text-gray-300" | ||||
|         disabled={ | ||||
|           !me || | ||||
|           me.id === comment.userId || | ||||
|           me.balance < localTip - savedTip + 5 | ||||
|         } | ||||
|         onClick={() => changeTip(localTip + 5)} | ||||
|       > | ||||
|         Tip | ||||
|       </button> | ||||
|       {localTip > 0 && ( | ||||
|         <span className="text-primary font-semibold">(+{localTip})</span> | ||||
|       )} | ||||
|       {/* undo button */} | ||||
|       {localTip > 0 && ( | ||||
|         <button className="text-red-500" onClick={() => changeTip(0)}> | ||||
|           <XIcon className="w-4" /> | ||||
|         </button> | ||||
|       )} | ||||
|     </Row> | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| function DownTip(prop: { | ||||
|   value: number | ||||
|   onChange: (tip: number) => void | ||||
|   disabled?: boolean | ||||
| }) { | ||||
|   const { onChange, value, disabled } = prop | ||||
|   const marginal = 5 * invQuad(value) | ||||
|   return ( | ||||
|     <Tooltip | ||||
|       className="tooltip-bottom" | ||||
|       text={!disabled && `Refund ${formatMoney(marginal)}`} | ||||
|     > | ||||
|       <button | ||||
|         className="flex h-max items-center hover:text-red-600 disabled:text-gray-300" | ||||
|         disabled={disabled} | ||||
|         onClick={() => onChange(value - marginal)} | ||||
|       > | ||||
|         <ChevronLeftIcon className="h-6 w-6" /> | ||||
|       </button> | ||||
|     </Tooltip> | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| function UpTip(prop: { | ||||
|   value: number | ||||
|   onChange: (tip: number) => void | ||||
|   disabled?: boolean | ||||
| }) { | ||||
|   const { onChange, value, disabled } = prop | ||||
|   const marginal = 5 * invQuad(value) + 5 | ||||
| 
 | ||||
|   return ( | ||||
|     <Tooltip | ||||
|       className="tooltip-bottom" | ||||
|       text={!disabled && `Tip ${formatMoney(marginal)}`} | ||||
|     > | ||||
|       <button | ||||
|         className="hover:text-primary flex h-max items-center disabled:text-gray-300" | ||||
|         disabled={disabled} | ||||
|         onClick={() => onChange(value + marginal)} | ||||
|       > | ||||
|         {value >= quad(2) ? ( | ||||
|           <ChevronDoubleRightIcon className="text-primary mx-1 h-6 w-6" /> | ||||
|         ) : value > 0 ? ( | ||||
|           <ChevronRightIcon className="text-primary h-6 w-6" /> | ||||
|         ) : ( | ||||
|           <ChevronRightIcon className="h-6 w-6" /> | ||||
|         )} | ||||
|       </button> | ||||
|     </Tooltip> | ||||
|   ) | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user