This commit is contained in:
Nathan Bierema 2025-05-31 13:58:39 -04:00
parent 5940aaba9a
commit 6a1f3194f8
7 changed files with 98 additions and 76 deletions

View File

@ -1,7 +1,7 @@
import PokemonView from './features/pokemon/PokemonView'; import PokemonView from './features/pokemon/PokemonView';
import PostsView from './features/posts/PostsView'; import PostsView from './features/posts/PostsView';
import { Box, Flex, Heading } from '@chakra-ui/react'; import { Box, Flex, Heading, List } from '@chakra-ui/react';
import { Link, UnorderedList, ListItem } from '@chakra-ui/react'; import { Link } from '@chakra-ui/react';
import { Code } from '@chakra-ui/react'; import { Code } from '@chakra-ui/react';
import * as React from 'react'; import * as React from 'react';
import { DevToolsSelector } from './features/DevTools/DevToolsSelector'; import { DevToolsSelector } from './features/DevTools/DevToolsSelector';
@ -27,44 +27,48 @@ export function App() {
</Box> </Box>
</Flex> </Flex>
<Flex p="2" as="footer"> <Flex p="2" as="footer">
<UnorderedList p="2"> <List.Root p="2">
<ListItem> <List.Item>
<Link <Link
className="link" className="link"
isExternal target="_blank"
rel="noopener noreferrer"
href="https://github.com/FaberVitale/redux-devtools/tree/feat/rtk-query-monitor/packages/redux-devtools-rtk-query-monitor/demo" href="https://github.com/FaberVitale/redux-devtools/tree/feat/rtk-query-monitor/packages/redux-devtools-rtk-query-monitor/demo"
> >
demo source demo source
</Link> </Link>
</ListItem> </List.Item>
<ListItem> <List.Item>
<Link <Link
className="link" className="link"
isExternal target="_blank"
rel="noopener noreferrer"
href="https://github.com/FaberVitale/redux-devtools/tree/feat/rtk-query-monitor/packages/redux-devtools-rtk-query-monitor" href="https://github.com/FaberVitale/redux-devtools/tree/feat/rtk-query-monitor/packages/redux-devtools-rtk-query-monitor"
> >
@redux-devtools/rtk-query-monitor source @redux-devtools/rtk-query-monitor source
</Link> </Link>
</ListItem> </List.Item>
<ListItem> <List.Item>
<Link <Link
className="link" className="link"
isExternal target="_blank"
rel="noopener noreferrer"
href="https://github.com/reduxjs/redux-toolkit/tree/master/examples/query/react/polling" href="https://github.com/reduxjs/redux-toolkit/tree/master/examples/query/react/polling"
> >
polling example polling example
</Link> </Link>
</ListItem> </List.Item>
<ListItem> <List.Item>
<Link <Link
className="link" className="link"
isExternal target="_blank"
rel="noopener noreferrer"
href="https://github.com/reduxjs/redux-toolkit/tree/master/examples/query/react/mutations" href="https://github.com/reduxjs/redux-toolkit/tree/master/examples/query/react/mutations"
> >
mutations example mutations example
</Link> </Link>
</ListItem> </List.Item>
</UnorderedList> </List.Root>
</Flex> </Flex>
</main> </main>
); );

View File

