[claude] Mobile: Paid job submission with inline Lightning invoice (#25) #88
@@ -14,6 +14,7 @@ import {
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
|
||||
import { ConnectionBadge } from "@/components/ConnectionBadge";
|
||||
import { JobSubmissionSheet } from "@/components/JobSubmissionSheet";
|
||||
import { TimmyFace } from "@/components/TimmyFace";
|
||||
import { Colors } from "@/constants/colors";
|
||||
import { useTimmy } from "@/context/TimmyContext";
|
||||
@@ -64,6 +65,7 @@ export default function FaceScreen() {
|
||||
const [isListening, setIsListening] = useState(false);
|
||||
const [transcript, setTranscript] = useState("");
|
||||
const [lastReply, setLastReply] = useState("");
|
||||
const [jobSheetVisible, setJobSheetVisible] = useState(false);
|
||||
const micScale = useRef(new Animated.Value(1)).current;
|
||||
const micPulseRef = useRef<Animated.CompositeAnimation | null>(null);
|
||||
const webRecognitionRef = useRef<WebSpeechRecognition | null>(null);
|
||||
@@ -273,31 +275,48 @@ export default function FaceScreen() {
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{/* Mic button */}
|
||||
{/* Action buttons */}
|
||||
<View style={[styles.micArea, { paddingBottom: bottomPad }]}>
|
||||
<Pressable
|
||||
onPress={handleMicPress}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={isListening ? "Stop listening" : "Start voice"}
|
||||
>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.micButton,
|
||||
isListening && styles.micButtonActive,
|
||||
{ transform: [{ scale: micScale }] },
|
||||
]}
|
||||
<View style={styles.actionRow}>
|
||||
<Pressable
|
||||
onPress={handleMicPress}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={isListening ? "Stop listening" : "Start voice"}
|
||||
>
|
||||
<Ionicons
|
||||
name={isListening ? "mic" : "mic-outline"}
|
||||
size={32}
|
||||
color={isListening ? "#fff" : C.textSecondary}
|
||||
/>
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.micButton,
|
||||
isListening && styles.micButtonActive,
|
||||
{ transform: [{ scale: micScale }] },
|
||||
]}
|
||||
>
|
||||
<Ionicons
|
||||
name={isListening ? "mic" : "mic-outline"}
|
||||
size={32}
|
||||
color={isListening ? "#fff" : C.textSecondary}
|
||||
/>
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
|
||||
<Pressable
|
||||
onPress={() => setJobSheetVisible(true)}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Submit paid job"
|
||||
>
|
||||
<View style={styles.jobButton}>
|
||||
<Ionicons name="flash" size={26} color={C.jobStarted} />
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
<Text style={styles.micHint}>
|
||||
{isListening ? "Listening..." : "Tap to speak to Timmy"}
|
||||
{isListening ? "Listening..." : "Tap mic to speak \u00B7 bolt to submit a job"}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<JobSubmissionSheet
|
||||
visible={jobSheetVisible}
|
||||
onClose={() => setJobSheetVisible(false)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -405,6 +424,26 @@ const styles = StyleSheet.create({
|
||||
paddingTop: 16,
|
||||
gap: 10,
|
||||
},
|
||||
actionRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 20,
|
||||
},
|
||||
jobButton: {
|
||||
width: 52,
|
||||
height: 52,
|
||||
borderRadius: 26,
|
||||
backgroundColor: C.surface,
|
||||
borderWidth: 1.5,
|
||||
borderColor: C.jobStarted + "66",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
shadowColor: C.jobStarted,
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.25,
|
||||
shadowRadius: 6,
|
||||
elevation: 4,
|
||||
},
|
||||
micButton: {
|
||||
width: 72,
|
||||
height: 72,
|
||||
|
||||
737
artifacts/mobile/components/JobSubmissionSheet.tsx
Normal file
737
artifacts/mobile/components/JobSubmissionSheet.tsx
Normal file
@@ -0,0 +1,737 @@
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Animated,
|
||||
Dimensions,
|
||||
Keyboard,
|
||||
Modal,
|
||||
Platform,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import QRCode from "react-native-qrcode-svg";
|
||||
|
||||
import { Colors } from "@/constants/colors";
|
||||
|
||||
const C = Colors.dark;
|
||||
const POLL_INTERVAL = 3000;
|
||||
const SCREEN_WIDTH = Dimensions.get("window").width;
|
||||
const QR_SIZE = Math.min(SCREEN_WIDTH - 80, 240);
|
||||
|
||||
const BASE_URL = process.env.EXPO_PUBLIC_DOMAIN ?? "";
|
||||
|
||||
function getApiBase(): string {
|
||||
let domain = BASE_URL;
|
||||
if (!domain) domain = "localhost:8080";
|
||||
domain = domain.replace(/\/$/, "");
|
||||
if (!/^https?:\/\//.test(domain)) {
|
||||
const proto = domain.startsWith("localhost") ? "http" : "https";
|
||||
domain = `${proto}://${domain}`;
|
||||
}
|
||||
return domain;
|
||||
}
|
||||
|
||||
type JobState =
|
||||
| "awaiting_eval_payment"
|
||||
| "evaluating"
|
||||
| "rejected"
|
||||
| "awaiting_work_payment"
|
||||
| "executing"
|
||||
| "complete"
|
||||
| "failed";
|
||||
|
||||
type InvoiceInfo = {
|
||||
paymentRequest: string;
|
||||
amountSats: number;
|
||||
};
|
||||
|
||||
type JobStatus = {
|
||||
jobId: string;
|
||||
state: JobState;
|
||||
evalInvoice?: InvoiceInfo;
|
||||
workInvoice?: InvoiceInfo;
|
||||
result?: string;
|
||||
reason?: string;
|
||||
errorMessage?: string;
|
||||
};
|
||||
|
||||
type CreateJobResponse = {
|
||||
jobId: string;
|
||||
evalInvoice: InvoiceInfo;
|
||||
};
|
||||
|
||||
type EstimateResponse = {
|
||||
estimatedCostSats: number;
|
||||
estimatedCostUsd: number;
|
||||
btcPriceUsd: number;
|
||||
};
|
||||
|
||||
const STATE_LABELS: Record<string, string> = {
|
||||
awaiting_eval_payment: "Awaiting eval payment",
|
||||
evaluating: "Evaluating your request...",
|
||||
rejected: "Request rejected",
|
||||
awaiting_work_payment: "Awaiting work payment",
|
||||
executing: "Executing job...",
|
||||
complete: "Complete!",
|
||||
failed: "Job failed",
|
||||
};
|
||||
|
||||
const STATE_ICONS: Record<string, string> = {
|
||||
awaiting_eval_payment: "flash-outline",
|
||||
evaluating: "hourglass-outline",
|
||||
rejected: "close-circle-outline",
|
||||
awaiting_work_payment: "flash-outline",
|
||||
executing: "cog-outline",
|
||||
complete: "checkmark-circle-outline",
|
||||
failed: "alert-circle-outline",
|
||||
};
|
||||
|
||||
type Props = {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export function JobSubmissionSheet({ visible, onClose }: Props) {
|
||||
const insets = useSafeAreaInsets();
|
||||
const [prompt, setPrompt] = useState("");
|
||||
const [estimate, setEstimate] = useState<EstimateResponse | null>(null);
|
||||
const [estimateLoading, setEstimateLoading] = useState(false);
|
||||
const [estimateError, setEstimateError] = useState("");
|
||||
const [jobId, setJobId] = useState<string | null>(null);
|
||||
const [jobStatus, setJobStatus] = useState<JobStatus | null>(null);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [submitError, setSubmitError] = useState("");
|
||||
const [resultExpanded, setResultExpanded] = useState(false);
|
||||
const slideAnim = useRef(new Animated.Value(0)).current;
|
||||
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
|
||||
// Slide animation
|
||||
useEffect(() => {
|
||||
Animated.timing(slideAnim, {
|
||||
toValue: visible ? 1 : 0,
|
||||
duration: 300,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
}, [visible, slideAnim]);
|
||||
|
||||
// Polling for job status
|
||||
useEffect(() => {
|
||||
if (!jobId) return;
|
||||
|
||||
const poll = async () => {
|
||||
try {
|
||||
const res = await fetch(`${getApiBase()}/api/jobs/${jobId}`);
|
||||
if (!res.ok) return;
|
||||
const data = (await res.json()) as JobStatus;
|
||||
setJobStatus(data);
|
||||
|
||||
// Stop polling on terminal states
|
||||
if (
|
||||
data.state === "complete" ||
|
||||
data.state === "failed" ||
|
||||
data.state === "rejected"
|
||||
) {
|
||||
if (pollRef.current) {
|
||||
clearInterval(pollRef.current);
|
||||
pollRef.current = null;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore poll errors
|
||||
}
|
||||
};
|
||||
|
||||
// Immediately fetch once
|
||||
void poll();
|
||||
pollRef.current = setInterval(poll, POLL_INTERVAL);
|
||||
|
||||
return () => {
|
||||
if (pollRef.current) {
|
||||
clearInterval(pollRef.current);
|
||||
pollRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [jobId]);
|
||||
|
||||
const resetState = useCallback(() => {
|
||||
setPrompt("");
|
||||
setEstimate(null);
|
||||
setEstimateError("");
|
||||
setJobId(null);
|
||||
setJobStatus(null);
|
||||
setSubmitting(false);
|
||||
setSubmitError("");
|
||||
setResultExpanded(false);
|
||||
if (pollRef.current) {
|
||||
clearInterval(pollRef.current);
|
||||
pollRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
resetState();
|
||||
onClose();
|
||||
}, [onClose, resetState]);
|
||||
|
||||
const handleEstimate = useCallback(async () => {
|
||||
if (!prompt.trim()) return;
|
||||
Keyboard.dismiss();
|
||||
setEstimateLoading(true);
|
||||
setEstimateError("");
|
||||
setEstimate(null);
|
||||
try {
|
||||
const res = await fetch(`${getApiBase()}/api/jobs/estimate`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ request: prompt.trim() }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ error: "Request failed" }));
|
||||
setEstimateError(
|
||||
(err as { error?: string }).error ?? `HTTP ${res.status}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
const data = (await res.json()) as EstimateResponse;
|
||||
setEstimate(data);
|
||||
} catch (e) {
|
||||
setEstimateError(
|
||||
e instanceof Error ? e.message : "Failed to get estimate"
|
||||
);
|
||||
} finally {
|
||||
setEstimateLoading(false);
|
||||
}
|
||||
}, [prompt]);
|
||||
|
||||
const handleSubmit = useCallback(async () => {
|
||||
if (!prompt.trim()) return;
|
||||
Keyboard.dismiss();
|
||||
setSubmitting(true);
|
||||
setSubmitError("");
|
||||
try {
|
||||
const res = await fetch(`${getApiBase()}/api/jobs`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ request: prompt.trim() }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ error: "Request failed" }));
|
||||
setSubmitError(
|
||||
(err as { error?: string }).error ?? `HTTP ${res.status}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
const data = (await res.json()) as CreateJobResponse;
|
||||
setJobId(data.jobId);
|
||||
setJobStatus({
|
||||
jobId: data.jobId,
|
||||
state: "awaiting_eval_payment",
|
||||
evalInvoice: data.evalInvoice,
|
||||
});
|
||||
} catch (e) {
|
||||
setSubmitError(
|
||||
e instanceof Error ? e.message : "Failed to submit job"
|
||||
);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}, [prompt]);
|
||||
|
||||
// Determine which invoice to show
|
||||
const activeInvoice: InvoiceInfo | undefined =
|
||||
jobStatus?.state === "awaiting_work_payment"
|
||||
? jobStatus.workInvoice
|
||||
: jobStatus?.state === "awaiting_eval_payment"
|
||||
? jobStatus.evalInvoice
|
||||
: undefined;
|
||||
|
||||
const isTerminal =
|
||||
jobStatus?.state === "complete" ||
|
||||
jobStatus?.state === "failed" ||
|
||||
jobStatus?.state === "rejected";
|
||||
|
||||
const isPolling = jobId != null && !isTerminal;
|
||||
|
||||
const renderContent = () => {
|
||||
// === JOB IN PROGRESS / COMPLETE ===
|
||||
if (jobId && jobStatus) {
|
||||
return (
|
||||
<ScrollView
|
||||
style={styles.sheetScroll}
|
||||
contentContainerStyle={styles.sheetScrollContent}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
{/* Status badge */}
|
||||
<View style={styles.statusRow}>
|
||||
<Ionicons
|
||||
name={
|
||||
(STATE_ICONS[jobStatus.state] ?? "help-outline") as React.ComponentProps<typeof Ionicons>["name"]
|
||||
}
|
||||
size={20}
|
||||
color={
|
||||
jobStatus.state === "complete"
|
||||
? C.jobCompleted
|
||||
: jobStatus.state === "failed" || jobStatus.state === "rejected"
|
||||
? C.error
|
||||
: C.jobStarted
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={[
|
||||
styles.statusText,
|
||||
jobStatus.state === "complete" && { color: C.jobCompleted },
|
||||
(jobStatus.state === "failed" ||
|
||||
jobStatus.state === "rejected") && { color: C.error },
|
||||
]}
|
||||
>
|
||||
{STATE_LABELS[jobStatus.state] ?? jobStatus.state}
|
||||
</Text>
|
||||
{isPolling && (
|
||||
<ActivityIndicator size="small" color={C.accent} />
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Invoice QR */}
|
||||
{activeInvoice && (
|
||||
<View style={styles.qrContainer}>
|
||||
<Text style={styles.qrLabel}>
|
||||
{jobStatus.state === "awaiting_eval_payment"
|
||||
? "Pay eval invoice"
|
||||
: "Pay work invoice"}
|
||||
</Text>
|
||||
<View style={styles.qrWrapper}>
|
||||
<QRCode
|
||||
value={activeInvoice.paymentRequest}
|
||||
size={QR_SIZE}
|
||||
backgroundColor="#FFFFFF"
|
||||
color="#000000"
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.satsLabel}>
|
||||
{activeInvoice.amountSats} sats
|
||||
</Text>
|
||||
<Text style={styles.invoiceHint}>
|
||||
Scan with your Lightning wallet
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Rejection reason */}
|
||||
{jobStatus.state === "rejected" && jobStatus.reason && (
|
||||
<View style={styles.errorCard}>
|
||||
<Ionicons name="close-circle" size={16} color={C.error} />
|
||||
<Text style={styles.errorCardText}>{jobStatus.reason}</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Error message */}
|
||||
{jobStatus.state === "failed" && jobStatus.errorMessage && (
|
||||
<View style={styles.errorCard}>
|
||||
<Ionicons name="alert-circle" size={16} color={C.error} />
|
||||
<Text style={styles.errorCardText}>
|
||||
{jobStatus.errorMessage}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Result */}
|
||||
{jobStatus.state === "complete" && jobStatus.result && (
|
||||
<Pressable
|
||||
style={styles.resultCard}
|
||||
onPress={() => setResultExpanded((v) => !v)}
|
||||
>
|
||||
<View style={styles.resultHeader}>
|
||||
<Ionicons
|
||||
name="checkmark-circle"
|
||||
size={18}
|
||||
color={C.jobCompleted}
|
||||
/>
|
||||
<Text style={styles.resultTitle}>Result</Text>
|
||||
<Ionicons
|
||||
name={resultExpanded ? "chevron-up" : "chevron-down"}
|
||||
size={16}
|
||||
color={C.textSecondary}
|
||||
/>
|
||||
</View>
|
||||
<Text
|
||||
style={styles.resultText}
|
||||
numberOfLines={resultExpanded ? undefined : 6}
|
||||
>
|
||||
{jobStatus.result}
|
||||
</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
|
||||
{/* New job button for terminal states */}
|
||||
{isTerminal && (
|
||||
<Pressable style={styles.newJobButton} onPress={resetState}>
|
||||
<Text style={styles.newJobButtonText}>Submit another job</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
// === INPUT FORM ===
|
||||
return (
|
||||
<ScrollView
|
||||
style={styles.sheetScroll}
|
||||
contentContainerStyle={styles.sheetScrollContent}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
<Text style={styles.inputLabel}>What should Timmy do?</Text>
|
||||
<TextInput
|
||||
style={styles.textInput}
|
||||
placeholder="Describe your job..."
|
||||
placeholderTextColor={C.textMuted}
|
||||
value={prompt}
|
||||
onChangeText={setPrompt}
|
||||
multiline
|
||||
numberOfLines={4}
|
||||
textAlignVertical="top"
|
||||
maxLength={2000}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
{/* Estimate result */}
|
||||
{estimate && (
|
||||
<View style={styles.estimateCard}>
|
||||
<Ionicons name="flash" size={16} color={C.jobStarted} />
|
||||
<Text style={styles.estimateText}>
|
||||
Estimated cost: {estimate.estimatedCostSats} sats (~$
|
||||
{estimate.estimatedCostUsd.toFixed(4)})
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{estimateError ? (
|
||||
<View style={styles.errorCard}>
|
||||
<Ionicons name="alert-circle" size={16} color={C.error} />
|
||||
<Text style={styles.errorCardText}>{estimateError}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{submitError ? (
|
||||
<View style={styles.errorCard}>
|
||||
<Ionicons name="alert-circle" size={16} color={C.error} />
|
||||
<Text style={styles.errorCardText}>{submitError}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{/* Action buttons */}
|
||||
<View style={styles.buttonRow}>
|
||||
<Pressable
|
||||
style={[
|
||||
styles.estimateButton,
|
||||
(!prompt.trim() || estimateLoading) && styles.buttonDisabled,
|
||||
]}
|
||||
onPress={handleEstimate}
|
||||
disabled={!prompt.trim() || estimateLoading}
|
||||
>
|
||||
{estimateLoading ? (
|
||||
<ActivityIndicator size="small" color={C.accent} />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="calculator-outline" size={18} color={C.accent} />
|
||||
<Text style={styles.estimateButtonText}>Estimate</Text>
|
||||
</>
|
||||
)}
|
||||
</Pressable>
|
||||
|
||||
<Pressable
|
||||
style={[
|
||||
styles.submitButton,
|
||||
(!prompt.trim() || submitting) && styles.buttonDisabled,
|
||||
]}
|
||||
onPress={handleSubmit}
|
||||
disabled={!prompt.trim() || submitting}
|
||||
>
|
||||
{submitting ? (
|
||||
<ActivityIndicator size="small" color="#fff" />
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="flash" size={18} color="#fff" />
|
||||
<Text style={styles.submitButtonText}>Submit Job</Text>
|
||||
</>
|
||||
)}
|
||||
</Pressable>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
animationType="slide"
|
||||
transparent
|
||||
onRequestClose={handleClose}
|
||||
>
|
||||
<View style={styles.overlay}>
|
||||
<Pressable style={styles.overlayBackdrop} onPress={handleClose} />
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.sheet,
|
||||
{
|
||||
paddingBottom: Math.max(insets.bottom, 16),
|
||||
transform: [
|
||||
{
|
||||
translateY: slideAnim.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [600, 0],
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
>
|
||||
{/* Handle bar */}
|
||||
<View style={styles.handleRow}>
|
||||
<View style={styles.handle} />
|
||||
</View>
|
||||
|
||||
{/* Header */}
|
||||
<View style={styles.sheetHeader}>
|
||||
<Ionicons name="flash" size={22} color={C.jobStarted} />
|
||||
<Text style={styles.sheetTitle}>Submit Job</Text>
|
||||
<Pressable
|
||||
onPress={handleClose}
|
||||
hitSlop={12}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Close"
|
||||
>
|
||||
<Ionicons name="close" size={24} color={C.textSecondary} />
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
{renderContent()}
|
||||
</Animated.View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
overlay: {
|
||||
flex: 1,
|
||||
justifyContent: "flex-end",
|
||||
},
|
||||
overlayBackdrop: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: "rgba(0,0,0,0.5)",
|
||||
},
|
||||
sheet: {
|
||||
backgroundColor: C.surface,
|
||||
borderTopLeftRadius: 20,
|
||||
borderTopRightRadius: 20,
|
||||
maxHeight: "85%",
|
||||
borderWidth: 1,
|
||||
borderBottomWidth: 0,
|
||||
borderColor: C.border,
|
||||
},
|
||||
handleRow: {
|
||||
alignItems: "center",
|
||||
paddingTop: 10,
|
||||
paddingBottom: 4,
|
||||
},
|
||||
handle: {
|
||||
width: 36,
|
||||
height: 4,
|
||||
borderRadius: 2,
|
||||
backgroundColor: C.textMuted,
|
||||
},
|
||||
sheetHeader: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 12,
|
||||
gap: 10,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: C.border,
|
||||
},
|
||||
sheetTitle: {
|
||||
flex: 1,
|
||||
fontSize: 18,
|
||||
fontFamily: "Inter_600SemiBold",
|
||||
color: C.text,
|
||||
},
|
||||
sheetScroll: {
|
||||
flex: 1,
|
||||
},
|
||||
sheetScrollContent: {
|
||||
padding: 20,
|
||||
gap: 16,
|
||||
},
|
||||
inputLabel: {
|
||||
fontSize: 14,
|
||||
fontFamily: "Inter_500Medium",
|
||||
color: C.textSecondary,
|
||||
},
|
||||
textInput: {
|
||||
backgroundColor: C.surfaceElevated,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: C.border,
|
||||
color: C.text,
|
||||
fontFamily: "Inter_400Regular",
|
||||
fontSize: 15,
|
||||
padding: 14,
|
||||
minHeight: 100,
|
||||
...(Platform.OS === "web" ? { outlineStyle: "none" as unknown as undefined } : {}),
|
||||
},
|
||||
estimateCard: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
backgroundColor: C.surfaceElevated,
|
||||
borderRadius: 10,
|
||||
padding: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: C.jobStarted + "44",
|
||||
},
|
||||
estimateText: {
|
||||
fontSize: 14,
|
||||
fontFamily: "Inter_500Medium",
|
||||
color: C.jobStarted,
|
||||
flex: 1,
|
||||
},
|
||||
errorCard: {
|
||||
flexDirection: "row",
|
||||
alignItems: "flex-start",
|
||||
gap: 8,
|
||||
backgroundColor: C.error + "18",
|
||||
borderRadius: 10,
|
||||
padding: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: C.error + "44",
|
||||
},
|
||||
errorCardText: {
|
||||
fontSize: 13,
|
||||
fontFamily: "Inter_400Regular",
|
||||
color: C.error,
|
||||
flex: 1,
|
||||
},
|
||||
buttonRow: {
|
||||
flexDirection: "row",
|
||||
gap: 12,
|
||||
},
|
||||
estimateButton: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 6,
|
||||
backgroundColor: C.surfaceElevated,
|
||||
borderRadius: 12,
|
||||
paddingVertical: 14,
|
||||
borderWidth: 1,
|
||||
borderColor: C.accent + "44",
|
||||
},
|
||||
estimateButtonText: {
|
||||
fontSize: 15,
|
||||
fontFamily: "Inter_600SemiBold",
|
||||
color: C.accent,
|
||||
},
|
||||
submitButton: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 6,
|
||||
backgroundColor: C.accent,
|
||||
borderRadius: 12,
|
||||
paddingVertical: 14,
|
||||
},
|
||||
submitButtonText: {
|
||||
fontSize: 15,
|
||||
fontFamily: "Inter_600SemiBold",
|
||||
color: "#fff",
|
||||
},
|
||||
buttonDisabled: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
statusRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
backgroundColor: C.surfaceElevated,
|
||||
borderRadius: 10,
|
||||
padding: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: C.border,
|
||||
},
|
||||
statusText: {
|
||||
flex: 1,
|
||||
fontSize: 14,
|
||||
fontFamily: "Inter_500Medium",
|
||||
color: C.text,
|
||||
},
|
||||
qrContainer: {
|
||||
alignItems: "center",
|
||||
gap: 12,
|
||||
},
|
||||
qrLabel: {
|
||||
fontSize: 14,
|
||||
fontFamily: "Inter_500Medium",
|
||||
color: C.textSecondary,
|
||||
},
|
||||
qrWrapper: {
|
||||
backgroundColor: "#FFFFFF",
|
||||
padding: 16,
|
||||
borderRadius: 12,
|
||||
},
|
||||
satsLabel: {
|
||||
fontSize: 20,
|
||||
fontFamily: "Inter_700Bold",
|
||||
color: C.jobStarted,
|
||||
},
|
||||
invoiceHint: {
|
||||
fontSize: 12,
|
||||
fontFamily: "Inter_400Regular",
|
||||
color: C.textMuted,
|
||||
},
|
||||
resultCard: {
|
||||
backgroundColor: C.surfaceElevated,
|
||||
borderRadius: 12,
|
||||
padding: 14,
|
||||
borderWidth: 1,
|
||||
borderColor: C.jobCompleted + "44",
|
||||
gap: 10,
|
||||
},
|
||||
resultHeader: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
},
|
||||
resultTitle: {
|
||||
flex: 1,
|
||||
fontSize: 15,
|
||||
fontFamily: "Inter_600SemiBold",
|
||||
color: C.text,
|
||||
},
|
||||
resultText: {
|
||||
fontSize: 14,
|
||||
fontFamily: "Inter_400Regular",
|
||||
color: C.text,
|
||||
lineHeight: 20,
|
||||
},
|
||||
newJobButton: {
|
||||
alignItems: "center",
|
||||
paddingVertical: 14,
|
||||
backgroundColor: C.surfaceElevated,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: C.border,
|
||||
},
|
||||
newJobButtonText: {
|
||||
fontSize: 15,
|
||||
fontFamily: "Inter_500Medium",
|
||||
color: C.accent,
|
||||
},
|
||||
});
|
||||
@@ -58,6 +58,7 @@
|
||||
"dependencies": {
|
||||
"@react-native-voice/voice": "^3.2.4",
|
||||
"expo-speech": "^14.0.8",
|
||||
"react-native-qrcode-svg": "^6.3.21",
|
||||
"react-native-webview": "^13.15.0"
|
||||
}
|
||||
}
|
||||
|
||||
119
pnpm-lock.yaml
generated
119
pnpm-lock.yaml
generated
@@ -233,6 +233,9 @@ importers:
|
||||
expo-speech:
|
||||
specifier: ^14.0.8
|
||||
version: 14.0.8(expo@54.0.33)
|
||||
react-native-qrcode-svg:
|
||||
specifier: ^6.3.21
|
||||
version: 6.3.21(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
react-native-webview:
|
||||
specifier: ^13.15.0
|
||||
version: 13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
@@ -3024,6 +3027,9 @@ packages:
|
||||
client-only@0.0.1:
|
||||
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
||||
|
||||
cliui@6.0.0:
|
||||
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
|
||||
|
||||
cliui@8.0.1:
|
||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -3241,6 +3247,10 @@ packages:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
decamelize@1.2.0:
|
||||
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
decimal.js-light@2.5.1:
|
||||
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
|
||||
|
||||
@@ -3289,6 +3299,9 @@ packages:
|
||||
detect-node-es@1.1.0:
|
||||
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
||||
|
||||
dijkstrajs@1.0.3:
|
||||
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
|
||||
|
||||
dom-helpers@5.2.1:
|
||||
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
|
||||
|
||||
@@ -4960,6 +4973,10 @@ packages:
|
||||
resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
|
||||
pngjs@5.0.0:
|
||||
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
postcss-value-parser@4.2.0:
|
||||
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
|
||||
|
||||
@@ -5052,6 +5069,11 @@ packages:
|
||||
resolution: {integrity: sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==}
|
||||
hasBin: true
|
||||
|
||||
qrcode@1.5.4:
|
||||
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
hasBin: true
|
||||
|
||||
qs@6.15.0:
|
||||
resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==}
|
||||
engines: {node: '>=0.6'}
|
||||
@@ -5139,6 +5161,13 @@ packages:
|
||||
react-native: '*'
|
||||
react-native-reanimated: '>=3.0.0'
|
||||
|
||||
react-native-qrcode-svg@6.3.21:
|
||||
resolution: {integrity: sha512-6vcj4rcdpWedvphDR+NSJcudJykNuLgNGFwm2p4xYjR8RdyTzlrELKI5LkO4ANS9cQUbqsfkpippPv64Q2tUtA==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
react-native: '>=0.63.4'
|
||||
react-native-svg: '>=14.0.0'
|
||||
|
||||
react-native-reanimated@4.1.6:
|
||||
resolution: {integrity: sha512-F+ZJBYiok/6Jzp1re75F/9aLzkgoQCOh4yxrnwATa8392RvM3kx+fiXXFvwcgE59v48lMwd9q0nzF1oJLXpfxQ==}
|
||||
peerDependencies:
|
||||
@@ -5305,6 +5334,9 @@ packages:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
require-main-filename@2.0.0:
|
||||
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
|
||||
|
||||
requireg@0.2.2:
|
||||
resolution: {integrity: sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==}
|
||||
engines: {node: '>= 4.0.0'}
|
||||
@@ -5421,6 +5453,9 @@ packages:
|
||||
server-only@0.0.1:
|
||||
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
|
||||
|
||||
set-blocking@2.0.0:
|
||||
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
||||
|
||||
setimmediate@1.0.5:
|
||||
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
|
||||
|
||||
@@ -5636,6 +5671,10 @@ packages:
|
||||
resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
text-encoding@0.7.0:
|
||||
resolution: {integrity: sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==}
|
||||
deprecated: no longer maintained
|
||||
|
||||
thenify-all@1.6.0:
|
||||
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
|
||||
engines: {node: '>=0.8'}
|
||||
@@ -5943,6 +5982,9 @@ packages:
|
||||
whatwg-url@5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
|
||||
which-module@2.0.1:
|
||||
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
|
||||
|
||||
which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -5955,6 +5997,10 @@ packages:
|
||||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
wrap-ansi@6.2.0:
|
||||
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -6036,6 +6082,9 @@ packages:
|
||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||
engines: {node: '>=0.4'}
|
||||
|
||||
y18n@4.0.3:
|
||||
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
|
||||
|
||||
y18n@5.0.8:
|
||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -6056,10 +6105,18 @@ packages:
|
||||
engines: {node: '>= 14.6'}
|
||||
hasBin: true
|
||||
|
||||
yargs-parser@18.1.3:
|
||||
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
yargs-parser@21.1.1:
|
||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
yargs@15.4.1:
|
||||
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
yargs@17.7.2:
|
||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -9313,6 +9370,12 @@ snapshots:
|
||||
|
||||
client-only@0.0.1: {}
|
||||
|
||||
cliui@6.0.0:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 6.2.0
|
||||
|
||||
cliui@8.0.1:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
@@ -9517,6 +9580,8 @@ snapshots:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
decamelize@1.2.0: {}
|
||||
|
||||
decimal.js-light@2.5.1: {}
|
||||
|
||||
decode-uri-component@0.2.2: {}
|
||||
@@ -9547,6 +9612,8 @@ snapshots:
|
||||
|
||||
detect-node-es@1.1.0: {}
|
||||
|
||||
dijkstrajs@1.0.3: {}
|
||||
|
||||
dom-helpers@5.2.1:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
@@ -11432,6 +11499,8 @@ snapshots:
|
||||
|
||||
pngjs@3.4.0: {}
|
||||
|
||||
pngjs@5.0.0: {}
|
||||
|
||||
postcss-value-parser@4.2.0: {}
|
||||
|
||||
postcss@8.4.49:
|
||||
@@ -11526,6 +11595,12 @@ snapshots:
|
||||
|
||||
qrcode-terminal@0.11.0: {}
|
||||
|
||||
qrcode@1.5.4:
|
||||
dependencies:
|
||||
dijkstrajs: 1.0.3
|
||||
pngjs: 5.0.0
|
||||
yargs: 15.4.1
|
||||
|
||||
qs@6.15.0:
|
||||
dependencies:
|
||||
side-channel: 1.1.0
|
||||
@@ -11618,6 +11693,15 @@ snapshots:
|
||||
react-native-is-edge-to-edge: 1.3.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
react-native-reanimated: 4.1.6(@babel/core@7.29.0)(react-native-worklets@0.5.1(@babel/core@7.29.0)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
|
||||
react-native-qrcode-svg@6.3.21(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
prop-types: 15.8.1
|
||||
qrcode: 1.5.4
|
||||
react: 19.1.0
|
||||
react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)
|
||||
react-native-svg: 15.12.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
text-encoding: 0.7.0
|
||||
|
||||
react-native-reanimated@4.1.6(@babel/core@7.29.0)(react-native-worklets@0.5.1(@babel/core@7.29.0)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
@@ -11868,6 +11952,8 @@ snapshots:
|
||||
|
||||
require-from-string@2.0.2: {}
|
||||
|
||||
require-main-filename@2.0.0: {}
|
||||
|
||||
requireg@0.2.2:
|
||||
dependencies:
|
||||
nested-error-stacks: 2.0.1
|
||||
@@ -12004,6 +12090,8 @@ snapshots:
|
||||
|
||||
server-only@0.0.1: {}
|
||||
|
||||
set-blocking@2.0.0: {}
|
||||
|
||||
setimmediate@1.0.5: {}
|
||||
|
||||
setprototypeof@1.2.0: {}
|
||||
@@ -12199,6 +12287,8 @@ snapshots:
|
||||
glob: 7.2.3
|
||||
minimatch: 3.1.5
|
||||
|
||||
text-encoding@0.7.0: {}
|
||||
|
||||
thenify-all@1.6.0:
|
||||
dependencies:
|
||||
thenify: 3.3.1
|
||||
@@ -12465,6 +12555,8 @@ snapshots:
|
||||
tr46: 0.0.3
|
||||
webidl-conversions: 3.0.1
|
||||
|
||||
which-module@2.0.1: {}
|
||||
|
||||
which@2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
@@ -12473,6 +12565,12 @@ snapshots:
|
||||
|
||||
word-wrap@1.2.5: {}
|
||||
|
||||
wrap-ansi@6.2.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
@@ -12525,6 +12623,8 @@ snapshots:
|
||||
|
||||
xtend@4.0.2: {}
|
||||
|
||||
y18n@4.0.3: {}
|
||||
|
||||
y18n@5.0.8: {}
|
||||
|
||||
yallist@3.1.1: {}
|
||||
@@ -12535,8 +12635,27 @@ snapshots:
|
||||
|
||||
yaml@2.8.2: {}
|
||||
|
||||
yargs-parser@18.1.3:
|
||||
dependencies:
|
||||
camelcase: 5.3.1
|
||||
decamelize: 1.2.0
|
||||
|
||||
yargs-parser@21.1.1: {}
|
||||
|
||||
yargs@15.4.1:
|
||||
dependencies:
|
||||
cliui: 6.0.0
|
||||
decamelize: 1.2.0
|
||||
find-up: 4.1.0
|
||||
get-caller-file: 2.0.5
|
||||
require-directory: 2.1.1
|
||||
require-main-filename: 2.0.0
|
||||
set-blocking: 2.0.0
|
||||
string-width: 4.2.3
|
||||
which-module: 2.0.1
|
||||
y18n: 4.0.3
|
||||
yargs-parser: 18.1.3
|
||||
|
||||
yargs@17.7.2:
|
||||
dependencies:
|
||||
cliui: 8.0.1
|
||||
|
||||
Reference in New Issue
Block a user