|
1 | | -import { Image } from 'expo-image'; |
2 | | -import { Platform, StyleSheet } from 'react-native'; |
| 1 | +import { useState } from 'react'; |
| 2 | +import { Button, Platform, ScrollView, StyleSheet, Text } from 'react-native'; |
3 | 3 |
|
4 | | -import { HelloWave } from '@/components/hello-wave'; |
5 | | -import ParallaxScrollView from '@/components/parallax-scroll-view'; |
6 | 4 | import { ThemedText } from '@/components/themed-text'; |
7 | 5 | import { ThemedView } from '@/components/themed-view'; |
8 | | -import { Link } from 'expo-router'; |
9 | 6 |
|
10 | 7 | export default function HomeScreen() { |
| 8 | + const [responseLog, setResponseLog] = useState<string>('Console ready. Click any button to start testing HTTP requests.'); |
| 9 | + const [isLoading, setIsLoading] = useState(false); |
| 10 | + const [activeRequest, setActiveRequest] = useState<string>(''); |
| 11 | + |
| 12 | + const generateRandomQuery = () => { |
| 13 | + const randomId = Math.floor(Math.random() * 1000); |
| 14 | + const randomParam = Math.random().toString(36).substring(7); |
| 15 | + return `?id=${randomId}¶m=${randomParam}×tamp=${Date.now()}`; |
| 16 | + }; |
| 17 | + |
| 18 | + const makeRequest = async (method: string, endpoint: string) => { |
| 19 | + setIsLoading(true); |
| 20 | + setActiveRequest(method); |
| 21 | + setResponseLog(`Making ${method} request to ${endpoint}...`); |
| 22 | + |
| 23 | + try { |
| 24 | + const url = endpoint + (method === 'GET' ? generateRandomQuery() : ''); |
| 25 | + const options: RequestInit = { |
| 26 | + method, |
| 27 | + headers: { |
| 28 | + 'Content-Type': 'application/json', |
| 29 | + }, |
| 30 | + }; |
| 31 | + |
| 32 | + if (method !== 'GET') { |
| 33 | + options.body = JSON.stringify({ |
| 34 | + message: `Hello from ${method} request!`, |
| 35 | + timestamp: new Date().toISOString(), |
| 36 | + randomData: Math.random().toString(36).substring(7), |
| 37 | + method: method, |
| 38 | + }); |
| 39 | + } |
| 40 | + |
| 41 | + const response = await fetch(url, options); |
| 42 | + const data = await response.json(); |
| 43 | + |
| 44 | + const logMessage = `\n=== ${method} REQUEST COMPLETED ===\nURL: ${url}\nStatus: ${response.status} ${response.statusText}\nResponse Headers: ${JSON.stringify(Object.fromEntries(response.headers.entries()), null, 2)}\n\nResponse Body:\n${JSON.stringify(data, null, 2)}\n\n${'='.repeat(50)}`; |
| 45 | + |
| 46 | + setResponseLog(logMessage); |
| 47 | + console.log(`${method} Response:`, data); |
| 48 | + |
| 49 | + } catch (error) { |
| 50 | + const errorMessage = `\n=== ${method} REQUEST FAILED ===\nError: ${error instanceof Error ? error.message : 'Unknown error'}\nTimestamp: ${new Date().toISOString()}\n\n${'='.repeat(50)}`; |
| 51 | + setResponseLog(errorMessage); |
| 52 | + console.error(`${method} Error:`, error); |
| 53 | + } finally { |
| 54 | + setIsLoading(false); |
| 55 | + setActiveRequest(''); |
| 56 | + } |
| 57 | + }; |
| 58 | + |
| 59 | + const handleGetRequest = () => makeRequest('GET', 'https://httpbin.proxyman.app/get'); |
| 60 | + const handlePostRequest = () => makeRequest('POST', 'https://httpbin.proxyman.app/post'); |
| 61 | + const handlePutRequest = () => makeRequest('PUT', 'https://httpbin.proxyman.app/put'); |
| 62 | + const handleUpdateRequest = () => makeRequest('PATCH', 'https://httpbin.proxyman.app/patch'); |
| 63 | + |
11 | 64 | return ( |
12 | | - <ParallaxScrollView |
13 | | - headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }} |
14 | | - headerImage={ |
15 | | - <Image |
16 | | - source={require('@/assets/images/partial-react-logo.png')} |
17 | | - style={styles.reactLogo} |
18 | | - /> |
19 | | - }> |
20 | | - <ThemedView style={styles.titleContainer}> |
21 | | - <ThemedText type="title">Welcome!</ThemedText> |
22 | | - <HelloWave /> |
| 65 | + <ThemedView style={styles.container}> |
| 66 | + <ThemedView style={styles.header}> |
| 67 | + <ThemedText type="title">HTTP Request Tester</ThemedText> |
| 68 | + <ThemedText style={styles.subtitle}>Test different HTTP methods with httpbin.proxyman.app</ThemedText> |
23 | 69 | </ThemedView> |
24 | | - <ThemedView style={styles.stepContainer}> |
25 | | - <ThemedText type="subtitle">Step 1: Try it</ThemedText> |
26 | | - <ThemedText> |
27 | | - Edit <ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> to see changes. |
28 | | - Press{' '} |
29 | | - <ThemedText type="defaultSemiBold"> |
30 | | - {Platform.select({ |
31 | | - ios: 'cmd + d', |
32 | | - android: 'cmd + m', |
33 | | - web: 'F12', |
34 | | - })} |
35 | | - </ThemedText>{' '} |
36 | | - to open developer tools. |
37 | | - </ThemedText> |
| 70 | + |
| 71 | + <ThemedView style={styles.buttonContainer}> |
| 72 | + <Button |
| 73 | + title={isLoading && activeRequest === 'GET' ? "Making GET Request..." : "GET Request"} |
| 74 | + onPress={handleGetRequest} |
| 75 | + disabled={isLoading} |
| 76 | + color="#007AFF" |
| 77 | + /> |
| 78 | + <Button |
| 79 | + title={isLoading && activeRequest === 'POST' ? "Making POST Request..." : "POST Request"} |
| 80 | + onPress={handlePostRequest} |
| 81 | + disabled={isLoading} |
| 82 | + color="#007AFF" |
| 83 | + /> |
| 84 | + <Button |
| 85 | + title={isLoading && activeRequest === 'PUT' ? "Making PUT Request..." : "PUT Request"} |
| 86 | + onPress={handlePutRequest} |
| 87 | + disabled={isLoading} |
| 88 | + color="#007AFF" |
| 89 | + /> |
| 90 | + <Button |
| 91 | + title={isLoading && activeRequest === 'PATCH' ? "Making UPDATE Request..." : "UPDATE (PATCH)"} |
| 92 | + onPress={handleUpdateRequest} |
| 93 | + disabled={isLoading} |
| 94 | + color="#007AFF" |
| 95 | + /> |
38 | 96 | </ThemedView> |
39 | | - <ThemedView style={styles.stepContainer}> |
40 | | - <Link href="/modal"> |
41 | | - <Link.Trigger> |
42 | | - <ThemedText type="subtitle">Step 2: Explore</ThemedText> |
43 | | - </Link.Trigger> |
44 | | - <Link.Preview /> |
45 | | - <Link.Menu> |
46 | | - <Link.MenuAction title="Action" icon="cube" onPress={() => alert('Action pressed')} /> |
47 | | - <Link.MenuAction |
48 | | - title="Share" |
49 | | - icon="square.and.arrow.up" |
50 | | - onPress={() => alert('Share pressed')} |
51 | | - /> |
52 | | - <Link.Menu title="More" icon="ellipsis"> |
53 | | - <Link.MenuAction |
54 | | - title="Delete" |
55 | | - icon="trash" |
56 | | - destructive |
57 | | - onPress={() => alert('Delete pressed')} |
58 | | - /> |
59 | | - </Link.Menu> |
60 | | - </Link.Menu> |
61 | | - </Link> |
62 | 97 |
|
63 | | - <ThemedText> |
64 | | - {`Tap the Explore tab to learn more about what's included in this starter app.`} |
65 | | - </ThemedText> |
| 98 | + <ThemedView style={styles.consoleContainer}> |
| 99 | + <ThemedText type="defaultSemiBold" style={styles.consoleHeader}>Console Output</ThemedText> |
| 100 | + <ScrollView style={styles.consoleScroll} showsVerticalScrollIndicator={true}> |
| 101 | + <Text style={styles.consoleText}>{responseLog}</Text> |
| 102 | + </ScrollView> |
66 | 103 | </ThemedView> |
67 | | - <ThemedView style={styles.stepContainer}> |
68 | | - <ThemedText type="subtitle">Step 3: Get a fresh start</ThemedText> |
69 | | - <ThemedText> |
70 | | - {`When you're ready, run `} |
71 | | - <ThemedText type="defaultSemiBold">npm run reset-project</ThemedText> to get a fresh{' '} |
72 | | - <ThemedText type="defaultSemiBold">app</ThemedText> directory. This will move the current{' '} |
73 | | - <ThemedText type="defaultSemiBold">app</ThemedText> to{' '} |
74 | | - <ThemedText type="defaultSemiBold">app-example</ThemedText>. |
75 | | - </ThemedText> |
76 | | - </ThemedView> |
77 | | - </ParallaxScrollView> |
| 104 | + </ThemedView> |
78 | 105 | ); |
79 | 106 | } |
80 | 107 |
|
81 | 108 | const styles = StyleSheet.create({ |
82 | | - titleContainer: { |
83 | | - flexDirection: 'row', |
| 109 | + container: { |
| 110 | + flex: 1, |
| 111 | + padding: 20, |
| 112 | + paddingTop: Platform.select({ |
| 113 | + ios: 60, |
| 114 | + android: 40, |
| 115 | + default: 40, |
| 116 | + }), |
| 117 | + }, |
| 118 | + header: { |
84 | 119 | alignItems: 'center', |
85 | | - gap: 8, |
| 120 | + marginBottom: 30, |
| 121 | + }, |
| 122 | + subtitle: { |
| 123 | + marginTop: 8, |
| 124 | + opacity: 0.7, |
| 125 | + textAlign: 'center', |
| 126 | + }, |
| 127 | + buttonContainer: { |
| 128 | + gap: 16, |
| 129 | + marginBottom: 24, |
| 130 | + }, |
| 131 | + consoleContainer: { |
| 132 | + flex: 1, |
| 133 | + backgroundColor: '#1a1a1a', |
| 134 | + borderRadius: 8, |
| 135 | + overflow: 'hidden', |
| 136 | + }, |
| 137 | + consoleHeader: { |
| 138 | + backgroundColor: '#333', |
| 139 | + color: '#fff', |
| 140 | + padding: 12, |
| 141 | + fontSize: 14, |
86 | 142 | }, |
87 | | - stepContainer: { |
88 | | - gap: 8, |
89 | | - marginBottom: 8, |
| 143 | + consoleScroll: { |
| 144 | + flex: 1, |
| 145 | + padding: 12, |
90 | 146 | }, |
91 | | - reactLogo: { |
92 | | - height: 178, |
93 | | - width: 290, |
94 | | - bottom: 0, |
95 | | - left: 0, |
96 | | - position: 'absolute', |
| 147 | + consoleText: { |
| 148 | + fontSize: 12, |
| 149 | + fontFamily: Platform.select({ |
| 150 | + ios: 'Courier', |
| 151 | + android: 'monospace', |
| 152 | + default: 'monospace', |
| 153 | + }), |
| 154 | + color: '#00ff00', |
| 155 | + lineHeight: 16, |
97 | 156 | }, |
98 | 157 | }); |
0 commit comments