1'use client';
2
3import { useEffect, useState } from 'react';
4
5import { classNames } from '../../../lib/utils';
6
7import { heroPlaygroundData } from './data';
8import { Title } from '../../atoms/title/title';
9import { PlaygroundTable } from './playground-table';
10
11import { Button } from '../../atoms/button/button';
12import { Paragraph } from '../../atoms/paragraph/paragraph';
13import { DotsPagination } from '../../molecules/dots-pagination/dots-pagination';
14import { PlayIcon, TableCellsIcon } from '@heroicons/react/20/solid';
15import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
16import { Icon } from '../../atoms/icon/icon';
17
18export type ResponseData = {
19 rowCount: number;
20 schema: Array<{
21 name: string;
22 type: {
23 name: string;
24 };
25 }>;
26 rows: Array<Record<string, any>>;
27};
28
29export const HeroPlaygroundOptions = () => {
30 const [currentIndex, setCurrentIndex] = useState(0);
31 const currentData = heroPlaygroundData[currentIndex];
32 const [requestDuration, setRequestDuration] = useState<number | null>(null);
33 const [responseData, setResponseData] = useState<ResponseData | null>(null);
34 const [isOpenTable, setIsOpenTable] = useState(false);
35 const [isLoading, setIsLoading] = useState(false);
36
37 const handlePrev = () => {
38 setCurrentIndex((prevIndex) =>
39 prevIndex === 0 ? heroPlaygroundData.length - 1 : prevIndex - 1
40 );
41 setRequestDuration(null);
42 };
43
44 const handleNext = () => {
45 setCurrentIndex((prevIndex) => (prevIndex + 1) % heroPlaygroundData.length);
46 setRequestDuration(null);
47 };
48
49 const handleCurrentIndexChange = (index: number) => {
50 setCurrentIndex(index);
51 setRequestDuration(null);
52 };
53
54 const handleRequest = async () => {
55 if (requestDuration || isLoading) return;
56
57 setIsLoading(true);
58 const startTime = performance.now();
59
60 try {
61 const response = await fetch(
62 `https://data.spiceai.io/v0.1/sampler/${currentData.requestUrl}?api_key=313834%7C0666ecca421b4b33ba4d0dd2e90d6daa`
63 );
64 const data: ResponseData = await response.json();
65
66 if (data?.rows?.length > 0) {
67 setResponseData(data);
68 }
69
70 const endTime = performance.now();
71 const durationInSeconds = (endTime - startTime) / 1000;
72
73 setRequestDuration(durationInSeconds);
74 } catch (error) {
75 console.error('Error fetching data:', error);
76 setRequestDuration(null);
77 } finally {
78 setIsLoading(false);
79 }
80 };
81
82 const handleKeyDown = (event: KeyboardEvent) => {
83 if (event.defaultPrevented) {
84 return;
85 }
86
87 switch (event.code) {
88 case 'ArrowLeft':
89 handlePrev();
90 break;
91 case 'ArrowRight':
92 handleNext();
93 break;
94 case 'Enter':
95 handleRequest();
96 break;
97 default:
98 return;
99 }
100
101 event.preventDefault();
102 };
103
104 useEffect(() => {
105 window.addEventListener('keydown', handleKeyDown, true);
106
107 return () => {
108 window.removeEventListener('keydown', handleKeyDown, true);
109 };
110 }, [handleKeyDown]);
111
112 return (
113 <div className='rounded-lg border border-alpha-150 bg-neutral pb-0 md:pb-10'>
114 <div className='flex flex-col items-center justify-between gap-6 border-b border-alpha-150 px-6 py-8 md:flex-row md:gap-10 md:px-10 md:py-10'>
115 <ArrowButton onClick={handlePrev} />
116 <div>
117 <Title as='h4' variant='small' className='mb-4 md:text-center'>
118 {currentData.title}
119 </Title>
120 <Paragraph className='md:text-center'>
121 {currentData.description}
122 </Paragraph>
123 </div>
124 <ArrowButton onClick={handleNext} isLeft={false} />
125
126 <MobileNavigation
127 onPrev={handlePrev}
128 onNext={handleNext}
129 currentIndex={currentIndex}
130 totalItems={heroPlaygroundData.length}
131 handleCurrentIndexChange={handleCurrentIndexChange}
132 />
133 </div>
134
135 <div className='flex flex-col gap-6 p-8 md:flex-row md:gap-10 md:p-10'>
136 <div className='flex flex-col items-start gap-6 md:w-1/2 md:p-6'>
137 <Title as='h4' variant='small'>
138 SQL Query
139 </Title>
140
141 {currentData.code}
142
143 <Button
144 variant={'primary'}
145 className='flex items-center gap-2'
146 onClick={handleRequest}
147 >
148 {isLoading ? (
149 <Icon iconName='spinner' className='h-5 w-5 animate-spin' />
150 ) : (
151 <PlayIcon className='h-5 w-5' />
152 )}
153 Run code
154 </Button>
155 </div>
156
157 <div className='relative flex items-center justify-center overflow-hidden rounded-sm border border-dashed p-6 md:h-80 md:w-1/2'>
158 {requestDuration == null ? (
159 <Paragraph variant={'small'}>Run code to get results.</Paragraph>
160 ) : (
161 <div>
162 <div className='flex flex-col items-center justify-center gap-2'>
163 <p className='font-semibold text-neutral-600'>
164 {responseData?.rowCount} of {responseData?.rowCount} total
165 results in{' '}
166 <span className='text-primary'>
167 {requestDuration.toFixed(3)}
168 </span>{' '}
169 seconds.
170 </p>
171
172 {responseData?.schema && (
173 <PlaygroundTable
174 data={responseData}
175 isOpenTable={isOpenTable}
176 setIsOpenTable={setIsOpenTable}
177 />
178 )}
179
180 {responseData && responseData?.schema.length > 3 && (
181 <Button
182 variant={'brand'}
183 className='flex items-center gap-2'
184 onClick={() => setIsOpenTable(true)}
185 >
186 <TableCellsIcon className='h-5 w-5' />
187 View results
188 </Button>
189 )}
190 </div>
191 </div>
192 )}
193 </div>
194 </div>
195 </div>
196 );
197};
198
199const ArrowButton = ({
200 className,
201 isLeft = true,
202 onClick,
203}: {
204 className?: string;
205 isLeft?: boolean;
206 onClick: () => void;
207}) => {
208 return (
209 <button
210 type='button'
211 onClick={onClick}
212 className={classNames(
213 'z-10 hidden rounded-full p-2 text-neutral-700 transition-colors hover:bg-neutral-100 hover:text-primary md:block',
214 className
215 )}
216 >
217 {isLeft ? (
218 <ChevronLeftIcon className='relative right-px h-6 w-6' />
219 ) : (
220 <ChevronRightIcon className='relative left-px h-6 w-6' />
221 )}
222 </button>
223 );
224};
225
226export const MobileNavigation = ({
227 onPrev,
228 onNext,
229 currentIndex,
230 handleCurrentIndexChange,
231 totalItems,
232}: {
233 onPrev: () => void;
234 onNext: () => void;
235 currentIndex: number;
236 handleCurrentIndexChange: (index: number) => void;
237 totalItems: number;
238}) => {
239 return (
240 <div className='relative flex w-full items-center justify-between gap-2 md:hidden'>
241 <button
242 type='button'
243 onClick={onPrev}
244 className={
245 'z-10 rounded-full p-2 text-neutral-700 transition-colors hover:bg-neutral-100 hover:text-primary'
246 }
247 >
248 <ChevronLeftIcon className='relative right-px h-6 w-6' />
249 </button>
250 <DotsPagination
251 className='bottom-auto translate-y-0'
252 current={currentIndex}
253 dotsLength={totalItems}
254 handleCurrentIndexChange={handleCurrentIndexChange}
255 />
256 <button
257 type='button'
258 onClick={onNext}
259 className={
260 'z-10 rounded-full p-2 text-neutral-700 transition-colors hover:bg-neutral-100 hover:text-primary'
261 }
262 >
263 <ChevronRightIcon className='relative right-px h-6 w-6' />
264 </button>
265 </div>
266 );
267};
268
1'use client';
2
3import { useEffect, useState } from 'react';
4
5import { classNames } from '../../../lib/utils';
6
7import { heroPlaygroundData } from './data';
8import { Title } from '../../atoms/title/title';
9import { PlaygroundTable } from './playground-table';
10
11import { Button } from '../../atoms/button/button';
12import { Paragraph } from '../../atoms/paragraph/paragraph';
13import { DotsPagination } from '../../molecules/dots-pagination/dots-pagination';
14import { PlayIcon, TableCellsIcon } from '@heroicons/react/20/solid';
15import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
16import { Icon } from '../../atoms/icon/icon';
17
18export type ResponseData = {
19 rowCount: number;
20 schema: Array<{
21 name: string;
22 type: {
23 name: string;
24 };
25 }>;
26 rows: Array<Record<string, any>>;
27};
28
29export const HeroPlaygroundOptions = () => {
30 const [currentIndex, setCurrentIndex] = useState(0);
31 const currentData = heroPlaygroundData[currentIndex];
32 const [requestDuration, setRequestDuration] = useState<number | null>(null);
33 const [responseData, setResponseData] = useState<ResponseData | null>(null);
34 const [isOpenTable, setIsOpenTable] = useState(false);
35 const [isLoading, setIsLoading] = useState(false);
36
37 const handlePrev = () => {
38 setCurrentIndex((prevIndex) =>
39 prevIndex === 0 ? heroPlaygroundData.length - 1 : prevIndex - 1
40 );
41 setRequestDuration(null);
42 };
43
44 const handleNext = () => {
45 setCurrentIndex((prevIndex) => (prevIndex + 1) % heroPlaygroundData.length);
46 setRequestDuration(null);
47 };
48
49 const handleCurrentIndexChange = (index: number) => {
50 setCurrentIndex(index);
51 setRequestDuration(null);
52 };
53
54 const handleRequest = async () => {
55 if (requestDuration || isLoading) return;
56
57 setIsLoading(true);
58 const startTime = performance.now();
59
60 try {
61 const response = await fetch(
62 `https://data.spiceai.io/v0.1/sampler/${currentData.requestUrl}?api_key=313834%7C0666ecca421b4b33ba4d0dd2e90d6daa`
63 );
64 const data: ResponseData = await response.json();
65
66 if (data?.rows?.length > 0) {
67 setResponseData(data);
68 }
69
70 const endTime = performance.now();
71 const durationInSeconds = (endTime - startTime) / 1000;
72
73 setRequestDuration(durationInSeconds);
74 } catch (error) {
75 console.error('Error fetching data:', error);
76 setRequestDuration(null);
77 } finally {
78 setIsLoading(false);
79 }
80 };
81
82 const handleKeyDown = (event: KeyboardEvent) => {
83 if (event.defaultPrevented) {
84 return;
85 }
86
87 switch (event.code) {
88 case 'ArrowLeft':
89 handlePrev();
90 break;
91 case 'ArrowRight':
92 handleNext();
93 break;
94 case 'Enter':
95 handleRequest();
96 break;
97 default:
98 return;
99 }
100
101 event.preventDefault();
102 };
103
104 useEffect(() => {
105 window.addEventListener('keydown', handleKeyDown, true);
106
107 return () => {
108 window.removeEventListener('keydown', handleKeyDown, true);
109 };
110 }, [handleKeyDown]);
111
112 return (
113 <div className='rounded-lg border border-alpha-150 bg-neutral pb-0 md:pb-10'>
114 <div className='flex flex-col items-center justify-between gap-6 border-b border-alpha-150 px-6 py-8 md:flex-row md:gap-10 md:px-10 md:py-10'>
115 <ArrowButton onClick={handlePrev} />
116 <div>
117 <Title as='h4' variant='small' className='mb-4 md:text-center'>
118 {currentData.title}
119 </Title>
120 <Paragraph className='md:text-center'>
121 {currentData.description}
122 </Paragraph>
123 </div>
124 <ArrowButton onClick={handleNext} isLeft={false} />
125
126 <MobileNavigation
127 onPrev={handlePrev}
128 onNext={handleNext}
129 currentIndex={currentIndex}
130 totalItems={heroPlaygroundData.length}
131 handleCurrentIndexChange={handleCurrentIndexChange}
132 />
133 </div>
134
135 <div className='flex flex-col gap-6 p-8 md:flex-row md:gap-10 md:p-10'>
136 <div className='flex flex-col items-start gap-6 md:w-1/2 md:p-6'>
137 <Title as='h4' variant='small'>
138 SQL Query
139 </Title>
140
141 {currentData.code}
142
143 <Button
144 variant={'primary'}
145 className='flex items-center gap-2'
146 onClick={handleRequest}
147 >
148 {isLoading ? (
149 <Icon iconName='spinner' className='h-5 w-5 animate-spin' />
150 ) : (
151 <PlayIcon className='h-5 w-5' />
152 )}
153 Run code
154 </Button>
155 </div>
156
157 <div className='relative flex items-center justify-center overflow-hidden rounded-sm border border-dashed p-6 md:h-80 md:w-1/2'>
158 {requestDuration == null ? (
159 <Paragraph variant={'small'}>Run code to get results.</Paragraph>
160 ) : (
161 <div>
162 <div className='flex flex-col items-center justify-center gap-2'>
163 <p className='font-semibold text-neutral-600'>
164 {responseData?.rowCount} of {responseData?.rowCount} total
165 results in{' '}
166 <span className='text-primary'>
167 {requestDuration.toFixed(3)}
168 </span>{' '}
169 seconds.
170 </p>
171
172 {responseData?.schema && (
173 <PlaygroundTable
174 data={responseData}
175 isOpenTable={isOpenTable}
176 setIsOpenTable={setIsOpenTable}
177 />
178 )}
179
180 {responseData && responseData?.schema.length > 3 && (
181 <Button
182 variant={'brand'}
183 className='flex items-center gap-2'
184 onClick={() => setIsOpenTable(true)}
185 >
186 <TableCellsIcon className='h-5 w-5' />
187 View results
188 </Button>
189 )}
190 </div>
191 </div>
192 )}
193 </div>
194 </div>
195 </div>
196 );
197};
198
199const ArrowButton = ({
200 className,
201 isLeft = true,
202 onClick,
203}: {
204 className?: string;
205 isLeft?: boolean;
206 onClick: () => void;
207}) => {
208 return (
209 <button
210 type='button'
211 onClick={onClick}
212 className={classNames(
213 'z-10 hidden rounded-full p-2 text-neutral-700 transition-colors hover:bg-neutral-100 hover:text-primary md:block',
214 className
215 )}
216 >
217 {isLeft ? (
218 <ChevronLeftIcon className='relative right-px h-6 w-6' />
219 ) : (
220 <ChevronRightIcon className='relative left-px h-6 w-6' />
221 )}
222 </button>
223 );
224};
225
226export const MobileNavigation = ({
227 onPrev,
228 onNext,
229 currentIndex,
230 handleCurrentIndexChange,
231 totalItems,
232}: {
233 onPrev: () => void;
234 onNext: () => void;
235 currentIndex: number;
236 handleCurrentIndexChange: (index: number) => void;
237 totalItems: number;
238}) => {
239 return (
240 <div className='relative flex w-full items-center justify-between gap-2 md:hidden'>
241 <button
242 type='button'
243 onClick={onPrev}
244 className={
245 'z-10 rounded-full p-2 text-neutral-700 transition-colors hover:bg-neutral-100 hover:text-primary'
246 }
247 >
248 <ChevronLeftIcon className='relative right-px h-6 w-6' />
249 </button>
250 <DotsPagination
251 className='bottom-auto translate-y-0'
252 current={currentIndex}
253 dotsLength={totalItems}
254 handleCurrentIndexChange={handleCurrentIndexChange}
255 />
256 <button
257 type='button'
258 onClick={onNext}
259 className={
260 'z-10 rounded-full p-2 text-neutral-700 transition-colors hover:bg-neutral-100 hover:text-primary'
261 }
262 >
263 <ChevronRightIcon className='relative right-px h-6 w-6' />
264 </button>
265 </div>
266 );
267};
268