Skip to content

Commit

Permalink
feat: address PR comments
Browse files Browse the repository at this point in the history
* use phosphor icons
* use existing Tooltip
* add 'hover' option to Tooltip
* make front-end and back-end logic the same
* add Alpine option when creating routes
  • Loading branch information
glassbead0 committed Jan 30, 2025
1 parent 0a59541 commit 87e4b61
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
'use client'
import { ArrowsVertical } from '@phosphor-icons/react/dist/ssr'

import TickButton from '@/components/users/TickButton'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export const DisciplinesSelection: React.FC<FieldArrayInputProps> = ({ formConte
<Checkbox label='Mixed' index={index} discipline='mixed' formContext={formContext} />
<Checkbox label='Ice' index={index} discipline='ice' formContext={formContext} />
<Checkbox label='Snow' index={index} discipline='snow' formContext={formContext} />
<Checkbox label='Alpine' index={index} discipline='alpine' formContext={formContext} />
</div>

<div className='self-end'>
Expand Down
16 changes: 12 additions & 4 deletions src/components/ui/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
import * as Popover from '@radix-ui/react-popover'
import { ReactNode } from 'react'
import { ReactNode, useState } from 'react'

interface Props {
content: string | ReactNode
enabled?: boolean
children: JSX.Element | JSX.Element [] | null
className?: string
trigger?: 'click' | 'hover'
}

/**
* A Tooltip that activates on mouse click or touch.
* @param enabled false to disable tooltip but still render the trigger element
* @param children Trigger element
* @param trigger 'click' to activate on click, 'hover' to activate on hover. defaults to click
*/
export default function Tooltip ({ content, enabled = true, className = '', children }: Props): JSX.Element {
export default function Tooltip ({ content, enabled = true, className = '', children, trigger = 'click' }: Props): JSX.Element {
const [open, setOpen] = useState(false)
const handleMouseEnter = (): void => { if (trigger === 'hover') { setOpen(true) } }
const handleMouseLeave = (): void => { if (trigger === 'hover') { setOpen(false) } }

return (
<Popover.Root>
<Popover.Trigger className={className}>{children}</Popover.Trigger>
<Popover.Root open={trigger === 'hover' ? open : undefined} onOpenChange={setOpen}>
<Popover.Trigger className={className} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
{children}
</Popover.Trigger>
{enabled &&
<Content>
{content}
Expand Down
56 changes: 29 additions & 27 deletions src/components/users/TickForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { Fragment, useState } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { useMutation } from '@apollo/client'
import { useSession } from 'next-auth/react'
import { TickType } from '../../js/types'
import { graphqlClient } from '../../js/graphql/Client'
import { MUTATION_ADD_TICK } from '../../js/graphql/gql/fragments'
import { TickType } from '@/js/types'
import Tooltip from '../ui/Tooltip'
import { graphqlClient } from '@/js/graphql/Client'
import { MUTATION_ADD_TICK } from '@/js/graphql/gql/fragments'
import ComboBox from '../ui/ComboBox'
import * as Yup from 'yup'
import { InformationCircleIcon } from '@heroicons/react/20/solid'
import { Info } from '@phosphor-icons/react/dist/ssr'

// validation schema for ticks
const TickSchema = Yup.object().shape({
Expand All @@ -25,13 +26,12 @@ const TickSchema = Yup.object().shape({
dateClimbed: Yup.date()
.required('Please include a date')
.max(new Date(), 'Please include a date in the past'),
grade: Yup.string()
.required('Something went wrong fetching the climbs grade, please try again')
grade: Yup.string(),
})

/**
* Options for the dropdown comboboxes
* The logic here can get quite complicated. There is a hierarchy of "Climb type" -> "Style" -> "Attempt Type"
* Tick validation logic is complicated. see [tick_logic.md](https://github.com/OpenBeta/openbeta-graphql/blob/develop/documentation/tick_logic.md).
**/
const allStyles = [
{ id: 1, name: 'Lead' },
Expand All @@ -54,36 +54,36 @@ const allAttemptTypes = [

function hasKey (climbType: object, myList: string[]): boolean { return Object.keys(climbType).some(key => myList.includes(key)) }

function leadable (climbType: object): boolean { return hasKey(climbType, ['trad', 'sport', 'snow', 'ice', 'mixed', 'alpine']) }
function topropeable (climbType: object): boolean { return hasKey(climbType, ['tr']) || (leadable(climbType)) }
function aidable (climbType: object): boolean { return hasKey(climbType, ['aid']) }
function soloable (climbType: object): boolean { return hasKey(climbType, ['deepwatersolo']) || leadable(climbType) || aidable(climbType) || topropeable(climbType) }
function boulderable (climbType: object): boolean { return hasKey(climbType, ['bouldering']) }

function stylesForClimbType (climbType: object): Array<{ id: number, name: string }> {
const leadable = hasKey(climbType, ['trad', 'sport', 'snow', 'ice', 'mixed', 'alpine'])
const topropeable = hasKey(climbType, ['tr']) || (leadable)
const aidable = hasKey(climbType, ['aid'])
const boulderable = hasKey(climbType, ['bouldering'])
const soloable = hasKey(climbType, ['deepwatersolo']) || leadable || aidable || (topropeable && !boulderable)

let styles: Array<{ id: number, name: string }> = []
if (leadable(climbType)) { styles.push(...allStyles.filter(style => ['Lead', 'Follow'].includes(style.name))) }
if (topropeable(climbType)) { styles.push(...allStyles.filter(style => ['TR'].includes(style.name))) }
if (soloable(climbType)) { styles.push(...allStyles.filter(style => ['Solo'].includes(style.name))) }
if (boulderable(climbType)) { styles.push(...allStyles.filter(style => ['Boulder'].includes(style.name))) }
if (aidable(climbType)) { styles.push(...allStyles.filter(style => ['Aid'].includes(style.name))) }

if (leadable) { styles.push(...allStyles.filter(style => ['Lead', 'Follow'].includes(style.name))) }
if (boulderable) { styles.push(...allStyles.filter(style => ['Boulder'].includes(style.name))) }
if (topropeable) { styles.push(...allStyles.filter(style => ['TR'].includes(style.name))) }
if (aidable) { styles.push(...allStyles.filter(style => ['Aid'].includes(style.name))) }
if (soloable) { styles.push(...allStyles.filter(style => ['Solo'].includes(style.name))) }
if (styles.length === 0) { styles = [...allStyles] } // If a climb doesn't have a type, anything goes
styles.push({ id: 0, name: '\u00A0' })
styles.push({ id: 10, name: '\u00A0' })
return styles
}

function attemptTypesForStyle (styleName: string): Array<{ id: number, name: string }> {
const emptyOption = { id: 0, name: '\u00A0' }
const emptyOption = { id: 10, name: '\u00A0' }
switch (styleName) {
case 'Lead':
return [...allAttemptTypes.filter(type => ['Onsight', 'Flash', 'Redpoint', 'Pinkpoint', 'Attempt', 'Frenchfree'].includes(type.name)), emptyOption]
case 'Solo':
return [...allAttemptTypes.filter(type => ['Onsight', 'Flash', 'Redpoint', 'Attempt'].includes(type.name)), emptyOption]
case 'TR':
case 'Follow':
return [...allAttemptTypes.filter(type => ['Send', 'Attempt', 'Frenchfree'].includes(type.name)), emptyOption]
case 'Boulder':
return [...allAttemptTypes.filter(type => ['Flash', 'Send', 'Attempt'].includes(type.name)), emptyOption]
case 'TR':
case 'Follow':
case 'Aid':
return [...allAttemptTypes.filter(type => ['Send', 'Attempt'].includes(type.name)), emptyOption]
default:
Expand Down Expand Up @@ -147,8 +147,8 @@ export default function TickForm ({ open, setOpen, setTicks, ticks, isTicked, cl
notes,
climbId,
userId: session.data?.user.metadata.uuid,
style: style.name,
attemptType: attemptType.name,
style: style.name === '\u00A0' ? undefined : style.name,
attemptType: attemptType.name == '\u00A0' ? undefined : attemptType.name,
dateClimbed: new Date(Date.parse(`${dateClimbed}T00:00:00`)), // Date.parse without timezone converts dateClimbed into local timezone.
grade,
source: 'OB' // source manually set as Open Beta
Expand Down Expand Up @@ -240,8 +240,10 @@ export default function TickForm ({ open, setOpen, setTicks, ticks, isTicked, cl
<label htmlFor='attemptType' className='block text-sm font-medium text-gray-700'>
Attempt Type
</label>
<a href={climbingGlossaryLink} target='_blank' rel='noopener noreferrer' className='ml-2 text-gray-500 hover:text-gray-700' title='climbing terminology'>
<InformationCircleIcon className='h-5 w-5' />
<a href={climbingGlossaryLink} target='_blank' rel='noreferrer' className='ml-2 mt-1'>
<Tooltip content='Glossary of Climbing Terms' trigger='hover'>
<Info className='h-5 w-5' />
</Tooltip>
</a>
</div>
<ComboBox options={attemptTypesForStyle(style.name)} value={attemptType} onChange={setAttemptType} label='' />
Expand Down

0 comments on commit 87e4b61

Please sign in to comment.