mxivideo/src/components/CloudflareKVDemo.tsx

365 lines
12 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Cloudflare KV Demo Component
*
* This component demonstrates how to use the Cloudflare KV utilities
* in a React application with proper loading states and error handling.
*/
import React, { useState } from 'react'
import { useCloudflareKV, useKVValue } from '../hooks/useCloudflareKV'
interface DemoData {
message: string
timestamp: string
counter: number
}
export const CloudflareKVDemo: React.FC = () => {
const [inputKey, setInputKey] = useState('demo:test')
const [inputValue, setInputValue] = useState('Hello, Cloudflare KV!')
const [searchPrefix, setSearchPrefix] = useState('demo:')
const [logs, setLogs] = useState<string[]>([])
// Add log function
const addLog = (message: string) => {
const timestamp = new Date().toLocaleTimeString()
const logMessage = `[${timestamp}] ${message}`
console.log(logMessage)
setLogs(prev => [logMessage, ...prev].slice(0, 20)) // Keep last 20 logs
}
// Use the general KV hook
const kv = useCloudflareKV()
// Use the specific value hook for a counter
const {
value: counterValue,
loading: counterLoading,
error: counterError,
save: saveCounter,
refresh: refreshCounter
} = useKVValue<number>('demo:counter', true, true)
// Log initial state
React.useEffect(() => {
addLog('CloudflareKVDemo component initialized')
addLog(`KV loading state: ${kv.isLoading}`)
addLog(`KV has error: ${kv.hasError}`)
if (kv.hasError) {
addLog(`KV errors: ${JSON.stringify(kv.errors)}`)
}
}, [kv.isLoading, kv.hasError, kv.errors])
// Handle putting a value
const handlePutValue = async () => {
if (!inputKey || !inputValue) {
addLog('❌ Put operation failed: Key or value is empty')
alert('❌ Please enter both key and value')
return
}
addLog(`🔄 Starting put operation for key: ${inputKey}`)
try {
const data: DemoData = {
message: inputValue,
timestamp: new Date().toISOString(),
counter: (typeof counterValue === 'number' ? counterValue : 0) + 1
}
addLog(`📤 Sending data: ${JSON.stringify(data)}`)
const result = await kv.put(inputKey, data)
addLog(`✅ Put operation successful: ${JSON.stringify(result)}`)
alert('✅ Value stored successfully!')
} catch (error) {
const errorMessage = (error as Error).message
addLog(`❌ Put operation failed: ${errorMessage}`)
alert('❌ Failed to store value: ' + errorMessage)
}
}
// Handle getting a value
const handleGetValue = async () => {
if (!inputKey) {
addLog('❌ Get operation failed: Key is empty')
alert('❌ Please enter a key')
return
}
addLog(`🔄 Starting get operation for key: ${inputKey}`)
try {
const result = await kv.get<DemoData>(inputKey)
if (result) {
addLog(`✅ Get operation successful: ${JSON.stringify(result)}`)
alert(`✅ Retrieved value: ${JSON.stringify(result, null, 2)}`)
} else {
addLog(` Get operation completed: Key not found`)
alert(' Key not found')
}
} catch (error) {
const errorMessage = (error as Error).message
addLog(`❌ Get operation failed: ${errorMessage}`)
alert('❌ Failed to get value: ' + errorMessage)
}
}
// Handle deleting a value
const handleDeleteValue = async () => {
if (!inputKey) {
addLog('❌ Delete operation failed: Key is empty')
alert('❌ Please enter a key')
return
}
addLog(`🔄 Starting delete operation for key: ${inputKey}`)
try {
const result = await kv.delete(inputKey)
addLog(`✅ Delete operation successful: ${JSON.stringify(result)}`)
alert('✅ Value deleted successfully!')
} catch (error) {
const errorMessage = (error as Error).message
addLog(`❌ Delete operation failed: ${errorMessage}`)
alert('❌ Failed to delete value: ' + errorMessage)
}
}
// Handle listing keys
const handleListKeys = async () => {
addLog(`🔄 Starting list keys operation with prefix: ${searchPrefix}`)
try {
const result = await kv.listKeys(searchPrefix, 50)
addLog(`✅ List keys operation successful: Found ${result.result.keys.length} keys`)
const keyNames = result.result.keys.map(k => k.name).join('\n')
alert(`✅ Found keys:\n${keyNames || 'No keys found'}`)
} catch (error) {
const errorMessage = (error as Error).message
addLog(`❌ List keys operation failed: ${errorMessage}`)
alert('❌ Failed to list keys: ' + errorMessage)
}
}
// Handle incrementing counter
const handleIncrementCounter = async () => {
try {
const newValue = (typeof counterValue === 'number' ? counterValue : 0) + 1
await saveCounter(newValue)
} catch (error) {
alert('❌ Failed to increment counter: ' + (error as Error).message)
}
}
// Handle batch operations
const handleBatchPut = async () => {
try {
const entries = [
{ key: 'batch:item1', value: { name: 'Item 1', price: 10.99 } },
{ key: 'batch:item2', value: { name: 'Item 2', price: 15.50 } },
{ key: 'batch:item3', value: { name: 'Item 3', price: 8.75 } }
]
await kv.putBatch(entries)
alert('✅ Batch operation completed!')
} catch (error) {
alert('❌ Batch operation failed: ' + (error as Error).message)
}
}
return (
<div className="max-w-4xl mx-auto p-6 space-y-8">
<div className="bg-white rounded-lg shadow-lg p-6">
<h1 className="text-3xl font-bold text-gray-900 mb-6">
Cloudflare KV Demo
</h1>
{/* Loading indicator */}
{kv.isLoading && (
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
<div className="flex items-center">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600 mr-2"></div>
<span className="text-blue-800">Processing...</span>
</div>
</div>
)}
{/* Error display */}
{kv.hasError && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-6">
<h3 className="text-red-800 font-medium mb-2">Errors:</h3>
<ul className="text-red-700 text-sm space-y-1">
{kv.errors.get && <li>Get: {kv.errors.get}</li>}
{kv.errors.put && <li>Put: {kv.errors.put}</li>}
{kv.errors.delete && <li>Delete: {kv.errors.delete}</li>}
{kv.errors.list && <li>List: {kv.errors.list}</li>}
</ul>
</div>
)}
{/* Counter Section */}
<div className="bg-gray-50 rounded-lg p-4 mb-6">
<h2 className="text-xl font-semibold text-gray-900 mb-4">
Auto-loaded Counter
</h2>
<div className="flex items-center space-x-4">
<div className="text-lg">
Current value:
<span className="font-mono font-bold ml-2">
{counterLoading ? '...' : counterValue || 0}
</span>
</div>
<button
onClick={handleIncrementCounter}
disabled={counterLoading}
className="bg-blue-500 hover:bg-blue-600 disabled:bg-gray-400 text-white px-4 py-2 rounded-lg transition-colors"
>
Increment
</button>
<button
onClick={refreshCounter}
disabled={counterLoading}
className="bg-gray-500 hover:bg-gray-600 disabled:bg-gray-400 text-white px-4 py-2 rounded-lg transition-colors"
>
Refresh
</button>
</div>
{counterError && (
<p className="text-red-600 text-sm mt-2">{counterError}</p>
)}
</div>
{/* Manual Operations Section */}
<div className="space-y-6">
<h2 className="text-xl font-semibold text-gray-900">
Manual Operations
</h2>
{/* Key-Value Input */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Key
</label>
<input
type="text"
value={inputKey}
onChange={(e) => setInputKey(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="Enter key name"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Value
</label>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="Enter value"
/>
</div>
</div>
{/* Action Buttons */}
<div className="flex flex-wrap gap-3">
<button
onClick={handlePutValue}
disabled={kv.isLoading || !inputKey || !inputValue}
className="bg-green-500 hover:bg-green-600 disabled:bg-gray-400 text-white px-4 py-2 rounded-lg transition-colors"
>
Put Value
</button>
<button
onClick={handleGetValue}
disabled={kv.isLoading || !inputKey}
className="bg-blue-500 hover:bg-blue-600 disabled:bg-gray-400 text-white px-4 py-2 rounded-lg transition-colors"
>
Get Value
</button>
<button
onClick={handleDeleteValue}
disabled={kv.isLoading || !inputKey}
className="bg-red-500 hover:bg-red-600 disabled:bg-gray-400 text-white px-4 py-2 rounded-lg transition-colors"
>
Delete Value
</button>
</div>
{/* List Keys Section */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Search Prefix
</label>
<div className="flex gap-3">
<input
type="text"
value={searchPrefix}
onChange={(e) => setSearchPrefix(e.target.value)}
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="Enter prefix to search"
/>
<button
onClick={handleListKeys}
disabled={kv.isLoading}
className="bg-purple-500 hover:bg-purple-600 disabled:bg-gray-400 text-white px-4 py-2 rounded-lg transition-colors"
>
List Keys
</button>
</div>
</div>
{/* Batch Operations */}
<div>
<button
onClick={handleBatchPut}
disabled={kv.isLoading}
className="bg-orange-500 hover:bg-orange-600 disabled:bg-gray-400 text-white px-4 py-2 rounded-lg transition-colors"
>
Batch Put Demo Items
</button>
</div>
{/* Clear States */}
<div>
<button
onClick={kv.clearStates}
className="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg transition-colors"
>
Clear All States
</button>
<button
onClick={() => setLogs([])}
className="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg transition-colors ml-3"
>
Clear Logs
</button>
</div>
</div>
</div>
{/* Debug Logs Section */}
<div className="bg-white rounded-lg shadow-lg p-6 mt-6">
<h2 className="text-xl font-semibold text-gray-900 mb-4">
Debug Logs
</h2>
<div className="bg-gray-900 text-green-400 p-4 rounded-lg font-mono text-sm max-h-96 overflow-y-auto">
{logs.length === 0 ? (
<div className="text-gray-500">No logs yet...</div>
) : (
logs.map((log, index) => (
<div key={index} className="mb-1">
{log}
</div>
))
)}
</div>
</div>
</div>
)
}
export default CloudflareKVDemo