diff --git a/src/components/TypingText/TypingText.d.ts b/src/components/TypingText/TypingText.d.ts new file mode 100644 index 000000000..b43e075cc --- /dev/null +++ b/src/components/TypingText/TypingText.d.ts @@ -0,0 +1,8 @@ +export interface TypingTextProps { + content: string; + delay?: number; +} + +export const TypingText: React.FC; + +export function formatNumber(value: number, decimals?: number): string; \ No newline at end of file diff --git a/src/components/TypingText/TypingText.tsx b/src/components/TypingText/TypingText.tsx new file mode 100644 index 000000000..37eabe396 --- /dev/null +++ b/src/components/TypingText/TypingText.tsx @@ -0,0 +1,24 @@ +import { FC, useEffect, useState } from 'react'; + +export interface TypingTextProps { + content: string; + delay?: number; +} + +export const TypingText: FC = ({ content, delay = 40 }) => { + const [displayedContent, setDisplayedContent] = useState(''); + const [currentIndex, setCurrentIndex] = useState(0); + + useEffect(() => { + if (currentIndex < content.length) { + const timer = setTimeout(() => { + setDisplayedContent(prev => prev + content[currentIndex]); + setCurrentIndex(prev => prev + 1); + }, delay); + + return () => clearTimeout(timer); + } + }, [content, currentIndex, delay]); + + return {displayedContent}; +}; \ No newline at end of file diff --git a/src/components/TypingText/index.ts b/src/components/TypingText/index.ts new file mode 100644 index 000000000..3e2da69e6 --- /dev/null +++ b/src/components/TypingText/index.ts @@ -0,0 +1,2 @@ +export { default } from './TypingText'; +export type { TypingTextProps } from './TypingText'; \ No newline at end of file diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 000000000..3e2da69e6 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,2 @@ +export { default } from './TypingText'; +export type { TypingTextProps } from './TypingText'; \ No newline at end of file diff --git a/src/pages/oracle/landing/Stats/Stats.module.scss.d.ts b/src/pages/oracle/landing/Stats/Stats.module.scss.d.ts new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/src/pages/oracle/landing/Stats/Stats.module.scss.d.ts @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/pages/oracle/landing/Stats/Stats.test.tsx b/src/pages/oracle/landing/Stats/Stats.test.tsx new file mode 100644 index 000000000..50c4b2ddb --- /dev/null +++ b/src/pages/oracle/landing/Stats/Stats.test.tsx @@ -0,0 +1,56 @@ +import { render, screen } from '@testing-library/react'; +import { Stats, isValidStatsProps } from './Stats'; + +describe('Stats', () => { + const defaultProps = { + type: 'test', + value: 1000, + text: 'users', + }; + + it('renders correctly with minimum props', () => { + render(); + expect(screen.getByText('users')).toBeInTheDocument(); + expect(screen.getByText('1 000')).toBeInTheDocument(); + }); + + it('renders change information when provided', () => { + render({ + ...defaultProps, + change: 100, + time: '24h' + }); + expect(screen.getByText('+100')).toBeInTheDocument(); + expect(screen.getByText('in 24h')).toBeInTheDocument(); + }); + + it('returns null when value is not provided', () => { + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it('handles invalid number inputs', () => { + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it('formats large numbers correctly', () => { + render(); + expect(screen.getByText('1 000 000')).toBeInTheDocument(); + }); + + it('handles loading state', () => { + render(); + expect(screen.getByText('Loading...')).toBeInTheDocument(); + }); +}); + +describe('isValidStatsProps', () => { + it('validates correct props', () => { + expect(isValidStatsProps(defaultProps)).toBe(true); + }); + + it('invalidates incorrect props', () => { + expect(isValidStatsProps({ type: 123 })).toBe(false); + }); +}); \ No newline at end of file diff --git a/src/pages/oracle/landing/Stats/Stats.tsx b/src/pages/oracle/landing/Stats/Stats.tsx index 648ad5227..b9528e40e 100644 --- a/src/pages/oracle/landing/Stats/Stats.tsx +++ b/src/pages/oracle/landing/Stats/Stats.tsx @@ -1,84 +1,54 @@ -import { - useGetGraphStats, - useGetNegentropy, -} from 'src/containers/temple/hooks'; -import { TypingText } from 'src/containers/temple/pages/play/PlayBanerContent'; +import { FC } from 'react'; import cx from 'classnames'; -import { routes } from 'src/routes'; -import { Link } from 'react-router-dom'; -import { formatNumber, timeSince } from 'src/utils/utils'; +import { TypingText } from 'src/components/TypingText/TypingText'; +import { formatNumber } from 'src/utils/utils'; import styles from './Stats.module.scss'; -import { TitleType } from '../type'; -type Props = { - type: TitleType; -}; - -const REFETCH_INTERVAL = 1000 * 7; - -function Stats({ type }: Props) { - const dataGetGraphStats = useGetGraphStats(REFETCH_INTERVAL); - const negentropy = useGetNegentropy(REFETCH_INTERVAL); - let value: number | undefined; - let text: string | JSX.Element; - let change: number | undefined; - let time = timeSince(dataGetGraphStats.changeTimeAmount.time); - - switch (type) { - case TitleType.search: - value = dataGetGraphStats.data?.particles; - text = particles; - if (dataGetGraphStats.changeTimeAmount.particles) { - change = dataGetGraphStats.changeTimeAmount.particles; - } - break; - - case TitleType.learning: - value = dataGetGraphStats.data?.cyberlinks; - text = ( - cyberlinks - ); - if (dataGetGraphStats.changeTimeAmount.cyberlinks) { - change = dataGetGraphStats.changeTimeAmount.cyberlinks; - } - break; - - case TitleType.ai: - value = negentropy.data?.negentropy; - text = ( - syntropy bits - ); - if (negentropy.changeTimeAmount.amount) { - change = negentropy.changeTimeAmount.amount; - time = timeSince(negentropy.changeTimeAmount.time); - } - break; +interface StatsProps { + /** The type of statistic being displayed */ + type: string; + /** The numerical value to display */ + value?: number; + /** The descriptive text for the value */ + text?: string; + /** The amount of change to display */ + change?: number; + /** The time period for the change */ + time?: string; +} - default: +export const Stats: FC = ({ + type, + value, + text = '', + change, + time +}) => { + if (!value) { + return null; } + const formattedValue = value.toLocaleString().replaceAll(',', ' '); + const formattedChange = change ? formatNumber(change) : null; + return (
- {value && ( + {' '} + {text}{' '} + {change ? ( +

+ | +{formattedChange} in {time} +

+ ) : ( <> - {' '} - {text}{' '} - {change ? ( -

- | +{formatNumber(change)} in {time} -

- ) : ( - <> - and growing - - )} + and growing )}
); -} +}; export default Stats; diff --git a/src/pages/oracle/landing/Stats/index.ts b/src/pages/oracle/landing/Stats/index.ts new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/src/pages/oracle/landing/Stats/index.ts @@ -0,0 +1 @@ + \ No newline at end of file