Skip to content

Commit

Permalink
feat: refactor component, remove unused isButton prop and logic from …
Browse files Browse the repository at this point in the history
…component and references in apps #913
  • Loading branch information
clintonlunn committed Aug 23, 2023
1 parent d64a98d commit 513c6c0
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 111 deletions.
11 changes: 9 additions & 2 deletions src/components/ui/micro/AlertDialogue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ interface LeanAlertProps {
children?: ReactNode
className?: string
stackChildren?: boolean
onEscapeKeyDown?: () => void
}
/**
* A reusable popup alert
Expand All @@ -151,12 +152,18 @@ interface LeanAlertProps {
* @param cancelAction A button of type `AlertDialogPrimitive.Action` that closes the alert on click. You can register an `onClick()` to perform some action.
* @param noncancelAction Any kind of React component/button that doesn't close the alert on click. Use this if you want to perform an action on click and keep the alert open.
*/
export const LeanAlert = ({ icon = null, title = null, description = null, children = DefaultOkButton, closeOnEsc = true, className = '', stackChildren = false }: LeanAlertProps): JSX.Element => {
export const LeanAlert = ({ icon = null, title = null, description = null, children = DefaultOkButton, closeOnEsc = true, onEscapeKeyDown = () => {}, className = '', stackChildren = false }: LeanAlertProps): JSX.Element => {
return (
<AlertDialogPrimitive.Root defaultOpen>
<AlertDialogPrimitive.Overlay className='fixed inset-0 bg-black/60 z-50' />
<AlertDialogPrimitive.Content
onEscapeKeyDown={e => !closeOnEsc && e.preventDefault()}
onEscapeKeyDown={e => {
if (!closeOnEsc) {
e.preventDefault()
} else {
onEscapeKeyDown()
}
}}
className='z-50 fixed h-screen inset-0 mx-auto flex items-center justify-center px-2 lg:px-0 text-center overflow-y-auto max-w-xs md:max-w-md lg:max-w-lg'
>
<div className={`p-4 rounded-box bg-base-100 w-full ${className}`}>
Expand Down
150 changes: 57 additions & 93 deletions src/components/users/ImportFromMtnProj.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Fragment, useEffect, useState } from 'react'
import { useState } from 'react'
import { useRouter } from 'next/router'
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
import { FolderArrowDownIcon } from '@heroicons/react/24/outline'
Expand All @@ -14,21 +14,18 @@ import Spinner from '../ui/Spinner'
import { LeanAlert } from '../ui/micro/AlertDialogue'

interface Props {
isButton: boolean
username: string
}
// regex pattern to validate mountain project input
const pattern = /^https:\/\/www.mountainproject.com\/user\/\d{9}\/[a-zA-Z-]*/
const pattern = /^https:\/\/www.mountaainproject.com\/user\/\d{9}\/[a-zA-Z-]*/

/**
*
* @prop isButton -- a true or false value
* @prop username -- the openbeta username of the user
*
* if the isButton prop is true, the component will be rendered as a button
* if the isButton prop is false, the component will be rendered as a modal
* @returns JSX element
*/
export function ImportFromMtnProj ({ isButton, username }: Props): JSX.Element {
export function ImportFromMtnProj ({ username }: Props): JSX.Element {
const router = useRouter()
const [mpUID, setMPUID] = useState('')
const session = useSession()
Expand All @@ -42,39 +39,34 @@ export function ImportFromMtnProj ({ isButton, username }: Props): JSX.Element {
errorPolicy: 'none'
})

async function updateUserMetadata (method: 'PUT' | 'POST', body?: string): Promise<Response> {
async function fetchMPData (url: string, method: 'GET' | 'POST' | 'PUT' = 'GET', body?: string): Promise<any> {
try {
const response = await fetch('/api/user/ticks', {
const headers = {
'Content-Type': 'application/json'
}
const config: RequestInit = {
method,
body,
headers: {
'Content-Type': 'application/json'
}
})
return response
} catch (error) {
console.error('Error updating user metadata:', error)
throw new Error('Network error occurred')
}
}
headers
}

async function dontShowAgain (): Promise<void> {
setLoading(true)
if (body !== null && body !== undefined && body !== '') {
config.body = JSON.stringify(body)
}

try {
const response = await updateUserMetadata('PUT')
const response = await fetch(url, config)

if (response.status === 200) {
setShow(false)
} else {
if (!response.ok) {
const errorData = await response.json()
const errorMessage = errorData.error ?? 'Sorry, something went wrong. Please try again later'
setErrors([errorMessage])
throw new Error(errorData.statusText)
}

return await response.json()
} catch (error) {
setErrors(['Sorry, something went wrong. Please check your network and try again.'])
} finally {
setLoading(false)
if (error instanceof Error) {
console.error('Fetch error:', error.message)
throw error
}
throw new Error('An unexpected error occurred')
}
}

Expand All @@ -93,34 +85,38 @@ export function ImportFromMtnProj ({ isButton, username }: Props): JSX.Element {
setErrors([])
if (pattern.test(mpUID)) {
setLoading(true)
const res = await fetch('/api/user/ticks', {
method: 'POST',
body: JSON.stringify(mpUID)
})
if (res.status === 200) {
setShow(false)
const { ticks } = await res.json()
await addTicks({
variables: {
input: ticks
}
})

// Add a delay before rerouting to the new page
const ticksCount: number = ticks?.length ?? 0
toast.info(
<>
{ticksCount} ticks have been imported! 🎉 <br />
Redirecting in a few seconds...`
</>
)

setTimeout(() => {
void router.replace(`/u2/${username}`)
}, 2000)
} else {
setErrors(['Sorry, something went wrong. Please try again later'])
toast.error("We couldn't import your ticks. Please try again later.")

try {
const response = await fetchMPData('/api/user/ticks', 'POST', JSON.stringify(mpUID))

if (response.ticks[0] !== undefined) {
await addTicks({
variables: {
input: response.ticks
}
})
// Add a delay before rerouting to the new page
const ticksCount: number = response.ticks?.length ?? 0
toast.info(
<>
{ticksCount} ticks have been imported! 🎉 <br />
Redirecting in a few seconds...`
</>
)

setTimeout(() => {
void router.replace(`/u2/${username}`)
}, 2000)
setShow(false)
} else {
setErrors(['Sorry, no ticks were found for that user. Please check your Mountain Project ID and try again.'])
toast.error('Sorry, no ticks were found for that user. Please check your Mountain Project ID and try again.')
}
} catch (error) {
toast.error('Sorry, something went wrong. Please check your network and try again.')
setErrors(['Sorry, something went wrong. Please check your network and try again.'])
} finally {
setLoading(false)
}
} else {
// handle errors
Expand All @@ -129,31 +125,9 @@ export function ImportFromMtnProj ({ isButton, username }: Props): JSX.Element {
setLoading(false)
}

useEffect(() => {
// if we aren't rendering this component as a button
// and the user is authenticated we want to show the import your ticks modal
// then we check to see if they have a ticks imported flag set
// if it is, set show to the opposite of whatever it is
// otherwise don't show the modal
if (!isButton) {
fetch('/api/user/profile')
.then(async res => await res.json())
.then((profile) => {
if (profile?.ticksImported !== null) {
setShow(profile.ticksImported !== true)
} else if (session.status === 'authenticated') {
setShow(true)
} else {
setShow(false)
}
}).catch(console.error)
}
}, [session])

// if the isButton prop is passed to this component as true, the component will be rendered as a button, otherwise it will be a modal
return (
<>
{isButton && <button onClick={straightToInput} className='btn btn-xs md:btn-sm btn-primary'>Import ticks</button>}
<button onClick={straightToInput} className='btn btn-xs md:btn-sm btn-primary'>Import ticks</button>

{show && (
<LeanAlert
Expand Down Expand Up @@ -202,16 +176,6 @@ export function ImportFromMtnProj ({ isButton, username }: Props): JSX.Element {
</button>
)}

{!isButton && (
<button
type='button'
onClick={dontShowAgain}
className='bg-white rounded-md text-sm font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500'
>
{loading ? 'Working...' : "Don't show again"}
</button>
)}

<AlertDialogPrimitive.Cancel
asChild onClick={() => {
setShow(false)
Expand Down
2 changes: 1 addition & 1 deletion src/components/users/PublicProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default function PublicProfile ({ userProfile }: PublicProfileProps): JSX
<div className='btn btn-outline btn-xs md:btn-sm'> View ticks</div>
</a>
</Link>}
{username != null && isAuthorized && <ImportFromMtnProj isButton username={username} />}
{username != null && isAuthorized && <ImportFromMtnProj username={username} />}
{userProfile != null && <EditProfileButton userUuid={userProfile?.userUuid} />}
{userProfile != null && <APIKeyCopy userUuid={userProfile.userUuid} />}
</div>
Expand Down
17 changes: 3 additions & 14 deletions src/components/users/__tests__/ImportFromMtnProj.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,15 @@ describe('<ImportFromMtnProj />', () => {
it('renders without crashing', () => {
render(
<MockedProvider mocks={[]} addTypename={false}>
<ImportFromMtnProj isButton username='testuser' />
<ImportFromMtnProj username='testuser' />
</MockedProvider>
)
})

it('renders a button when isButton prop is true', () => {
render(
<MockedProvider mocks={[]} addTypename={false}>
<ImportFromMtnProj isButton username='testuser' />
</MockedProvider>
)

const button = screen.getByText('Import ticks')
expect(button).toBeInTheDocument()
})

it('renders modal on button click', async () => {
render(
<MockedProvider mocks={[]} addTypename={false}>
<ImportFromMtnProj isButton username='testuser' />
<ImportFromMtnProj username='testuser' />
</MockedProvider>
)

Expand All @@ -65,7 +54,7 @@ describe('<ImportFromMtnProj />', () => {
})

it('accepts input for the Mountain Project profile link', async () => {
render(<ImportFromMtnProj isButton username='testuser' />
render(<ImportFromMtnProj username='testuser' />
)

// Simulate a click to open the modal.
Expand Down
2 changes: 1 addition & 1 deletion src/pages/u2/[...slug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const Index: NextPage<TicksIndexPageProps> = ({ username, ticks }) => {
<section className='max-w-lg mx-auto w-full px-4 py-8'>
<h2>{username}</h2>
<div className='py-4 flex items-center gap-6'>
<ImportFromMtnProj isButton username={username} />
<ImportFromMtnProj username={username} />
<a className='btn btn-xs md:btn-sm btn-outline' href={`/u/${username}`}>Classic Profile</a>
</div>

Expand Down

0 comments on commit 513c6c0

Please sign in to comment.