@ -14,7 +14,7 @@ export function DevToolsSelector() {
return ( return (
<Box as="section" p="2"> <Box as="section" p="2">
<Heading as="h2">Set active devTools</Heading> <Heading as="h2">Set active devTools</Heading>
<ButtonGroup variant="outline" spacing="4" p="4"> <ButtonGroup variant="outline" gap="4" p="4">
<Button <Button
aria-selected={!extensionEnabled} aria-selected={!extensionEnabled}
colorScheme="blue" colorScheme="blue"

View File

@ -1,15 +1,17 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Button, Select } from '@chakra-ui/react'; import { Button, createListCollection, Portal, Select } from '@chakra-ui/react';
import { useGetPokemonByNameQuery } from '../../services/pokemon'; import { useGetPokemonByNameQuery } from '../../services/pokemon';
import type { PokemonName } from '../../pokemon.data'; import type { PokemonName } from '../../pokemon.data';
const intervalOptions = [ const intervalOptions = createListCollection({
{ label: 'Off', value: 0 }, items: [
{ label: '3s', value: 3000 }, { label: 'Off', value: 0 },
{ label: '5s', value: 5000 }, { label: '3s', value: 3000 },
{ label: '10s', value: 10000 }, { label: '5s', value: 5000 },
{ label: '1m', value: 60000 }, { label: '10s', value: 10000 },
]; { label: '1m', value: 60000 },
],
});
export function Pokemon({ name }: { name: PokemonName }) { export function Pokemon({ name }: { name: PokemonName }) {
const [pollingInterval, setPollingInterval] = useState(60000); const [pollingInterval, setPollingInterval] = useState(60000);
@ -41,19 +43,37 @@ export function Pokemon({ name }: { name: PokemonName }) {
/> />
</div> </div>
<div> <div>
<label style={{ display: 'block' }}>Polling interval</label> <Select.Root
<Select collection={intervalOptions}
value={pollingInterval} value={[pollingInterval.toString()]}
onChange={({ target: { value } }) => onValueChange={({ value }) => setPollingInterval(Number(value))}
setPollingInterval(Number(value))
}
> >
{intervalOptions.map(({ label, value }) => ( <Select.HiddenSelect />
<option key={value} value={value}> <Select.Label>Polling interval</Select.Label>
{label} <Select.Control>
</option> <Select.Trigger>
))} <Select.ValueText placeholder="Polling interval" />
</Select> </Select.Trigger>
<Select.IndicatorGroup>
<Select.Indicator />
</Select.IndicatorGroup>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
{intervalOptions.items.map((intervalOption) => (
<Select.Item
item={intervalOption}
key={intervalOption.value}
>
{intervalOption.label}
<Select.ItemIndicator />
</Select.Item>
))}
</Select.Content>
</Select.Positioner>
</Portal>
</Select.Root>
</div> </div>
<div> <div>
<Button <Button

View File

@ -15,8 +15,8 @@ import {
Input, Input,
Spacer, Spacer,
Stack, Stack,
useToast,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { toaster } from '../../components/ui/toaster';
const EditablePostName = ({ const EditablePostName = ({
name: initialName, name: initialName,
@ -50,8 +50,8 @@ const EditablePostName = ({
</Box> </Box>
<Spacer /> <Spacer />
<Box> <Box>
<Stack spacing={4} direction="row" align="center"> <Stack gap={4} direction="row" align="center">
<Button onClick={handleUpdate} isLoading={isLoading}> <Button onClick={handleUpdate} loading={isLoading}>
Update Update
</Button> </Button>
<CloseButton bg="red" onClick={handleCancel} disabled={isLoading} /> <CloseButton bg="red" onClick={handleCancel} disabled={isLoading} />
@ -75,8 +75,6 @@ export const PostDetail = () => {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const navigate = useNavigate(); const navigate = useNavigate();
const toast = useToast();
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const { data: post, isLoading } = useGetPostQuery(id!); const { data: post, isLoading } = useGetPostQuery(id!);
@ -108,12 +106,12 @@ export const PostDetail = () => {
try { try {
await updatePost({ id: id!, name }).unwrap(); await updatePost({ id: id!, name }).unwrap();
} catch { } catch {
toast({ toaster.create({
title: 'An error occurred', title: 'An error occurred',
description: "We couldn't save your changes, try again!", description: "We couldn't save your changes, try again!",
status: 'error', type: 'error',
duration: 2000, duration: 2000,
isClosable: true, closable: true,
}); });
} finally { } finally {
setIsEditing(false); setIsEditing(false);
@ -129,7 +127,7 @@ export const PostDetail = () => {
</Box> </Box>
<Spacer /> <Spacer />
<Box> <Box>
<Stack spacing={4} direction="row" align="center"> <Stack gap={4} direction="row" align="center">
<Button <Button
onClick={() => setIsEditing(true)} onClick={() => setIsEditing(true)}
disabled={isDeleting || isUpdating} disabled={isDeleting || isUpdating}

View File

@ -2,20 +2,15 @@ import {
Box, Box,
Button, Button,
Center, Center,
Divider, Field,
Flex, Flex,
FormControl, FormatNumber,
FormLabel,
Heading, Heading,
Input, Input,
List, List,
ListIcon, Separator,
ListItem,
Spacer, Spacer,
Stat, Stat,
StatLabel,
StatNumber,
useToast,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { Route, Routes, useNavigate } from 'react-router-dom'; import { Route, Routes, useNavigate } from 'react-router-dom';
import { MdBook } from 'react-icons/md'; import { MdBook } from 'react-icons/md';
@ -26,12 +21,12 @@ import {
useGetPostsQuery, useGetPostsQuery,
} from '../../services/posts'; } from '../../services/posts';
import { PostDetail } from './PostDetail'; import { PostDetail } from './PostDetail';
import { toaster } from '../../components/ui/toaster';
const AddPost = () => { const AddPost = () => {
const initialValue = { name: '' }; const initialValue = { name: '' };
const [post, setPost] = useState<Pick<Post, 'name'>>(initialValue); const [post, setPost] = useState<Pick<Post, 'name'>>(initialValue);
const [addPost, { isLoading }] = useAddPostMutation(); const [addPost, { isLoading }] = useAddPostMutation();
const toast = useToast();
const handleChange = ({ target }: React.ChangeEvent<HTMLInputElement>) => { const handleChange = ({ target }: React.ChangeEvent<HTMLInputElement>) => {
setPost((prev) => ({ setPost((prev) => ({
@ -45,12 +40,12 @@ const AddPost = () => {
await addPost(post).unwrap(); await addPost(post).unwrap();
setPost(initialValue); setPost(initialValue);
} catch { } catch {
toast({ toaster.create({
title: 'An error occurred', title: 'An error occurred',
description: "We couldn't save your post, try again!", description: "We couldn't save your post, try again!",
status: 'error', type: 'error',
duration: 2000, duration: 2000,
isClosable: true, closable: true,
}); });
} }
}; };
@ -58,11 +53,11 @@ const AddPost = () => {
return ( return (
<Flex p={'5px 0'} flexDirection="row" flexWrap="wrap" maxWidth={'85%'}> <Flex p={'5px 0'} flexDirection="row" flexWrap="wrap" maxWidth={'85%'}>
<Box flex={'5 0 auto'} padding="0 5px 0 0"> <Box flex={'5 0 auto'} padding="0 5px 0 0">
<FormControl <Field.Root
flexDirection="column" flexDirection="column"
isInvalid={Boolean(post.name.length < 3 && post.name)} invalid={Boolean(post.name.length < 3 && post.name)}
> >
<FormLabel htmlFor="name">Post name</FormLabel> <Field.Label htmlFor="name">Post name</Field.Label>
<Input <Input
id="name" id="name"
name="name" name="name"
@ -70,13 +65,13 @@ const AddPost = () => {
value={post.name} value={post.name}
onChange={handleChange} onChange={handleChange}
/> />
</FormControl> </Field.Root>
</Box> </Box>
<Box> <Box>
<Button <Button
mt={8} mt={8}
colorScheme="purple" colorScheme="purple"
isLoading={isLoading} loading={isLoading}
onClick={handleAddPost} onClick={handleAddPost}
> >
Add Post Add Post
@ -99,13 +94,16 @@ const PostList = () => {
} }
return ( return (
<List spacing={3}> <List.Root gap={3}>
{posts.map(({ id, name }) => ( {posts.map(({ id, name }) => (
<ListItem key={id} onClick={() => navigate(`/posts/${id}`)}> <List.Item key={id} onClick={() => navigate(`/posts/${id}`)}>
<ListIcon as={MdBook} color="green.500" /> {name} <List.Indicator asChild color="green.500">
</ListItem> <MdBook />
</List.Indicator>
{name}
</List.Item>
))} ))}
</List> </List.Root>
); );
}; };
@ -115,10 +113,12 @@ export const PostsCountStat = () => {
if (!posts) return null; if (!posts) return null;
return ( return (
<Stat> <Stat.Root>
<StatLabel>Active Posts</StatLabel> <Stat.Label>Active Posts</Stat.Label>
<StatNumber>{posts?.length}</StatNumber> <Stat.ValueText>
</Stat> <FormatNumber value={posts?.length} />
</Stat.ValueText>
</Stat.Root>
); );
}; };
@ -134,9 +134,9 @@ export const PostsManager = () => {
<PostsCountStat /> <PostsCountStat />
</Box> </Box>
</Flex> </Flex>
<Divider /> <Separator />
<AddPost /> <AddPost />
<Divider /> <Separator />
<Flex wrap="wrap"> <Flex wrap="wrap">
<Box flex={1} borderRight="1px solid #eee"> <Box flex={1} borderRight="1px solid #eee">
<Box p={4} borderBottom="1px solid #eee"> <Box p={4} borderBottom="1px solid #eee">

View File

@ -8,6 +8,7 @@ import DevTools from './features/DevTools/DevTools';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
import { App } from './App'; import { App } from './App';
import { worker } from './mocks/browser'; import { worker } from './mocks/browser';
import { Toaster } from './components/ui/toaster';
function renderApp() { function renderApp() {
const rootElement = document.getElementById('root'); const rootElement = document.getElementById('root');
@ -17,6 +18,7 @@ function renderApp() {
<ChakraProvider> <ChakraProvider>
<BrowserRouter> <BrowserRouter>
<App /> <App />
<Toaster />
<DevTools /> <DevTools />
</BrowserRouter> </BrowserRouter>
</ChakraProvider> </ChakraProvider>

View File

@ -1,5 +1,3 @@
/// <reference types="react-scripts" />
declare module '@redux-devtools/app'; declare module '@redux-devtools/app';
declare module 'remote-redux-devtools'; declare module 'remote-redux-devtools';