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 (
47 options.url === '/pet/{petId}' &&
48 options.method === 'GET' &&
49 Math.random() < 0.5
50 ) {
51 request.headers.set('Authorization', 'Bearer <token_from_interceptor>');
52 }
53 return request;
54});
55
56function App() {
57 const [pet, setPet] = useState<Pet>();
58 const [petId, setPetId] = useState<number>();
59 const [isRequiredNameError, setIsRequiredNameError] = useState(false);
60
61 const addPet = useMutation({
62 ...addPetMutation(),
63 onError: (error) => {
64 console.log(error);
65 setIsRequiredNameError(false);
66 },
67 onSuccess: (data) => {
68 setPet(data);
69 setIsRequiredNameError(false);
70 },
71 });
72
73 const updatePet = useMutation({
74 ...updatePetMutation(),
75 onError: (error) => {
76 console.log(error);
77 },
78 onSuccess: (data) => {
79 setPet(data);
80 },
81 });
82
83 const { data, error } = useQuery({
84 ...getPetByIdOptions({
85 client: localClient,
86 path: {
87 petId: petId!,
88 },
89 }),
90 enabled: Boolean(petId),
91 });
92
93 const onAddPet = async (formData: FormData) => {
94 // simple form field validation to demonstrate using schemas
95 if (PetSchema.required.includes('name') && !formData.get('name')) {
96 setIsRequiredNameError(true);
97 return;
98 }
99
100 addPet.mutate({
101 body: {
102 category: {
103 id: 0,
104 name: formData.get('category') as string,
105 },
106 id: 0,
107 name: formData.get('name') as string,
108 photoUrls: ['string'],
109 status: 'available',
110 tags: [
111 {
112 id: 0,
113 name: 'string',
114 },
115 ],
116 },
117 });
118 };
119
120 const onGetPetById = async () => {
121 // random id 1-10
122 setPetId(Math.floor(Math.random() * (10 - 1 + 1) + 1));
123 };
124
125 const onUpdatePet = async () => {
126 updatePet.mutate({
127 body: {
128 category: {
129 id: 0,
130 name: 'Cats',
131 },
132 id: 2,
133 name: 'Updated Kitty',
134 photoUrls: ['string'],
135 status: 'available',
136 tags: [
137 {
138 id: 0,
139 name: 'string',
140 },
141 ],
142 },
143 // setting headers per request
144 headers: {
145 Authorization: 'Bearer <token_from_method>',
146 },
147 });
148 };
149
150 useEffect(() => {
151 if (error) {
152 console.log(error);
153 return;
154 }
155 setPet(data!);
156 }, [data, error]);
157
158 return (
159 <Box
160 style={{ background: 'var(--gray-a2)', borderRadius: 'var(--radius-3)' }}
161 >
162 <Container size="1">
163 <Section size="1" />
164 <Flex align="center">
165 <a className="shrink-0" href="https://heyapi.dev/" target="_blank">
166 <img
167 src="https://heyapi.dev/assets/raw/logo.png"
168 className="h-16 w-16 transition duration-300 will-change-auto"
169 alt="Hey API logo"
170 />
171 </a>
172 <Heading>@hey-api/openapi-ts 🤝 TanStack React Query</Heading>
173 </Flex>
174 <Section size="1" />
175 <Flex direction="column" gapY="2">
176 <Box maxWidth="240px">
177 <Card>
178 <Flex gap="3" align="center">
179 <Avatar
180 size="3"
181 src={pet?.photoUrls[0]}
182 radius="full"
183 fallback={pet?.name.slice(0, 1) ?? 'N'}
184 />
185 <Box>
186 <Text as="div" size="2" weight="bold">
187 Name: {pet?.name ?? 'N/A'}
188 </Text>
189 <Text as="div" size="2" color="gray">
190 Category: {pet?.category?.name ?? 'N/A'}
191 </Text>
192 </Box>
193 </Flex>
194 </Card>
195 </Box>
196 <Button onClick={onGetPetById}>
197 <DownloadIcon /> Get Random Pet
198 </Button>
199 </Flex>
200 <Section size="1" />
201 <Flex direction="column" gapY="2">
202 <Form.Root
203 className="w-[260px]"
204 onSubmit={(event) => {
205 event.preventDefault();
206 onAddPet(new FormData(event.currentTarget));
207 }}
208 >
209 <Form.Field className="grid mb-[10px]" name="email">
210 <div className="flex items-baseline justify-between">
211 <Form.Label className="text-[15px] font-medium leading-[35px] text-white">
212 Name
213 </Form.Label>
214 {isRequiredNameError && (
215 <Form.Message className="text-[13px] text-white opacity-[0.8]">
216 Please enter a name
217 </Form.Message>
218 )}
219 </div>
220 <Form.Control asChild>
221 <TextField.Root placeholder="Kitty" name="name" type="text" />
222 </Form.Control>
223 </Form.Field>
224 <Form.Field className="grid mb-[10px]" name="question">
225 <div className="flex items-baseline justify-between">
226 <Form.Label className="text-[15px] font-medium leading-[35px] text-white">
227 Category
228 </Form.Label>
229 <Form.Message
230 className="text-[13px] text-white opacity-[0.8]"
231 match="valueMissing"
232 >
233 Please enter a category
234 </Form.Message>
235 </div>
236 <Form.Control asChild>
237 <TextField.Root
238 placeholder="Cats"
239 name="category"
240 type="text"
241 required
242 />
243 </Form.Control>
244 </Form.Field>
245 <Flex gapX="2">
246 <Form.Submit asChild>
247 <Button type="submit">
248 <PlusIcon /> Add Pet
249 </Button>
250 </Form.Submit>
251 <Button onClick={onUpdatePet} type="button">
252 <ReloadIcon /> Update Pet
253 </Button>
254 </Flex>
255 </Form.Root>
256 </Flex>
257 <Section size="1" />
258 </Container>
259 </Box>
260 );
261}
262
263export default App;