fork of hey-api/openapi-ts because I need some additional things
1import './App.css';
2
3import * as Form from '@radix-ui/react-form';
4import { DownloadIcon, PlusIcon, ReloadIcon } from '@radix-ui/react-icons';
5import {
6 Avatar,
7 Box,
8 Button,
9 Card,
10 Container,
11 Flex,
12 Heading,
13 Section,
14 Text,
15 TextField,
16} from '@radix-ui/themes';
17import { useMutation, useQuery } from '@tanstack/react-query';
18import { useEffect, useState } from 'react';
19
20import {
21 addPetMutation,
22 getPetByIdOptions,
23 updatePetMutation,
24} from './client/@tanstack/react-query.gen';
25import { createClient } from './client/client';
26import { PetSchema } from './client/schemas.gen';
27import type { Pet } from './client/types.gen';
28
29const localClient = createClient({
30 // set default base url for requests made by this client
31 baseUrl: 'https://petstore3.swagger.io/api/v3',
32 /**
33 * Set default headers only for requests made by this client. This is to
34 * demonstrate local clients and their configuration taking precedence over
35 * internal service client.
36 */
37 headers: {
38 Authorization: 'Bearer <token_from_local_client>',
39 },
40});
41
42localClient.interceptors.request.use((request, options) => {
43 // Middleware is great for adding authorization tokens to requests made to
44 // protected paths. Headers are set randomly here to allow surfacing the
45 // default headers, too.
46 if (options.url === '/pet/{petId}' && options.method === 'GET' && Math.random() < 0.5) {
47 request.headers.set('Authorization', 'Bearer <token_from_interceptor>');
48 }
49 return request;
50});
51
52function App() {
53 const [pet, setPet] = useState<Pet>();
54 const [petId, setPetId] = useState<number>();
55 const [isRequiredNameError, setIsRequiredNameError] = useState(false);
56
57 const addPet = useMutation({
58 ...addPetMutation(),
59 onError: (error) => {
60 console.log(error);
61 setIsRequiredNameError(false);
62 },
63 onSuccess: (data) => {
64 setPet(data);
65 setIsRequiredNameError(false);
66 },
67 });
68
69 const updatePet = useMutation({
70 ...updatePetMutation(),
71 onError: (error) => {
72 console.log(error);
73 },
74 onSuccess: (data) => {
75 setPet(data);
76 },
77 });
78
79 const { data, error } = useQuery({
80 ...getPetByIdOptions({
81 client: localClient,
82 path: {
83 petId: petId!,
84 },
85 }),
86 enabled: Boolean(petId),
87 });
88
89 const onAddPet = async (formData: FormData) => {
90 // simple form field validation to demonstrate using schemas
91 if (PetSchema.required.includes('name') && !formData.get('name')) {
92 setIsRequiredNameError(true);
93 return;
94 }
95
96 addPet.mutate({
97 body: {
98 category: {
99 id: 0,
100 name: formData.get('category') as string,
101 },
102 id: 0,
103 name: formData.get('name') as string,
104 photoUrls: ['string'],
105 status: 'available',
106 tags: [
107 {
108 id: 0,
109 name: 'string',
110 },
111 ],
112 },
113 });
114 };
115
116 const onGetPetById = async () => {
117 // random id 1-10
118 setPetId(Math.floor(Math.random() * (10 - 1 + 1) + 1));
119 };
120
121 const onUpdatePet = async () => {
122 updatePet.mutate({
123 body: {
124 category: {
125 id: 0,
126 name: 'Cats',
127 },
128 id: 2,
129 name: 'Updated Kitty',
130 photoUrls: ['string'],
131 status: 'available',
132 tags: [
133 {
134 id: 0,
135 name: 'string',
136 },
137 ],
138 },
139 // setting headers per request
140 headers: {
141 Authorization: 'Bearer <token_from_method>',
142 },
143 });
144 };
145
146 useEffect(() => {
147 if (error) {
148 console.log(error);
149 return;
150 }
151 setPet(data!);
152 }, [data, error]);
153
154 return (
155 <Box style={{ background: 'var(--gray-a2)', borderRadius: 'var(--radius-3)' }}>
156 <Container size="1">
157 <Section size="1" />
158 <Flex align="center">
159 <a className="shrink-0" href="https://heyapi.dev/" target="_blank">
160 <img
161 src="https://heyapi.dev/assets/raw/logo.png"
162 className="h-16 w-16 transition duration-300 will-change-auto"
163 alt="Hey API logo"
164 />
165 </a>
166 <Heading>@hey-api/openapi-ts 🤝 TanStack React Query</Heading>
167 </Flex>
168 <Section size="1" />
169 <Flex direction="column" gapY="2">
170 <Box maxWidth="240px">
171 <Card>
172 <Flex gap="3" align="center">
173 <Avatar
174 size="3"
175 src={pet?.photoUrls[0]}
176 radius="full"
177 fallback={pet?.name.slice(0, 1) ?? 'N'}
178 />
179 <Box>
180 <Text as="div" size="2" weight="bold">
181 Name: {pet?.name ?? 'N/A'}
182 </Text>
183 <Text as="div" size="2" color="gray">
184 Category: {pet?.category?.name ?? 'N/A'}
185 </Text>
186 </Box>
187 </Flex>
188 </Card>
189 </Box>
190 <Button onClick={onGetPetById}>
191 <DownloadIcon /> Get Random Pet
192 </Button>
193 </Flex>
194 <Section size="1" />
195 <Flex direction="column" gapY="2">
196 <Form.Root
197 className="w-[260px]"
198 onSubmit={(event) => {
199 event.preventDefault();
200 onAddPet(new FormData(event.currentTarget));
201 }}
202 >
203 <Form.Field className="grid mb-[10px]" name="email">
204 <div className="flex items-baseline justify-between">
205 <Form.Label className="text-[15px] font-medium leading-[35px] text-white">
206 Name
207 </Form.Label>
208 {isRequiredNameError && (
209 <Form.Message className="text-[13px] text-white opacity-[0.8]">
210 Please enter a name
211 </Form.Message>
212 )}
213 </div>
214 <Form.Control asChild>
215 <TextField.Root placeholder="Kitty" name="name" type="text" />
216 </Form.Control>
217 </Form.Field>
218 <Form.Field className="grid mb-[10px]" name="question">
219 <div className="flex items-baseline justify-between">
220 <Form.Label className="text-[15px] font-medium leading-[35px] text-white">
221 Category
222 </Form.Label>
223 <Form.Message className="text-[13px] text-white opacity-[0.8]" match="valueMissing">
224 Please enter a category
225 </Form.Message>
226 </div>
227 <Form.Control asChild>
228 <TextField.Root placeholder="Cats" name="category" type="text" required />
229 </Form.Control>
230 </Form.Field>
231 <Flex gapX="2">
232 <Form.Submit asChild>
233 <Button type="submit">
234 <PlusIcon /> Add Pet
235 </Button>
236 </Form.Submit>
237 <Button onClick={onUpdatePet} type="button">
238 <ReloadIcon /> Update Pet
239 </Button>
240 </Flex>
241 </Form.Root>
242 </Flex>
243 <Section size="1" />
244 </Container>
245 </Box>
246 );
247}
248
249export default App;