Karrio App Examples
Insiders
Enterprise
Learn by example with complete, real-world Karrio Apps that you can use as templates for your own projects.
Example Apps Overview
App | Type | Complexity | Features |
---|---|---|---|
Hello World | Basic | Beginner | Authentication, UI basics |
Shipment Tracker | Utility | Intermediate | Real-time tracking, webhooks |
E-commerce Sync | Integration | Advanced | OAuth, data sync, automation |
Analytics Dashboard | Analytics | Advanced | Charts, reports, data visualization |
Workflow Automation | Automation | Expert | Rules engine, batch processing |
Hello World App
A simple introduction app demonstrating basic Karrio App concepts.
Features
- Basic authentication
- Simple UI components
- API data fetching
- Configuration management
Complete Source Code
App Manifest
manifest.ts1import { AppManifest } from "@karrio/app-store/types"; 2 3export const manifest: AppManifest = { 4 id: "hello-world", 5 name: "Hello World", 6 version: "1.0.0", 7 description: "A simple hello world app demonstrating Karrio App basics", 8 author: { 9 name: "Your Name", 10 email: "you@example.com", 11 url: "https://your-website.com", 12 }, 13 permissions: ["manage_data"], 14 assets: { 15 icon: "./assets/icon.svg", 16 screenshots: ["./assets/screenshot1.png"], 17 readme: "./README.md", 18 }, 19 components: { 20 main: "./component.tsx", 21 configuration: "./configuration.tsx", 22 }, 23 api: { 24 routes: { 25 "/api/hello": "./api/hello.ts", 26 }, 27 }, 28};
Main Component
component.tsx1import React, { useState, useEffect } from "react"; 2import { Card, Button, Badge, Spinner } from "@karrio/ui"; 3import { AppComponentProps } from "@karrio/app-store/types"; 4 5export default function HelloWorldApp({ 6 app, 7 context, 8 karrio, 9}: AppComponentProps) { 10 const [user, setUser] = useState(null); 11 const [loading, setLoading] = useState(true); 12 const [message, setMessage] = useState(""); 13 14 useEffect(() => { 15 fetchUserInfo(); 16 }, []); 17 18 const fetchUserInfo = async () => { 19 try { 20 const response = await karrio.get("/v1/users/me"); 21 setUser(response); 22 } catch (error) { 23 console.error("Failed to fetch user info:", error); 24 } finally { 25 setLoading(false); 26 } 27 }; 28 29 const sendGreeting = async () => { 30 try { 31 const response = await fetch(`/api/apps/${app.id}/hello`, { 32 method: "POST", 33 headers: { 34 "Content-Type": "application/json", 35 Authorization: `Bearer ${app.installation.api_key}`, 36 }, 37 body: JSON.stringify({ name: user?.first_name || "User" }), 38 }); 39 40 const data = await response.json(); 41 setMessage(data.message); 42 } catch (error) { 43 console.error("Failed to send greeting:", error); 44 } 45 }; 46 47 if (loading) { 48 return ( 49 <div className="flex items-center justify-center p-8"> 50 <Spinner size="lg" /> 51 </div> 52 ); 53 } 54 55 return ( 56 <div className="max-w-2xl mx-auto p-6 space-y-6"> 57 {/* Header */} 58 <div className="text-center"> 59 <h1 className="text-3xl font-bold text-gray-900 mb-2"> 60 Hello World! 👋 61 </h1> 62 <p className="text-gray-600">Welcome to your first Karrio App</p> 63 </div> 64 65 {/* User Info Card */} 66 <Card> 67 <Card.Header> 68 <Card.Title>User Information</Card.Title> 69 </Card.Header> 70 <Card.Content> 71 <div className="space-y-3"> 72 <div className="flex justify-between"> 73 <span className="text-gray-500">Name:</span> 74 <span className="font-medium"> 75 {user?.first_name} {user?.last_name} 76 </span> 77 </div> 78 <div className="flex justify-between"> 79 <span className="text-gray-500">Email:</span> 80 <span className="font-medium">{user?.email}</span> 81 </div> 82 <div className="flex justify-between"> 83 <span className="text-gray-500">Organization:</span> 84 <span className="font-medium">{context.organization?.name}</span> 85 </div> 86 </div> 87 </Card.Content> 88 </Card> 89 90 {/* App Info Card */} 91 <Card> 92 <Card.Header> 93 <Card.Title>App Information</Card.Title> 94 </Card.Header> 95 <Card.Content> 96 <div className="space-y-3"> 97 <div className="flex justify-between"> 98 <span className="text-gray-500">App ID:</span> 99 <code className="text-sm bg-gray-100 px-2 py-1 rounded"> 100 {app.id} 101 </code> 102 </div> 103 <div className="flex justify-between"> 104 <span className="text-gray-500">Version:</span> 105 <Badge variant="secondary">{app.version}</Badge> 106 </div> 107 <div className="flex justify-between"> 108 <span className="text-gray-500">Installation ID:</span> 109 <code className="text-sm bg-gray-100 px-2 py-1 rounded"> 110 {app.installation.id} 111 </code> 112 </div> 113 <div className="flex justify-between"> 114 <span className="text-gray-500">Status:</span> 115 <Badge 116 variant={app.installation.is_active ? "success" : "danger"} 117 > 118 {app.installation.is_active ? "Active" : "Inactive"} 119 </Badge> 120 </div> 121 </div> 122 </Card.Content> 123 </Card> 124 125 {/* Interactive Section */} 126 <Card> 127 <Card.Header> 128 <Card.Title>Try It Out</Card.Title> 129 </Card.Header> 130 <Card.Content> 131 <div className="space-y-4"> 132 <p className="text-gray-600"> 133 Click the button below to call the app's API endpoint: 134 </p> 135 <Button onClick={sendGreeting} className="w-full"> 136 Send Greeting 137 </Button> 138 {message && ( 139 <div className="p-4 bg-green-50 border border-green-200 rounded-lg"> 140 <p className="text-green-800">{message}</p> 141 </div> 142 )} 143 </div> 144 </Card.Content> 145 </Card> 146 </div> 147 ); 148}
Configuration Component
configuration.tsx1import React, { useState } from "react"; 2import { Input, Textarea, Checkbox, Button } from "@karrio/ui"; 3import { AppConfigurationContext } from "@karrio/app-store/types"; 4 5export default function HelloWorldConfiguration({ 6 app, 7 context, 8 karrio, 9 onConfigChange, 10 onSave, 11 onCancel, 12}: AppConfigurationContext) { 13 const [config, setConfig] = useState({ 14 greeting_message: "Hello", 15 show_user_info: true, 16 custom_message: "", 17 debug_mode: false, 18 }); 19 20 const handleChange = (key: string, value: any) => { 21 const newConfig = { ...config, [key]: value }; 22 setConfig(newConfig); 23 onConfigChange?.(key, value); 24 }; 25 26 const handleSave = async () => { 27 try { 28 // Save configuration via metafields 29 await karrio.graphql( 30 ` 31 mutation UpdateAppInstallation($input: UpdateAppInstallationMutationInput!) { 32 update_app_installation(input: $input) { 33 installation { 34 id 35 metafields { 36 key 37 value 38 } 39 } 40 errors { 41 field 42 messages 43 } 44 } 45 } 46 `, 47 { 48 input: { 49 id: app.installation.id, 50 metafields: Object.entries(config).map(([key, value]) => ({ 51 key, 52 value: String(value), 53 type: typeof value === "boolean" ? "boolean" : "text", 54 })), 55 }, 56 }, 57 ); 58 59 onSave?.(config); 60 } catch (error) { 61 console.error("Failed to save configuration:", error); 62 } 63 }; 64 65 return ( 66 <div className="space-y-6"> 67 <div> 68 <h2 className="text-xl font-semibold mb-4">App Configuration</h2> 69 <p className="text-gray-600"> 70 Customize your Hello World app settings. 71 </p> 72 </div> 73 74 <div className="space-y-4"> 75 <Input 76 label="Greeting Message" 77 value={config.greeting_message} 78 onChange={(value) => handleChange("greeting_message", value)} 79 placeholder="Enter greeting message" 80 /> 81 82 <Textarea 83 label="Custom Message" 84 value={config.custom_message} 85 onChange={(value) => handleChange("custom_message", value)} 86 placeholder="Enter a custom message to display" 87 rows={3} 88 /> 89 90 <Checkbox 91 label="Show User Information" 92 checked={config.show_user_info} 93 onChange={(checked) => handleChange("show_user_info", checked)} 94 /> 95 96 <Checkbox 97 label="Debug Mode" 98 checked={config.debug_mode} 99 onChange={(checked) => handleChange("debug_mode", checked)} 100 /> 101 </div> 102 103 <div className="flex space-x-4"> 104 <Button variant="primary" onClick={handleSave}> 105 Save Configuration 106 </Button> 107 <Button variant="outline" onClick={onCancel}> 108 Cancel 109 </Button> 110 </div> 111 </div> 112 ); 113}
API Route
api/hello.ts1import { NextRequest, NextResponse } from "next/server"; 2import { authenticateAppRequest } from "@karrio/app-store"; 3 4export async function POST(request: NextRequest) { 5 try { 6 // Authenticate the app request 7 const appContext = await authenticateAppRequest("hello-world", request); 8 9 const { name } = await request.json(); 10 11 const message = `Hello ${name}! This message is from your Karrio App API.`; 12 13 return NextResponse.json({ 14 message, 15 timestamp: new Date().toISOString(), 16 appId: appContext.app.id, 17 }); 18 } catch (error) { 19 return NextResponse.json( 20 { error: "Authentication failed" }, 21 { status: 401 }, 22 ); 23 } 24}
Shipment Tracker App
A real-time shipment tracking app with webhooks and notifications.
Features
- Real-time tracking updates
- Webhook integration
- Status notifications
- Tracking history
- Multiple carrier support
Key Components
Main Tracking Component
components/ShipmentTracker.tsx1import React, { useState, useEffect } from "react"; 2import { Card, Badge, Button, Input, Table } from "@karrio/ui"; 3import { useRealtimeUpdates } from "../hooks/useRealtimeUpdates"; 4 5export default function ShipmentTracker({ app, context, karrio }) { 6 const [trackingNumber, setTrackingNumber] = useState(""); 7 const [trackingData, setTrackingData] = useState(null); 8 const [loading, setLoading] = useState(false); 9 10 // Real-time updates via WebSocket 11 const { updates } = useRealtimeUpdates(app.installation.id); 12 13 useEffect(() => { 14 if (updates.length > 0) { 15 const latestUpdate = updates[updates.length - 1]; 16 if ( 17 latestUpdate.type === "tracking_update" && 18 latestUpdate.tracking_number === trackingNumber 19 ) { 20 setTrackingData(latestUpdate.data); 21 } 22 } 23 }, [updates, trackingNumber]); 24 25 const trackShipment = async () => { 26 if (!trackingNumber.trim()) return; 27 28 try { 29 setLoading(true); 30 31 const response = await karrio.graphql( 32 ` 33 query GetShipmentByTracking($trackingNumber: String!) { 34 shipments(filter: { tracking_number: $trackingNumber, first: 1 }) { 35 edges { 36 node { 37 id 38 tracking_number 39 status 40 tracking_url 41 recipient { 42 name 43 city 44 state_code 45 } 46 shipper { 47 name 48 city 49 state_code 50 } 51 created_at 52 } 53 } 54 } 55 } 56 `, 57 { trackingNumber }, 58 ); 59 60 const shipment = response.data.shipments.edges[0]?.node; 61 if (shipment) { 62 // Use REST API to get detailed tracking info 63 const trackingResponse = await karrio.get( 64 `/v1/trackers/${shipment.tracking_number}`, 65 ); 66 setTrackingData(trackingResponse); 67 } 68 } catch (error) { 69 console.error("Failed to track shipment:", error); 70 } finally { 71 setLoading(false); 72 } 73 }; 74 75 const getStatusColor = (status) => { 76 const colors = { 77 delivered: "success", 78 in_transit: "warning", 79 out_for_delivery: "info", 80 exception: "danger", 81 pending: "secondary", 82 }; 83 return colors[status] || "secondary"; 84 }; 85 86 return ( 87 <div className="max-w-4xl mx-auto p-6 space-y-6"> 88 <div className="text-center"> 89 <h1 className="text-2xl font-bold mb-4">Shipment Tracker</h1> 90 <p className="text-gray-600"> 91 Track your shipments in real-time across multiple carriers 92 </p> 93 </div> 94 95 {/* Tracking Input */} 96 <Card> 97 <Card.Content> 98 <div className="flex space-x-4"> 99 <Input 100 placeholder="Enter tracking number" 101 value={trackingNumber} 102 onChange={setTrackingNumber} 103 className="flex-1" 104 /> 105 <Button 106 onClick={trackShipment} 107 loading={loading} 108 disabled={!trackingNumber.trim()} 109 > 110 Track Shipment 111 </Button> 112 </div> 113 </Card.Content> 114 </Card> 115 116 {/* Tracking Results */} 117 {trackingData && ( 118 <> 119 {/* Status Overview */} 120 <Card> 121 <Card.Header> 122 <Card.Title>Tracking Details</Card.Title> 123 </Card.Header> 124 <Card.Content> 125 <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> 126 <div> 127 <label className="text-sm text-gray-500"> 128 Tracking Number 129 </label> 130 <p className="font-mono">{trackingData.tracking_number}</p> 131 </div> 132 <div> 133 <label className="text-sm text-gray-500">Carrier</label> 134 <p className="font-medium">{trackingData.carrier_name}</p> 135 </div> 136 <div> 137 <label className="text-sm text-gray-500">Status</label> 138 <Badge variant={getStatusColor(trackingData.status)}> 139 {trackingData.status.replace("_", " ")} 140 </Badge> 141 </div> 142 <div> 143 <label className="text-sm text-gray-500">Est. Delivery</label> 144 <p>{trackingData.estimated_delivery || "N/A"}</p> 145 </div> 146 </div> 147 </Card.Content> 148 </Card> 149 150 {/* Tracking Events */} 151 <Card> 152 <Card.Header> 153 <Card.Title>Tracking History</Card.Title> 154 </Card.Header> 155 <Card.Content> 156 <div className="space-y-4"> 157 {trackingData.events.map((event, index) => ( 158 <div key={index} className="border-l-2 border-gray-200 pl-4"> 159 <div className="flex justify-between items-start"> 160 <div> 161 <p className="font-medium">{event.description}</p> 162 <p className="text-sm text-gray-500"> 163 {event.location} 164 </p> 165 </div> 166 <div className="text-right"> 167 <p className="text-sm font-medium"> 168 {event.date} {event.time} 169 </p> 170 <Badge variant={getStatusColor(event.status)} size="sm"> 171 {event.status} 172 </Badge> 173 </div> 174 </div> 175 </div> 176 ))} 177 </div> 178 </Card.Content> 179 </Card> 180 </> 181 )} 182 </div> 183 ); 184}
Real-time Updates Hook
hooks/useRealtimeUpdates.ts1import { useState, useEffect } from "react"; 2 3export function useRealtimeUpdates(installationId: string) { 4 const [updates, setUpdates] = useState([]); 5 const [connectionStatus, setConnectionStatus] = useState("disconnected"); 6 7 useEffect(() => { 8 const eventSource = new EventSource( 9 `/api/sse/tracking?installation=${installationId}`, 10 ); 11 12 eventSource.onopen = () => { 13 setConnectionStatus("connected"); 14 }; 15 16 eventSource.onmessage = (event) => { 17 const data = JSON.parse(event.data); 18 setUpdates((prev) => [...prev, data]); 19 }; 20 21 eventSource.onerror = () => { 22 setConnectionStatus("error"); 23 }; 24 25 return () => { 26 eventSource.close(); 27 }; 28 }, [installationId]); 29 30 return { updates, connectionStatus }; 31}
Webhook Handler
api/webhooks/tracking.ts1import { NextRequest, NextResponse } from "next/server"; 2 3export async function POST(request: NextRequest) { 4 try { 5 const payload = await request.json(); 6 7 // Process tracking webhook 8 if (payload.event === "tracking.updated") { 9 // Broadcast update to connected clients 10 await broadcastTrackingUpdate(payload.data); 11 12 // Store in database 13 await storeTrackingEvent(payload.data); 14 15 // Send notifications if needed 16 if (payload.data.status === "delivered") { 17 await sendDeliveryNotification(payload.data); 18 } 19 } 20 21 return NextResponse.json({ received: true }); 22 } catch (error) { 23 console.error("Webhook error:", error); 24 return NextResponse.json( 25 { error: "Webhook processing failed" }, 26 { status: 500 }, 27 ); 28 } 29} 30 31async function broadcastTrackingUpdate(trackingData) { 32 // Implementation for real-time broadcasting 33} 34 35async function storeTrackingEvent(trackingData) { 36 // Implementation for storing tracking events 37} 38 39async function sendDeliveryNotification(trackingData) { 40 // Implementation for sending notifications 41}
E-commerce Integration App
A sophisticated e-commerce platform integration with order synchronization.
Features
- OAuth authentication with e-commerce platforms
- Automatic order import
- Shipment creation and label generation
- Inventory tracking
- Customer notifications
Key Components
OAuth Configuration
components/OAuthSetup.tsx1import React, { useState } from "react"; 2import { Button, Card, Alert, Input } from "@karrio/ui"; 3 4export default function OAuthSetup({ app, platform }) { 5 const [connecting, setConnecting] = useState(false); 6 const [connected, setConnected] = useState(false); 7 const [credentials, setCredentials] = useState({ 8 client_id: "", 9 client_secret: "", 10 shop_url: "", 11 }); 12 13 const handleConnect = async () => { 14 try { 15 setConnecting(true); 16 17 // Step 1: Save credentials 18 await app.api.post("/oauth/credentials", credentials); 19 20 // Step 2: Initiate OAuth flow 21 const authUrl = await app.api.get("/oauth/authorize", { 22 platform, 23 return_url: window.location.href, 24 }); 25 26 // Redirect to OAuth provider 27 window.location.href = authUrl.authorization_url; 28 } catch (error) { 29 console.error("OAuth connection failed:", error); 30 } finally { 31 setConnecting(false); 32 } 33 }; 34 35 return ( 36 <Card> 37 <Card.Header> 38 <Card.Title>Connect to {platform}</Card.Title> 39 <Card.Subtitle> 40 Securely connect your {platform} store to sync orders 41 </Card.Subtitle> 42 </Card.Header> 43 <Card.Content> 44 {connected ? ( 45 <Alert variant="success">Successfully connected to {platform}!</Alert> 46 ) : ( 47 <div className="space-y-4"> 48 <Input 49 label="Client ID" 50 value={credentials.client_id} 51 onChange={(value) => 52 setCredentials((prev) => ({ ...prev, client_id: value })) 53 } 54 /> 55 <Input 56 label="Client Secret" 57 type="password" 58 value={credentials.client_secret} 59 onChange={(value) => 60 setCredentials((prev) => ({ ...prev, client_secret: value })) 61 } 62 /> 63 <Input 64 label="Shop URL" 65 value={credentials.shop_url} 66 onChange={(value) => 67 setCredentials((prev) => ({ ...prev, shop_url: value })) 68 } 69 placeholder="your-shop.myshopify.com" 70 /> 71 </div> 72 )} 73 </Card.Content> 74 <Card.Footer> 75 <Button 76 onClick={handleConnect} 77 loading={connecting} 78 disabled={connected} 79 > 80 {connected ? "Connected" : "Connect Store"} 81 </Button> 82 </Card.Footer> 83 </Card> 84 ); 85}
Order Sync Component
components/OrderSync.tsx1import React, { useState, useEffect } from "react"; 2import { Table, Button, Badge, Card } from "@karrio/ui"; 3 4export default function OrderSync({ app, karrio }) { 5 const [orders, setOrders] = useState([]); 6 const [syncing, setSyncing] = useState(false); 7 const [stats, setStats] = useState({ 8 total: 0, 9 pending: 0, 10 synced: 0, 11 errors: 0, 12 }); 13 14 useEffect(() => { 15 loadOrders(); 16 loadStats(); 17 }, []); 18 19 const loadOrders = async () => { 20 try { 21 const response = await app.api.get("/orders"); 22 setOrders(response.orders); 23 } catch (error) { 24 console.error("Failed to load orders:", error); 25 } 26 }; 27 28 const loadStats = async () => { 29 try { 30 const response = await app.api.get("/orders/stats"); 31 setStats(response); 32 } catch (error) { 33 console.error("Failed to load stats:", error); 34 } 35 }; 36 37 const syncOrders = async () => { 38 try { 39 setSyncing(true); 40 41 const response = await app.api.post("/orders/sync"); 42 43 // Process each order 44 for (const order of response.orders) { 45 await createShipmentForOrder(order); 46 } 47 48 await loadOrders(); 49 await loadStats(); 50 } catch (error) { 51 console.error("Order sync failed:", error); 52 } finally { 53 setSyncing(false); 54 } 55 }; 56 57 const createShipmentForOrder = async (order) => { 58 try { 59 const shipmentData = { 60 recipient: { 61 name: `${order.customer.first_name} ${order.customer.last_name}`, 62 company: order.shipping_address.company, 63 address_line1: order.shipping_address.address1, 64 address_line2: order.shipping_address.address2, 65 city: order.shipping_address.city, 66 state_code: order.shipping_address.province_code, 67 postal_code: order.shipping_address.zip, 68 country_code: order.shipping_address.country_code, 69 phone_number: order.shipping_address.phone, 70 email: order.customer.email, 71 }, 72 parcels: order.line_items.map((item) => ({ 73 weight: item.weight || 1, 74 width: item.width || 10, 75 height: item.height || 10, 76 length: item.length || 10, 77 description: item.title, 78 })), 79 reference: order.order_number, 80 service: "fedex_ground", // Default service 81 }; 82 83 const shipment = await karrio.createShipment(shipmentData); 84 85 // Update order with shipment info 86 await app.api.patch(`/orders/${order.id}`, { 87 shipment_id: shipment.id, 88 tracking_number: shipment.tracking_number, 89 status: "shipped", 90 }); 91 92 return shipment; 93 } catch (error) { 94 console.error(`Failed to create shipment for order ${order.id}:`, error); 95 throw error; 96 } 97 }; 98 99 const orderColumns = [ 100 { 101 header: "Order #", 102 accessor: "order_number", 103 cell: (value) => <code className="text-sm">{value}</code>, 104 }, 105 { 106 header: "Customer", 107 accessor: "customer", 108 cell: (customer) => ( 109 <div> 110 <div className="font-medium"> 111 {customer.first_name} {customer.last_name} 112 </div> 113 <div className="text-sm text-gray-500">{customer.email}</div> 114 </div> 115 ), 116 }, 117 { 118 header: "Total", 119 accessor: "total_price", 120 cell: (value, row) => `${row.currency} ${value}`, 121 }, 122 { 123 header: "Status", 124 accessor: "fulfillment_status", 125 cell: (status) => { 126 const variant = 127 status === "shipped" 128 ? "success" 129 : status === "pending" 130 ? "warning" 131 : "secondary"; 132 return <Badge variant={variant}>{status || "unfulfilled"}</Badge>; 133 }, 134 }, 135 { 136 header: "Tracking", 137 accessor: "tracking_number", 138 cell: (trackingNumber) => 139 trackingNumber ? ( 140 <code className="text-sm">{trackingNumber}</code> 141 ) : ( 142 <span className="text-gray-400">-</span> 143 ), 144 }, 145 ]; 146 147 return ( 148 <div className="space-y-6"> 149 {/* Stats Cards */} 150 <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> 151 <Card className="p-4"> 152 <div className="text-center"> 153 <div className="text-2xl font-bold">{stats.total}</div> 154 <div className="text-sm text-gray-500">Total Orders</div> 155 </div> 156 </Card> 157 <Card className="p-4"> 158 <div className="text-center"> 159 <div className="text-2xl font-bold text-orange-600"> 160 {stats.pending} 161 </div> 162 <div className="text-sm text-gray-500">Pending</div> 163 </div> 164 </Card> 165 <Card className="p-4"> 166 <div className="text-center"> 167 <div className="text-2xl font-bold text-green-600"> 168 {stats.synced} 169 </div> 170 <div className="text-sm text-gray-500">Synced</div> 171 </div> 172 </Card> 173 <Card className="p-4"> 174 <div className="text-center"> 175 <div className="text-2xl font-bold text-red-600"> 176 {stats.errors} 177 </div> 178 <div className="text-sm text-gray-500">Errors</div> 179 </div> 180 </Card> 181 </div> 182 183 {/* Controls */} 184 <div className="flex justify-between items-center"> 185 <h2 className="text-xl font-semibold">Orders</h2> 186 <Button onClick={syncOrders} loading={syncing}> 187 Sync Orders 188 </Button> 189 </div> 190 191 {/* Orders Table */} 192 <Table 193 data={orders} 194 columns={orderColumns} 195 searchable 196 pagination 197 emptyMessage="No orders found" 198 /> 199 </div> 200 ); 201}
Analytics Dashboard App
A comprehensive analytics dashboard with charts and insights.
Features
- Real-time shipping metrics
- Cost analysis and trends
- Performance dashboards
- Custom reports
- Data export capabilities
Key Components
Dashboard Overview
components/Dashboard.tsx1import React, { useState, useEffect } from "react"; 2import { Card, Select, DatePicker } from "@karrio/ui"; 3import { LineChart, BarChart, PieChart, MetricCard } from "./Charts"; 4 5// Helper function to process shipment data into metrics 6const processShipmentData = (shipments) => { 7 const totalShipments = shipments.length; 8 const totalCost = shipments.reduce( 9 (sum, s) => sum + (parseFloat(s.selected_rate?.total_charge) || 0), 10 0, 11 ); 12 const averageCost = totalCost / totalShipments || 0; 13 14 // Calculate success rate (delivered shipments) 15 const deliveredCount = shipments.filter( 16 (s) => s.status === "delivered", 17 ).length; 18 const successRate = ((deliveredCount / totalShipments) * 100).toFixed(1); 19 20 // Top carriers by usage 21 const carrierCounts = {}; 22 shipments.forEach((s) => { 23 const carrier = s.selected_rate?.carrier?.carrier_name || "Unknown"; 24 carrierCounts[carrier] = (carrierCounts[carrier] || 0) + 1; 25 }); 26 27 const topCarriers = Object.entries(carrierCounts) 28 .map(([name, count]) => ({ name, count, cost: 0 })) 29 .sort((a, b) => b.count - a.count) 30 .slice(0, 5); 31 32 return { 33 total_shipments: totalShipments, 34 total_cost: totalCost, 35 average_cost: averageCost, 36 success_rate: successRate, 37 top_carriers: topCarriers, 38 daily_trends: [], // Could be calculated from created_at dates 39 status_breakdown: [], // Could be calculated from status values 40 cost_breakdown: [], // Could be calculated from carriers 41 }; 42}; 43 44export default function AnalyticsDashboard({ app, karrio }) { 45 const [dateRange, setDateRange] = useState({ 46 start: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), // 30 days ago 47 end: new Date(), 48 }); 49 const [metrics, setMetrics] = useState(null); 50 const [loading, setLoading] = useState(true); 51 52 useEffect(() => { 53 loadMetrics(); 54 }, [dateRange]); 55 56 const loadMetrics = async () => { 57 try { 58 setLoading(true); 59 60 // Since shipping_metrics doesn't exist, we'll use actual shipments data 61 const response = await karrio.graphql( 62 ` 63 query GetShipmentAnalytics($start: DateTime!, $end: DateTime!) { 64 shipments(filter: { 65 created_after: $start, 66 created_before: $end, 67 first: 1000 68 }) { 69 edges { 70 node { 71 id 72 status 73 created_at 74 selected_rate { 75 total_charge 76 currency 77 service 78 carrier { 79 carrier_name 80 } 81 } 82 recipient { 83 city 84 state_code 85 country_code 86 } 87 } 88 } 89 } 90 } 91 `, 92 { 93 start: dateRange.start.toISOString(), 94 end: dateRange.end.toISOString(), 95 }, 96 ); 97 98 // Process the raw shipment data to create metrics 99 const shipments = response.data.shipments.edges.map((edge) => edge.node); 100 const processedMetrics = processShipmentData(shipments); 101 setMetrics(processedMetrics); 102 } catch (error) { 103 console.error("Failed to load metrics:", error); 104 } finally { 105 setLoading(false); 106 } 107 }; 108 109 if (loading) { 110 return <div>Loading analytics...</div>; 111 } 112 113 return ( 114 <div className="space-y-6"> 115 {/* Header & Controls */} 116 <div className="flex justify-between items-center"> 117 <h1 className="text-2xl font-bold">Shipping Analytics</h1> 118 <div className="flex space-x-4"> 119 <DatePicker 120 value={dateRange} 121 onChange={setDateRange} 122 placeholder="Select date range" 123 /> 124 </div> 125 </div> 126 127 {/* Key Metrics */} 128 <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> 129 <MetricCard 130 title="Total Shipments" 131 value={metrics.total_shipments} 132 change={"+12%"} 133 trend="up" 134 /> 135 <MetricCard 136 title="Total Cost" 137 value={`$${metrics.total_cost.toFixed(2)}`} 138 change={"-5%"} 139 trend="down" 140 /> 141 <MetricCard 142 title="Average Cost" 143 value={`$${metrics.average_cost.toFixed(2)}`} 144 change={"+2%"} 145 trend="up" 146 /> 147 <MetricCard 148 title="Success Rate" 149 value={`${metrics.success_rate}%`} 150 change={"+1%"} 151 trend="up" 152 /> 153 </div> 154 155 {/* Charts */} 156 <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> 157 {/* Daily Trends */} 158 <Card> 159 <Card.Header> 160 <Card.Title>Daily Shipping Trends</Card.Title> 161 </Card.Header> 162 <Card.Content> 163 <LineChart 164 data={metrics.daily_trends} 165 xKey="date" 166 yKeys={["shipments", "cost"]} 167 colors={["#3b82f6", "#10b981"]} 168 /> 169 </Card.Content> 170 </Card> 171 172 {/* Status Breakdown */} 173 <Card> 174 <Card.Header> 175 <Card.Title>Shipment Status</Card.Title> 176 </Card.Header> 177 <Card.Content> 178 <PieChart 179 data={metrics.status_breakdown} 180 dataKey="count" 181 nameKey="status" 182 /> 183 </Card.Content> 184 </Card> 185 186 {/* Top Carriers */} 187 <Card> 188 <Card.Header> 189 <Card.Title>Top Carriers</Card.Title> 190 </Card.Header> 191 <Card.Content> 192 <BarChart 193 data={metrics.top_carriers} 194 xKey="name" 195 yKey="count" 196 color="#f59e0b" 197 /> 198 </Card.Content> 199 </Card> 200 201 {/* Cost Breakdown */} 202 <Card> 203 <Card.Header> 204 <Card.Title>Cost by Carrier</Card.Title> 205 </Card.Header> 206 <Card.Content> 207 <PieChart 208 data={metrics.cost_breakdown} 209 dataKey="cost" 210 nameKey="carrier" 211 /> 212 </Card.Content> 213 </Card> 214 </div> 215 </div> 216 ); 217}
Workflow Automation App
An advanced automation app with rules engine and batch processing.
Features
- Visual workflow builder
- Rule-based automation
- Batch processing
- Conditional logic
- Integration triggers
Workflow Builder
components/WorkflowBuilder.tsx1import React, { useState } from "react"; 2import { Card, Button, Select, Input } from "@karrio/ui"; 3 4export default function WorkflowBuilder({ app, karrio }) { 5 const [workflow, setWorkflow] = useState({ 6 name: "", 7 trigger: null, 8 conditions: [], 9 actions: [], 10 }); 11 12 const triggers = [ 13 { value: "order_created", label: "New Order Created" }, 14 { value: "shipment_delivered", label: "Shipment Delivered" }, 15 { value: "tracking_exception", label: "Tracking Exception" }, 16 { value: "cost_threshold", label: "Cost Threshold Exceeded" }, 17 ]; 18 19 const conditions = [ 20 { value: "order_value", label: "Order Value", type: "number" }, 21 { value: "destination", label: "Destination", type: "text" }, 22 { value: "carrier", label: "Carrier", type: "select" }, 23 { value: "weight", label: "Weight", type: "number" }, 24 ]; 25 26 const actions = [ 27 { value: "send_email", label: "Send Email" }, 28 { value: "create_shipment", label: "Create Shipment" }, 29 { value: "update_inventory", label: "Update Inventory" }, 30 { value: "webhook", label: "Call Webhook" }, 31 ]; 32 33 const addCondition = () => { 34 setWorkflow((prev) => ({ 35 ...prev, 36 conditions: [ 37 ...prev.conditions, 38 { 39 field: "", 40 operator: "equals", 41 value: "", 42 }, 43 ], 44 })); 45 }; 46 47 const addAction = () => { 48 setWorkflow((prev) => ({ 49 ...prev, 50 actions: [ 51 ...prev.actions, 52 { 53 type: "", 54 config: {}, 55 }, 56 ], 57 })); 58 }; 59 60 const saveWorkflow = async () => { 61 try { 62 await app.api.post("/workflows", workflow); 63 // Success handling 64 } catch (error) { 65 console.error("Failed to save workflow:", error); 66 } 67 }; 68 69 return ( 70 <div className="max-w-4xl mx-auto space-y-6"> 71 <div className="text-center"> 72 <h1 className="text-2xl font-bold mb-2">Workflow Builder</h1> 73 <p className="text-gray-600"> 74 Create automated workflows for your shipping operations 75 </p> 76 </div> 77 78 {/* Workflow Name */} 79 <Card> 80 <Card.Header> 81 <Card.Title>Workflow Details</Card.Title> 82 </Card.Header> 83 <Card.Content> 84 <Input 85 label="Workflow Name" 86 value={workflow.name} 87 onChange={(value) => 88 setWorkflow((prev) => ({ ...prev, name: value })) 89 } 90 placeholder="Enter workflow name" 91 /> 92 </Card.Content> 93 </Card> 94 95 {/* Trigger */} 96 <Card> 97 <Card.Header> 98 <Card.Title>Trigger</Card.Title> 99 <Card.Subtitle>When should this workflow run?</Card.Subtitle> 100 </Card.Header> 101 <Card.Content> 102 <Select 103 label="Select Trigger" 104 value={workflow.trigger} 105 onChange={(value) => 106 setWorkflow((prev) => ({ ...prev, trigger: value })) 107 } 108 options={triggers} 109 /> 110 </Card.Content> 111 </Card> 112 113 {/* Conditions */} 114 <Card> 115 <Card.Header> 116 <div className="flex justify-between items-center"> 117 <div> 118 <Card.Title>Conditions</Card.Title> 119 <Card.Subtitle> 120 Add conditions to filter when the workflow runs 121 </Card.Subtitle> 122 </div> 123 <Button onClick={addCondition} variant="outline" size="sm"> 124 Add Condition 125 </Button> 126 </div> 127 </Card.Header> 128 <Card.Content> 129 <div className="space-y-4"> 130 {workflow.conditions.map((condition, index) => ( 131 <div key={index} className="flex space-x-4 items-end"> 132 <Select 133 label="Field" 134 value={condition.field} 135 onChange={(value) => { 136 const newConditions = [...workflow.conditions]; 137 newConditions[index].field = value; 138 setWorkflow((prev) => ({ 139 ...prev, 140 conditions: newConditions, 141 })); 142 }} 143 options={conditions} 144 /> 145 <Select 146 label="Operator" 147 value={condition.operator} 148 onChange={(value) => { 149 const newConditions = [...workflow.conditions]; 150 newConditions[index].operator = value; 151 setWorkflow((prev) => ({ 152 ...prev, 153 conditions: newConditions, 154 })); 155 }} 156 options={[ 157 { value: "equals", label: "Equals" }, 158 { value: "greater_than", label: "Greater Than" }, 159 { value: "less_than", label: "Less Than" }, 160 { value: "contains", label: "Contains" }, 161 ]} 162 /> 163 <Input 164 label="Value" 165 value={condition.value} 166 onChange={(value) => { 167 const newConditions = [...workflow.conditions]; 168 newConditions[index].value = value; 169 setWorkflow((prev) => ({ 170 ...prev, 171 conditions: newConditions, 172 })); 173 }} 174 /> 175 </div> 176 ))} 177 </div> 178 </Card.Content> 179 </Card> 180 181 {/* Actions */} 182 <Card> 183 <Card.Header> 184 <div className="flex justify-between items-center"> 185 <div> 186 <Card.Title>Actions</Card.Title> 187 <Card.Subtitle> 188 What should happen when conditions are met? 189 </Card.Subtitle> 190 </div> 191 <Button onClick={addAction} variant="outline" size="sm"> 192 Add Action 193 </Button> 194 </div> 195 </Card.Header> 196 <Card.Content> 197 <div className="space-y-4"> 198 {workflow.actions.map((action, index) => ( 199 <div key={index} className="border rounded-lg p-4"> 200 <Select 201 label="Action Type" 202 value={action.type} 203 onChange={(value) => { 204 const newActions = [...workflow.actions]; 205 newActions[index].type = value; 206 setWorkflow((prev) => ({ ...prev, actions: newActions })); 207 }} 208 options={actions} 209 /> 210 {/* Action-specific configuration would go here */} 211 </div> 212 ))} 213 </div> 214 </Card.Content> 215 </Card> 216 217 {/* Save */} 218 <div className="flex justify-center"> 219 <Button onClick={saveWorkflow} size="lg"> 220 Save Workflow 221 </Button> 222 </div> 223 </div> 224 ); 225}
Getting Started with Examples
1. Clone an Example
Clone the examples repository1git clone https://github.com/karrioapi/karrio-app-examples.git 2cd karrio-app-examples 3 4# Choose an example 5cd hello-world-app 6 7# Install dependencies 8npm install 9 10# Configure environment 11cp .env.example .env.local 12# Edit .env.local with your settings 13 14# Run in development 15npm run dev
2. Customize for Your Needs
- Update the manifest - Change app ID, name, and permissions
- Modify components - Adapt UI to your requirements
- Add your business logic - Implement your specific features
- Test thoroughly - Use the included test suites
- Deploy - Follow the deployment guide
3. Resources
- GitHub Repository - Complete source code
- Live Demos - See examples in action
- Video Tutorials - Step-by-step walkthroughs
- Community Discord - Get help and share ideas
Next Steps
- Authentication - Implement secure authentication
- API Integration - Connect to Karrio’s APIs
- Deployment - Deploy your app to production
Start building amazing Karrio Apps with these proven examples and patterns!