From 82e03978b89938219958032efb1448cc76baa181 Mon Sep 17 00:00:00 2001 From: Saumit Date: Sat, 27 Sep 2025 02:14:26 +0530 Subject: Initial snapshot - OpenTelemetry demo 2.1.3 -f --- .../components/CheckoutForm/CheckoutForm.tsx | 198 +++++++++++++++++++++ .../components/CheckoutForm/index.ts | 3 + .../components/EmptyCart/EmptyCart.tsx | 37 ++++ src/react-native-app/components/EmptyCart/index.ts | 3 + src/react-native-app/components/Field.tsx | 30 ++++ .../components/ProductCard/ProductCard.tsx | 102 +++++++++++ .../components/ProductCard/index.ts | 3 + .../components/ProductList/ProductList.tsx | 50 ++++++ .../components/ProductList/index.ts | 3 + src/react-native-app/components/Setting.tsx | 76 ++++++++ .../components/ThemedScrollView.tsx | 23 +++ src/react-native-app/components/ThemedText.tsx | 62 +++++++ src/react-native-app/components/ThemedView.tsx | 24 +++ .../components/navigation/TabBarIcon.tsx | 14 ++ 14 files changed, 628 insertions(+) create mode 100644 src/react-native-app/components/CheckoutForm/CheckoutForm.tsx create mode 100644 src/react-native-app/components/CheckoutForm/index.ts create mode 100644 src/react-native-app/components/EmptyCart/EmptyCart.tsx create mode 100644 src/react-native-app/components/EmptyCart/index.ts create mode 100644 src/react-native-app/components/Field.tsx create mode 100644 src/react-native-app/components/ProductCard/ProductCard.tsx create mode 100644 src/react-native-app/components/ProductCard/index.ts create mode 100644 src/react-native-app/components/ProductList/ProductList.tsx create mode 100644 src/react-native-app/components/ProductList/index.ts create mode 100644 src/react-native-app/components/Setting.tsx create mode 100644 src/react-native-app/components/ThemedScrollView.tsx create mode 100644 src/react-native-app/components/ThemedText.tsx create mode 100644 src/react-native-app/components/ThemedView.tsx create mode 100644 src/react-native-app/components/navigation/TabBarIcon.tsx (limited to 'src/react-native-app/components') diff --git a/src/react-native-app/components/CheckoutForm/CheckoutForm.tsx b/src/react-native-app/components/CheckoutForm/CheckoutForm.tsx new file mode 100644 index 0000000..378246a --- /dev/null +++ b/src/react-native-app/components/CheckoutForm/CheckoutForm.tsx @@ -0,0 +1,198 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +/** + * Copied with modification from src/frontend/components/CheckoutForm/CheckoutForm.tsx + */ +import { ThemedScrollView } from "@/components/ThemedScrollView"; +import { Field } from "@/components/Field"; +import { StyleSheet, Pressable } from "react-native"; +import { useForm, Controller } from "react-hook-form"; +import { ThemedText } from "@/components/ThemedText"; +import { ThemedView } from "@/components/ThemedView"; + +export interface IFormData { + email: string; + streetAddress: string; + city: string; + state: string; + country: string; + zipCode: string; + creditCardNumber: string; + creditCardCvv: number; + creditCardExpirationYear: number; + creditCardExpirationMonth: number; +} + +interface IProps { + onSubmit(formData: IFormData): void; +} + +const CheckoutForm = ({ onSubmit }: IProps) => { + const { control, handleSubmit } = useForm({ + defaultValues: { + email: "someone@example.com", + streetAddress: "1600 Amphitheatre Parkway", + city: "Mountain View", + state: "CA", + country: "United States", + zipCode: "94043", + creditCardNumber: "4432-8015-6152-0454", + creditCardCvv: 672, + creditCardExpirationYear: 2030, + creditCardExpirationMonth: 1, + }, + }); + + return ( + + ( + + )} + name="email" + /> + ( + + )} + name="streetAddress" + /> + ( + + )} + name="zipCode" + /> + ( + + )} + name="country" + /> + ( + + )} + name="creditCardNumber" + /> + ( + + )} + name="creditCardExpirationMonth" + /> + ( + + )} + name="creditCardExpirationYear" + /> + ( + + )} + name="creditCardCvv" + /> + + + Place Order + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + marginLeft: 30, + }, + submitContainer: { + display: "flex", + justifyContent: "center", + alignItems: "center", + margin: 20, + }, + submit: { + borderRadius: 4, + backgroundColor: "blue", + alignItems: "center", + justifyContent: "center", + width: 150, + padding: 10, + position: "relative", + }, + submitText: { + color: "white", + fontSize: 20, + }, +}); + +export default CheckoutForm; diff --git a/src/react-native-app/components/CheckoutForm/index.ts b/src/react-native-app/components/CheckoutForm/index.ts new file mode 100644 index 0000000..dc8dd0b --- /dev/null +++ b/src/react-native-app/components/CheckoutForm/index.ts @@ -0,0 +1,3 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +export { default } from "./CheckoutForm"; diff --git a/src/react-native-app/components/EmptyCart/EmptyCart.tsx b/src/react-native-app/components/EmptyCart/EmptyCart.tsx new file mode 100644 index 0000000..962cf28 --- /dev/null +++ b/src/react-native-app/components/EmptyCart/EmptyCart.tsx @@ -0,0 +1,37 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +/** + * Copied with modification from src/frontend/components/Cart/EmptyCart.tsx + */ +import { ThemedView } from "@/components/ThemedView"; +import { ThemedText } from "@/components/ThemedText"; +import { StyleSheet } from "react-native"; + +const EmptyCart = () => { + return ( + + + Your shopping cart is empty! + + + Items you add to your shopping cart will appear here. + + + ); +}; + +export default EmptyCart; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, + header: { + fontSize: 20, + }, + subHeader: { + fontSize: 14, + }, +}); diff --git a/src/react-native-app/components/EmptyCart/index.ts b/src/react-native-app/components/EmptyCart/index.ts new file mode 100644 index 0000000..9f421d1 --- /dev/null +++ b/src/react-native-app/components/EmptyCart/index.ts @@ -0,0 +1,3 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +export { default } from "./EmptyCart"; diff --git a/src/react-native-app/components/Field.tsx b/src/react-native-app/components/Field.tsx new file mode 100644 index 0000000..d0f8e96 --- /dev/null +++ b/src/react-native-app/components/Field.tsx @@ -0,0 +1,30 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +import { StyleSheet, TextInput, type TextInputProps } from "react-native"; +import { ThemedView } from "@/components/ThemedView"; +import { ThemedText } from "@/components/ThemedText"; +import { useThemeColor } from "@/hooks/useThemeColor"; + +export type FieldProps = TextInputProps & { + label: string; +}; + +export function Field({ label, ...otherProps }: FieldProps) { + const color = useThemeColor({}, "text"); + + return ( + + {label}: + + + ); +} + +const styles = StyleSheet.create({ + container: { + display: "flex", + flexDirection: "row", + gap: 10, + alignItems: "center", + }, +}); diff --git a/src/react-native-app/components/ProductCard/ProductCard.tsx b/src/react-native-app/components/ProductCard/ProductCard.tsx new file mode 100644 index 0000000..d5bd301 --- /dev/null +++ b/src/react-native-app/components/ProductCard/ProductCard.tsx @@ -0,0 +1,102 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +/** + * Copied with modification from src/frontend/components/ProductCard/ProductCard.tsx + */ +import { Product } from "@/protos/demo"; +import { ThemedView } from "@/components/ThemedView"; +import { useState, useEffect, useMemo } from "react"; +import { Image, Pressable, StyleSheet } from "react-native"; +import { ThemedText } from "@/components/ThemedText"; +import { useThemeColor } from "@/hooks/useThemeColor"; +import getFrontendProxyURL from "@/utils/Settings"; + +interface IProps { + product: Product; + onClickAdd: () => void; +} + +async function getImageURL(picture: string) { + const proxyURL = await getFrontendProxyURL(); + return `${proxyURL}/images/products/${picture}`; +} + +const ProductCard = ({ + product: { + picture, + name, + priceUsd = { + currencyCode: "USD", + units: 0, + nanos: 0, + }, + }, + onClickAdd, +}: IProps) => { + const tint = useThemeColor({}, "tint"); + const styles = useMemo(() => getStyles(tint), [tint]); + const [imageSrc, setImageSrc] = useState(""); + + useEffect(() => { + getImageURL(picture) + .then(setImageSrc) + .catch((reason) => { + console.warn("Failed to get image URL: ", reason); + }); + }, [picture]); + + // TODO simplify react native demo for now by hard-coding the selected currency + const price = (priceUsd?.units + priceUsd?.nanos / 100000000).toFixed(2); + + return ( + + + {imageSrc && } + + + {name} + $ {price} + + Add to Cart + + + + ); +}; + +const getStyles = (tint: string) => + StyleSheet.create({ + container: { + display: "flex", + flexDirection: "row", + padding: 20, + marginLeft: 10, + marginRight: 10, + borderStyle: "solid", + borderBottomWidth: 1, + borderColor: tint, + gap: 30, + }, + image: { + width: 100, + height: 100, + }, + productInfo: { + flexShrink: 1, + }, + name: {}, + price: { + fontWeight: "bold", + }, + add: { + borderRadius: 4, + backgroundColor: "green", + alignItems: "center", + width: 100, + }, + addText: { + color: "white", + }, + }); + +export default ProductCard; diff --git a/src/react-native-app/components/ProductCard/index.ts b/src/react-native-app/components/ProductCard/index.ts new file mode 100644 index 0000000..e088670 --- /dev/null +++ b/src/react-native-app/components/ProductCard/index.ts @@ -0,0 +1,3 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +export { default } from "./ProductCard"; diff --git a/src/react-native-app/components/ProductList/ProductList.tsx b/src/react-native-app/components/ProductList/ProductList.tsx new file mode 100644 index 0000000..30bd6ef --- /dev/null +++ b/src/react-native-app/components/ProductList/ProductList.tsx @@ -0,0 +1,50 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +/** + * Copied with modification from: + * src/frontend/pages/product/[productId]/index.tsx + * src/frontend/components/ProductList/ProductList.tsx + */ +import Toast from "react-native-toast-message"; +import { useCallback } from "react"; +import ProductCard from "@/components/ProductCard"; +import { Product } from "@/protos/demo"; +import { ThemedScrollView } from "@/components/ThemedScrollView"; +import { useCart } from "@/providers/Cart.provider"; + +interface IProps { + productList: Product[]; +} + +const ProductList = ({ productList }: IProps) => { + const { addItem } = useCart(); + const onAddItem = useCallback( + async (id: string) => { + addItem({ + productId: id, + quantity: 1, + }); + + Toast.show({ + type: "success", + position: "bottom", + text1: "This item has been added to your cart", + }); + }, + [addItem], + ); + + return ( + + {productList.map((product) => ( + onAddItem(product.id)} + /> + ))} + + ); +}; + +export default ProductList; diff --git a/src/react-native-app/components/ProductList/index.ts b/src/react-native-app/components/ProductList/index.ts new file mode 100644 index 0000000..d3e14bb --- /dev/null +++ b/src/react-native-app/components/ProductList/index.ts @@ -0,0 +1,3 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +export { default } from "./ProductList"; diff --git a/src/react-native-app/components/Setting.tsx b/src/react-native-app/components/Setting.tsx new file mode 100644 index 0000000..d7db519 --- /dev/null +++ b/src/react-native-app/components/Setting.tsx @@ -0,0 +1,76 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +import {Pressable, StyleSheet, TextInput, type TextInputProps} from "react-native"; +import { ThemedView } from "@/components/ThemedView"; +import { ThemedText } from "@/components/ThemedText"; +import { useThemeColor } from "@/hooks/useThemeColor"; +import {useCallback, useEffect, useState} from "react"; +import Toast from "react-native-toast-message"; + +export type SettingProps = TextInputProps & { + name: string; + get: () => Promise; + set: (value: string) => Promise; +}; + +export function Setting({ name, get, set, ...otherProps }: SettingProps) { + const color = useThemeColor({}, "text"); + const [loading, setLoading] = useState(false); + const [text, setText] = useState(''); + + useEffect(() => { + get().then(existingValue => { + setText(existingValue); + setLoading(false); + }); + }, []); + + const onApply = useCallback(async () => { + await set(text); + + Toast.show({ + type: "success", + position: "bottom", + text1: `${name} applied`, + }); + }, [text]); + + return ( + + {name}: + {loading ? ( + Fetching current value... + ) : ( + <> + + + Apply + + + )} + + ); +} + + +const styles = StyleSheet.create({ + container: { + display: "flex", + gap: 5, + }, + apply: { + borderRadius: 4, + backgroundColor: "green", + alignItems: "center", + width: 100, + position: "relative", + }, + applyText: { + color: "white", + }, +}); diff --git a/src/react-native-app/components/ThemedScrollView.tsx b/src/react-native-app/components/ThemedScrollView.tsx new file mode 100644 index 0000000..34f5d21 --- /dev/null +++ b/src/react-native-app/components/ThemedScrollView.tsx @@ -0,0 +1,23 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +import { ScrollView, type ScrollViewProps } from "react-native"; +import { useThemeColor } from "@/hooks/useThemeColor"; + +export type ThemedViewProps = ScrollViewProps & { + lightColor?: string; + darkColor?: string; +}; + +export function ThemedScrollView({ + style, + lightColor, + darkColor, + ...otherProps +}: ThemedViewProps) { + const backgroundColor = useThemeColor( + { light: lightColor, dark: darkColor }, + "background", + ); + + return ; +} diff --git a/src/react-native-app/components/ThemedText.tsx b/src/react-native-app/components/ThemedText.tsx new file mode 100644 index 0000000..cf6abf8 --- /dev/null +++ b/src/react-native-app/components/ThemedText.tsx @@ -0,0 +1,62 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +import { Text, type TextProps, StyleSheet } from "react-native"; + +import { useThemeColor } from "@/hooks/useThemeColor"; + +export type ThemedTextProps = TextProps & { + lightColor?: string; + darkColor?: string; + type?: "default" | "title" | "defaultSemiBold" | "subtitle" | "link"; +}; + +export function ThemedText({ + style, + lightColor, + darkColor, + type = "default", + ...rest +}: ThemedTextProps) { + const color = useThemeColor({ light: lightColor, dark: darkColor }, "text"); + + return ( + + ); +} + +const styles = StyleSheet.create({ + default: { + fontSize: 16, + lineHeight: 24, + }, + defaultSemiBold: { + fontSize: 16, + lineHeight: 24, + fontWeight: "600", + }, + title: { + fontSize: 32, + fontWeight: "bold", + lineHeight: 32, + }, + subtitle: { + fontSize: 20, + fontWeight: "bold", + }, + link: { + lineHeight: 30, + fontSize: 16, + color: "#0a7ea4", + }, +}); diff --git a/src/react-native-app/components/ThemedView.tsx b/src/react-native-app/components/ThemedView.tsx new file mode 100644 index 0000000..94668b3 --- /dev/null +++ b/src/react-native-app/components/ThemedView.tsx @@ -0,0 +1,24 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +import { View, type ViewProps } from "react-native"; + +import { useThemeColor } from "@/hooks/useThemeColor"; + +export type ThemedViewProps = ViewProps & { + lightColor?: string; + darkColor?: string; +}; + +export function ThemedView({ + style, + lightColor, + darkColor, + ...otherProps +}: ThemedViewProps) { + const backgroundColor = useThemeColor( + { light: lightColor, dark: darkColor }, + "background", + ); + + return ; +} diff --git a/src/react-native-app/components/navigation/TabBarIcon.tsx b/src/react-native-app/components/navigation/TabBarIcon.tsx new file mode 100644 index 0000000..42f43b2 --- /dev/null +++ b/src/react-native-app/components/navigation/TabBarIcon.tsx @@ -0,0 +1,14 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 +// You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/ + +import Ionicons from "@expo/vector-icons/Ionicons"; +import { type IconProps } from "@expo/vector-icons/build/createIconSet"; +import { type ComponentProps } from "react"; + +export function TabBarIcon({ + style, + ...rest +}: IconProps["name"]>) { + return ; +} -- cgit v1.2.3