From 939083e3a943a0be5ba0e8b2cd3b7d9508d2645e Mon Sep 17 00:00:00 2001 From: Michael Schultz Date: Sat, 27 Dec 2025 23:51:07 -0800 Subject: [PATCH 01/15] replaces geist and styled-components with tailwind --- biome.json | 6 + components/Modal.tsx | 81 +++ components/Popover.tsx | 165 +++++ components/Tabs.tsx | 108 ++++ components/Tooltip.tsx | 80 +++ components/blog/blogFooter.tsx | 47 +- components/blog/postFooter.tsx | 70 ++- components/descriptionCards.tsx | 117 ++-- components/emergencyCard.tsx | 211 +++---- components/emergencyInfo.tsx | 117 ++-- components/emergencySnippet.tsx | 36 +- components/feedbackFishFooter.tsx | 24 +- components/feedbackModal.tsx | 136 ++-- components/feedbackPage.tsx | 77 +-- components/footer.tsx | 79 +-- components/header.tsx | 175 +++--- components/homePage.tsx | 122 ++-- components/identity.tsx | 44 +- components/infusionModal.tsx | 462 +++++++------- components/infusionTable.tsx | 511 +++++++++------ components/loadingScreen.tsx | 15 +- components/logo.tsx | 22 +- components/profilePage.tsx | 222 +++---- components/settingsForm.tsx | 286 +++++---- components/statCard.tsx | 34 +- components/staticHeader.tsx | 49 +- components/stats.tsx | 185 +++--- firestore.dev.rules | 10 + firestore.prod.rules | 18 + firestore.rules | 26 +- lib/contexts/ThemeContext.tsx | 58 ++ lib/hooks/useMediaQuery.ts | 33 + lib/hooks/useModal.ts | 27 + package.json | 14 +- pages/_app.tsx | 81 +-- pages/_document.tsx | 37 +- pages/about.tsx | 218 ++++--- pages/changelog/hello-world-again.tsx | 121 ++-- pages/changelog/index.tsx | 271 ++++---- pages/changelog/mobile-enhancements.tsx | 175 +++--- pages/changelog/monoclonal-antibodies.tsx | 155 +++-- pages/changelog/raycast-extension.tsx | 213 ++++--- pages/emergency/[alertId].tsx | 118 ++-- pages/emergency/print.tsx | 93 ++- pages/home.tsx | 67 +- pages/index.tsx | 111 ++-- pages/signin.tsx | 139 ++--- pnpm-lock.yaml | 721 ++++++++++++++-------- postcss.config.cjs | 6 + styles/globals.css | 71 +++ switch-rules.sh | 21 + tsconfig.tsbuildinfo | 1 - 52 files changed, 3541 insertions(+), 2745 deletions(-) create mode 100644 components/Modal.tsx create mode 100644 components/Popover.tsx create mode 100644 components/Tabs.tsx create mode 100644 components/Tooltip.tsx create mode 100644 firestore.dev.rules create mode 100644 firestore.prod.rules create mode 100644 lib/contexts/ThemeContext.tsx create mode 100644 lib/hooks/useMediaQuery.ts create mode 100644 lib/hooks/useModal.ts create mode 100644 postcss.config.cjs create mode 100644 styles/globals.css create mode 100755 switch-rules.sh delete mode 100644 tsconfig.tsbuildinfo diff --git a/biome.json b/biome.json index 4afb514..dfc26e9 100644 --- a/biome.json +++ b/biome.json @@ -12,6 +12,7 @@ "**/*.tsx", "**/*.js", "**/*.jsx", + "**/*.css", "!**/*.min.js", "!**/node_modules", "!**/dist", @@ -45,6 +46,11 @@ "trailingCommas": "es5" } }, + "css": { + "parser": { + "tailwindDirectives": true + } + }, "assist": { "enabled": true, "actions": { diff --git a/components/Modal.tsx b/components/Modal.tsx new file mode 100644 index 0000000..dc0a9bc --- /dev/null +++ b/components/Modal.tsx @@ -0,0 +1,81 @@ +import type React from 'react' + +export interface ModalProps { + visible: boolean + onClose?: () => void + children: React.ReactNode + title?: string +} + +export function Modal({ + visible, + onClose, + children, + title, +}: ModalProps): JSX.Element | null { + if (!visible) { + return null + } + + const handleBackdropClick = ( + event: React.MouseEvent + ): void => { + if (event.target === event.currentTarget && onClose) { + onClose() + } + } + + const handleKeyDown = (event: React.KeyboardEvent): void => { + if (event.key === 'Escape' && onClose) { + onClose() + } + } + + return ( +
+
+ {title && ( +
+ + {onClose && ( + + )} +
+ )} +
{children}
+
+
+ ) +} diff --git a/components/Popover.tsx b/components/Popover.tsx new file mode 100644 index 0000000..09208f4 --- /dev/null +++ b/components/Popover.tsx @@ -0,0 +1,165 @@ +import type React from 'react' +import { useState, useRef, useEffect } from 'react' + +export type PopoverPlacement = + | 'top' + | 'bottom' + | 'left' + | 'right' + | 'top-start' + | 'top-end' + | 'bottom-start' + | 'bottom-end' + | 'left-start' + | 'left-end' + | 'right-start' + | 'right-end' + +export interface PopoverItemProps { + children: React.ReactNode + onClick?: () => void + disabled?: boolean + title?: boolean + line?: boolean +} + +export interface PopoverContentProps { + children: React.ReactNode +} + +export interface PopoverProps { + content: React.ReactNode + children: React.ReactElement + placement?: PopoverPlacement + trigger?: 'click' | 'hover' +} + +export function PopoverItem({ + children, + onClick, + disabled, + title, + line, +}: PopoverItemProps): JSX.Element { + const baseClasses = 'w-full px-4 py-2 text-left text-sm transition-colors' + + const classes = title + ? `${baseClasses} font-semibold text-gray-900 dark:text-white border-b border-gray-200 dark:border-gray-700` + : line + ? `${baseClasses} border-t border-gray-200 dark:border-gray-700` + : disabled + ? `${baseClasses} text-gray-400 dark:text-gray-500 cursor-not-allowed` + : `${baseClasses} text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 cursor-pointer` + + const handleClick = (): void => { + if (!disabled && onClick) { + onClick() + } + } + + return ( + + ) +} + +export function PopoverContent({ children }: PopoverContentProps): JSX.Element { + return
{children}
+} + +export function Popover({ + content, + children, + placement = 'bottom-end', + trigger = 'click', +}: PopoverProps): JSX.Element { + const [isOpen, setIsOpen] = useState(false) + const popoverRef = useRef(null) + const triggerRef = useRef(null) + + useEffect(() => { + const handleClickOutside = (event: MouseEvent): void => { + if ( + popoverRef.current && + !popoverRef.current.contains(event.target as Node) && + triggerRef.current && + !triggerRef.current.contains(event.target as Node) + ) { + setIsOpen(false) + } + } + + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside) + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside) + } + }, [isOpen]) + + const handleTriggerClick = (): void => { + if (trigger === 'click') { + setIsOpen(!isOpen) + } + } + + const handleTriggerMouseEnter = (): void => { + if (trigger === 'hover') { + setIsOpen(true) + } + } + + const handleTriggerMouseLeave = (): void => { + if (trigger === 'hover') { + setIsOpen(false) + } + } + + const getPopoverClasses = (): string => { + const baseClasses = + 'absolute z-50 mt-2 w-56 rounded-md shadow-lg bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 focus:outline-none' + + const placementClasses = { + top: 'bottom-full mb-2', + 'top-start': 'bottom-full right-0 mb-2', + 'top-end': 'bottom-full left-0 mb-2', + bottom: 'top-full mt-2', + 'bottom-start': 'top-full right-0 mt-2', + 'bottom-end': 'top-full left-0 mt-2', + left: 'right-full mr-2 top-1/2 transform -translate-y-1/2', + 'left-start': 'right-full mr-2 top-0', + 'left-end': 'right-full mr-2 bottom-0', + right: 'left-full ml-2 top-1/2 transform -translate-y-1/2', + 'right-start': 'left-full ml-2 top-0', + 'right-end': 'left-full ml-2 bottom-0', + } + + return `${baseClasses} ${placementClasses[placement]} ${isOpen ? 'block' : 'hidden'}` + } + + return ( +
+ + +
+ {content} +
+
+ ) +} diff --git a/components/Tabs.tsx b/components/Tabs.tsx new file mode 100644 index 0000000..697c6ae --- /dev/null +++ b/components/Tabs.tsx @@ -0,0 +1,108 @@ +import React, { useState } from 'react' + +export interface TabsProps { + initialValue?: string + value?: string + onChange?: (value: string) => void + children: React.ReactNode + className?: string +} + +export interface TabsItemProps { + label: string + value: string + children: React.ReactNode +} + +export function TabsItem({ children }: TabsItemProps): JSX.Element { + return <>{children} +} + +export function Tabs({ + initialValue, + value, + onChange, + children, + className = '', +}: TabsProps): JSX.Element { + const [activeTab, setActiveTab] = useState(initialValue || '') + + const currentValue = value !== undefined ? value : activeTab + + const handleTabChange = (newValue: string): void => { + if (value === undefined) { + setActiveTab(newValue) + } + if (onChange) { + onChange(newValue) + } + } + + const tabItems: { + label: string + value: string + children: React.ReactNode + }[] = [] + + React.Children.forEach(children, (child) => { + if (React.isValidElement(child) && child.type === TabsItem) { + const { + label, + value: tabValue, + children: tabChildren, + } = child.props as TabsItemProps + tabItems.push({ label, value: tabValue, children: tabChildren }) + } + }) + + const activeTabContent = tabItems.find( + (item) => item.value === currentValue + )?.children + + return ( + <> +
+ +
+ {activeTabContent} + + ) +} + +export interface TabsContentProps { + children: React.ReactNode + className?: string +} + +export function TabsContent({ + children, + className = '', +}: TabsContentProps): JSX.Element { + return
{children}
+} diff --git a/components/Tooltip.tsx b/components/Tooltip.tsx new file mode 100644 index 0000000..2d5d31f --- /dev/null +++ b/components/Tooltip.tsx @@ -0,0 +1,80 @@ +import React, { useState, useRef } from 'react' + +export type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right' + +export interface TooltipProps { + content: string + children: React.ReactElement + placement?: TooltipPlacement + delay?: number +} + +export function Tooltip({ + content, + children, + placement = 'top', + delay = 200, +}: TooltipProps): JSX.Element { + const [isVisible, setIsVisible] = useState(false) + const timeoutRef = useRef(null) + + const showTooltip = (): void => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + } + timeoutRef.current = setTimeout(() => setIsVisible(true), delay) + } + + const hideTooltip = (): void => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + } + setIsVisible(false) + } + + const getTooltipClasses = (): string => { + const baseClasses = + 'absolute z-50 px-3 py-2 text-sm text-white bg-gray-900 dark:bg-gray-700 rounded-md shadow-md whitespace-nowrap pointer-events-none transition-opacity duration-200' + + const placementClasses = { + top: 'bottom-full left-1/2 transform -translate-x-1/2 mb-2', + bottom: 'top-full left-1/2 transform -translate-x-1/2 mt-2', + left: 'right-full top-1/2 transform -translate-y-1/2 mr-2', + right: 'left-full top-1/2 transform -translate-y-1/2 ml-2', + } + + return `${baseClasses} ${placementClasses[placement]} ${isVisible ? 'opacity-100' : 'opacity-0'}` + } + + const getArrowClasses = (): string => { + const baseClasses = + 'absolute w-2 h-2 bg-gray-900 dark:bg-gray-700 transform rotate-45' + + const arrowClasses = { + top: 'top-full left-1/2 transform -translate-x-1/2 -mt-1', + bottom: 'bottom-full left-1/2 transform -translate-x-1/2 -mb-1', + left: 'left-full top-1/2 transform -translate-y-1/2 -ml-1', + right: 'right-full top-1/2 transform -translate-y-1/2 -mr-1', + } + + return `${baseClasses} ${arrowClasses[placement]}` + } + + return ( +
+ {React.cloneElement(children, { + onMouseEnter: showTooltip, + onMouseLeave: hideTooltip, + onFocus: showTooltip, + onBlur: hideTooltip, + })} + + {isVisible && ( +
+ {content} +
+
+ )} +
+ ) +} diff --git a/components/blog/blogFooter.tsx b/components/blog/blogFooter.tsx index f7f25a0..8cc7aff 100644 --- a/components/blog/blogFooter.tsx +++ b/components/blog/blogFooter.tsx @@ -1,5 +1,5 @@ -import { Text, Image, Spacer, Grid, Button } from '@geist-ui/react' import { useRouter } from 'next/router' +import Image from 'next/image' import { useAuth } from 'lib/auth' @@ -8,39 +8,42 @@ export default function BlogFooter(): JSX.Element { const router = useRouter() return ( - - - +
+
+

Designed and developed by Michael Schultz in Oakland, California. - +

{user ? ( - Thanks for being part of the Hemolog community! +

+ Thanks for being part of the Hemolog community! +

) : ( <> - Start using Hemolog for free. - + )} - - - +
+ +
Michael Schultz - - +
+
) } diff --git a/components/blog/postFooter.tsx b/components/blog/postFooter.tsx index c69c834..ba270de 100644 --- a/components/blog/postFooter.tsx +++ b/components/blog/postFooter.tsx @@ -1,40 +1,50 @@ -import { - Spacer, - Grid, - User, - Divider, - useClipboard, - useToasts, -} from '@geist-ui/react' -import { Share } from '@geist-ui/react-icons' +import { IconShare } from '@tabler/icons-react' +import toast from 'react-hot-toast' export default function PostFooter({ postId }: { postId: string }) { - const [, setToast] = useToasts() - const { copy } = useClipboard() - const handleCopy = (postId: string) => { - copy(`https://hemolog.com/changelog#${postId}`) - setToast({ type: 'success', text: 'Link copied!' }) + const handleCopy = async (postId: string) => { + try { + await navigator.clipboard.writeText( + `https://hemolog.com/changelog#${postId}` + ) + toast.success('Link copied!') + } catch (err) { + console.error('Failed to copy link:', err) + toast.error('Failed to copy link') + } } return ( <> - - - - - - @michaelschultz - - - - -
- handleCopy(postId)} /> +
+
+
+
+ Michael Schultz +
+
Michael Schultz
+ + @michaelschultz + +
- - - - +
+
+ handleCopy(postId)} + /> +
+
+
+
) } diff --git a/components/descriptionCards.tsx b/components/descriptionCards.tsx index 857c4ac..338318d 100644 --- a/components/descriptionCards.tsx +++ b/components/descriptionCards.tsx @@ -1,62 +1,67 @@ -import { Text, Grid, Link, Card } from '@geist-ui/react' import Image from 'next/image' export default function DescrtipionCards(): JSX.Element { return ( - - - - - - Free forever - - - No sponsorships, pharma companies, or ads. - - - - - - - - Didn’t Hemolog die? - - - Yep, but it’s back. Just wait till you see what this reincarnation - can do! - - - - - - - - Safe and secure - - - Your data is stored in Firebase, a trused database owned by Google. - - - - - - - - Open source - - - Check out the code on{' '} - - Github - - . - - - - +
+
+ Free forever +

Free forever

+

+ No sponsorships, pharma companies, or ads. +

+
+ +
+ Hemolog is back +

Didn't Hemolog die?

+

+ Yep, but it's back. Just wait till you see what this reincarnation can + do! +

+
+ +
+ Safe and secure +

Safe and secure

+

+ Your data is stored in Firebase, a trusted database owned by Google. +

+
+ +
+ Open source +

Open source

+

+ Check out the code on{' '} + + Github + + . +

+
+
) } diff --git a/components/emergencyCard.tsx b/components/emergencyCard.tsx index 0ff88da..8403006 100644 --- a/components/emergencyCard.tsx +++ b/components/emergencyCard.tsx @@ -1,15 +1,5 @@ -import { useContext } from 'react' +import React from 'react' import Link from 'next/link' -import { - Grid, - Spacer, - Loading, - useTheme, - Tooltip, - Text, - useMediaQuery, -} from '@geist-ui/react' -import styled, { ThemeContext } from 'styled-components' import QRCode from 'react-qr-code' import { useAuth } from 'lib/auth' @@ -22,10 +12,19 @@ interface Props { export default function EmergencyCard({ forPrint }: Props): JSX.Element { const { user } = useAuth() const { person } = useDbUser(user?.uid || '') - const theme = useTheme() - // biome-ignore lint/suspicious/noExplicitAny: TODO: fix when moving to tailwind - const themeContext = useContext(ThemeContext) as any - const isMobile = useMediaQuery('xs', { match: 'down' }) + + // Check if we're on mobile - use a simple approach for now + const [isMobile, setIsMobile] = React.useState(false) + + React.useEffect(() => { + const checkMobile = () => { + setIsMobile(window.innerWidth < 640) // Tailwind's sm breakpoint + } + + checkMobile() + window.addEventListener('resize', checkMobile) + return () => window.removeEventListener('resize', checkMobile) + }, []) if (isMobile) { forPrint = true @@ -34,129 +33,109 @@ export default function EmergencyCard({ forPrint }: Props): JSX.Element { const alertUrl = `hemolog.com/emergency/${person?.alertId}` return ( - - - - -
+
+
+
+
+
Bleeding disorder
-

Emergency

- +

+ Emergency +

+
{user?.photoUrl && ( - - - +
+ User avatar +
)} - - - - - - +
+
+
+
+
+
{person ? ( ) : ( - +
+
+
)} - - - - + Blood drop +
+
+
{person ? ( - -

{person?.name}

-
+
+

+ {person?.name} +

+
{person?.severity} Hemophilia {person?.hemophiliaType}
- {person?.factor &&
Treat with factor {person.factor}
} - - Scan or visit for treatment history - + {person?.factor && ( +
+ Treat with factor {person.factor} +
+ )} +
+

+ Scan or visit for treatment history +

+
-

+

{alertUrl}

- - - +
+ Visit your page to preview what others will see. +
+
+
+
) : ( - +
+
+
)} - - - +
+
+
) } - -const StyledEmergencyCard = styled.div<{ forPrint?: boolean }>` - position: relative; - width: ${(props) => (props.forPrint ? '308px' : '525px')}; - height: ${(props) => (props.forPrint ? '192px' : '300px')}; - border-radius: 20px; - overflow: hidden; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); - - h2, - h3, - h4, - h5, - h6 { - padding: 0; - margin: 0; - line-height: ${(props) => (props.forPrint ? '15px' : '24px')}; - font-size: ${(props) => (props.forPrint ? '75%' : '100%')}; - } - - h5 { - font-weight: 400; - } -` - -const StyledHeader = styled.div<{ forPrint?: boolean; accentColor: string }>` - background-color: ${(props) => props.accentColor}; - height: ${(props) => (props.forPrint ? '56px' : '90px')}; - width: 100%; - padding: ${(props) => (props.forPrint ? '16px' : '24px')}; -` - -const StyledPersonalInfo = styled.div<{ forPrint?: boolean }>` - padding-left: ${(props) => (props.forPrint ? '8px' : '16px')}; -` - -const StyledQRCode = styled.div<{ forPrint?: boolean; accentColor: string }>` - position: relative; - width: ${(props) => (props.forPrint ? '96px' : '148px')}; - height: ${(props) => (props.forPrint ? '96px' : '148px')}; - padding: ${(props) => (props.forPrint ? '5px' : '8px')}; - border-radius: 8px; - border: ${(props) => (props.forPrint ? '3px' : '4px')} solid - ${(props) => props.accentColor}; -` - -const StyledScanLink = styled.div<{ forPrint?: boolean }>` - padding-top: ${(props) => (props.forPrint ? '8px' : '16px')}; -` - -const StyledBloodDrop = styled.img<{ forPrint?: boolean }>` - position: absolute; - left: ${(props) => (props.forPrint ? '34px' : '56px')}; - top: ${(props) => (props.forPrint ? '-18px' : '-24px')}; - width: ${(props) => (props.forPrint ? '24px' : '32px')}; - height: ${(props) => (props.forPrint ? '24px' : '32px')}; - border: none !important; -` - -const StyledAvatar = styled.img<{ forPrint?: boolean }>` - width: ${(props) => (props.forPrint ? '60px' : '100px')}; - height: ${(props) => (props.forPrint ? '60px' : '100px')}; - border-radius: 50%; - border: ${(props) => (props.forPrint ? '4px' : '8px')} solid white; -` diff --git a/components/emergencyInfo.tsx b/components/emergencyInfo.tsx index 8a8d8f6..0c0d233 100644 --- a/components/emergencyInfo.tsx +++ b/components/emergencyInfo.tsx @@ -1,13 +1,4 @@ -import { - Grid, - Avatar, - Note, - Spacer, - Text, - useMediaQuery, -} from '@geist-ui/react' -import styled from 'styled-components' - +import React from 'react' import InfusionTable from 'components/infusionTable' import type { Person } from 'lib/types/person' import { useAuth } from 'lib/auth' @@ -19,48 +10,76 @@ interface Props { export default function EmergencyInfo(props: Props): JSX.Element { const { person } = props const { user } = useAuth() - const smallerThanSmall = useMediaQuery('xs', { match: 'down' }) + const [smallerThanSmall, setSmallerThanSmall] = React.useState(false) + + React.useEffect(() => { + const checkMobile = () => { + setSmallerThanSmall(window.innerWidth < 640) // Tailwind's sm breakpoint + } + + checkMobile() + window.addEventListener('resize', checkMobile) + return () => window.removeEventListener('resize', checkMobile) + }, []) if (person) { return ( <> - - +
+ {person.photoUrl ? ( + {person.name + ) : ( +
+ {person.name?.charAt(0) || '?'} +
+ )} -
- {person.name} - +
+

{person.name}

+
{person.severity} Hemophilia {person.hemophiliaType}, treat with factor {person.factor} - +
- +
- - - Most recent treatments - {smallerThanSmall && Swipe →} - +
+
+
Most recent treatments
+ {smallerThanSmall && ( + Swipe → + )} +
- - - Pay attention to the date on each of these logs. We’re only showing - you the 3 most recent logs. If you want to see more,{' '} - {person.name?.split(' ')[0]} will have to give you - permission. - +
+
+
Note
+
+ Pay attention to the date on each of these logs. We're only showing + you the 3 most recent logs. If + you want to see more,{' '} + {person.name?.split(' ')[0]} will + have to give you permission. +
+
- +
{user && ( <> - Emergency contacts (coming soon) - - Soon you’ll be able to add these from your settings page. - +
+ Emergency contacts (coming soon) +
+

+ Soon you'll be able to add these from your settings page. +

)} - +
{/* NOTE(michael) remember when you implement this that you remember to update the example logic on /emergency/alertId as to not leak my actual emergency contact's info */} @@ -102,25 +121,11 @@ export default function EmergencyInfo(props: Props): JSX.Element { } return ( - - This person’s information could not be found. - +
+
Error
+
+ This person's information could not be found. +
+
) } - -const StyledRow = styled.div` - display: flex; - align-items: center; - flex-shrink: 0; - - h3, - h5 { - margin: 0; - } - - div { - display: flex; - flex-direction: column; - padding-left: 16px; - } -` diff --git a/components/emergencySnippet.tsx b/components/emergencySnippet.tsx index 7e6bcb3..93857ab 100644 --- a/components/emergencySnippet.tsx +++ b/components/emergencySnippet.tsx @@ -1,4 +1,6 @@ -import { Grid, Snippet } from '@geist-ui/react' +import type React from 'react' +import { IconCopy } from '@tabler/icons-react' +import toast from 'react-hot-toast' interface Props { alertId: string @@ -9,14 +11,34 @@ export default function EmergencySnippet(props: Props): JSX.Element { const { alertId = 'example', style } = props const env = process.env.NODE_ENV const domain = env === 'development' ? 'localhost:3000' : 'hemolog.com' + const url = `${domain}/emergency/${alertId}` + + const copyToClipboard = async () => { + try { + await navigator.clipboard.writeText(url) + toast.success('Link copied!') + } catch (err) { + console.error('Failed to copy: ', err) + toast.error('Failed to copy link') + } + } return ( - - + - + > + {url} + + +
) } diff --git a/components/feedbackFishFooter.tsx b/components/feedbackFishFooter.tsx index 3a63bab..0e88c1d 100644 --- a/components/feedbackFishFooter.tsx +++ b/components/feedbackFishFooter.tsx @@ -1,4 +1,3 @@ -import { Text, Page, Grid, Link } from '@geist-ui/react' // import { FeedbackFish } from '@feedback-fish/react' export default function Footer(): JSX.Element { @@ -6,21 +5,24 @@ export default function Footer(): JSX.Element { // const PROJECT_ID = process.env.FEEDBACK_FISH_PROJECT_ID return ( - - + ) } diff --git a/components/feedbackModal.tsx b/components/feedbackModal.tsx index cda2d93..e0f2086 100644 --- a/components/feedbackModal.tsx +++ b/components/feedbackModal.tsx @@ -1,5 +1,5 @@ -import { Modal, Textarea, Text, Spacer, useToasts } from '@geist-ui/react' import { useFormik } from 'formik' +import toast from 'react-hot-toast' import { useAuth } from 'lib/auth' import { createFeedback, type FeedbackType } from 'lib/db/feedback' @@ -16,8 +16,7 @@ interface FeedbackModalProps { } export default function FeedbackModal(props: FeedbackModalProps): JSX.Element { - const { visible, setVisible, bindings } = props - const [, setToast] = useToasts() + const { visible, setVisible } = props const { user } = useAuth() const handleCreateFeedback = async (feedback: FeedbackValues) => { @@ -36,19 +35,13 @@ export default function FeedbackModal(props: FeedbackModalProps): JSX.Element { createFeedback(feedbackPayload) .then(() => { - setToast({ - text: "Feedback submitted! We'll respond soon via email.", - type: 'success', - delay: 5000, - }) + toast.success("Feedback submitted! We'll respond soon via email.") closeModal() }) .catch((error: unknown) => - setToast({ - text: `Something went wrong: ${error instanceof Error ? error.message : String(error)}`, - type: 'error', - delay: 10000, - }) + toast.error( + `Something went wrong: ${error instanceof Error ? error.message : String(error)}` + ) ) } @@ -67,47 +60,80 @@ export default function FeedbackModal(props: FeedbackModalProps): JSX.Element { }) return ( - - Feedback - Hemolog.com - -

- If you’ve run into a bug or have an idea for how Hemolog could work - better for you, let me know. -

- -
- {/* Name - */} - - Your feedback -