summaryrefslogtreecommitdiff
path: root/src/react-native-app/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/react-native-app/components')
-rw-r--r--src/react-native-app/components/CheckoutForm/CheckoutForm.tsx198
-rw-r--r--src/react-native-app/components/CheckoutForm/index.ts3
-rw-r--r--src/react-native-app/components/EmptyCart/EmptyCart.tsx37
-rw-r--r--src/react-native-app/components/EmptyCart/index.ts3
-rw-r--r--src/react-native-app/components/Field.tsx30
-rw-r--r--src/react-native-app/components/ProductCard/ProductCard.tsx102
-rw-r--r--src/react-native-app/components/ProductCard/index.ts3
-rw-r--r--src/react-native-app/components/ProductList/ProductList.tsx50
-rw-r--r--src/react-native-app/components/ProductList/index.ts3
-rw-r--r--src/react-native-app/components/Setting.tsx76
-rw-r--r--src/react-native-app/components/ThemedScrollView.tsx23
-rw-r--r--src/react-native-app/components/ThemedText.tsx62
-rw-r--r--src/react-native-app/components/ThemedView.tsx24
-rw-r--r--src/react-native-app/components/navigation/TabBarIcon.tsx14
14 files changed, 628 insertions, 0 deletions
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 (
+ <ThemedScrollView style={styles.container}>
+ <Controller
+ control={control}
+ rules={{ required: true }}
+ render={({ field: { onChange, onBlur, value } }) => (
+ <Field
+ label="E-mail Address"
+ placeholder="E-mail Address"
+ onBlur={onBlur}
+ onChangeText={onChange}
+ value={value}
+ />
+ )}
+ name="email"
+ />
+ <Controller
+ control={control}
+ rules={{ required: true }}
+ render={({ field: { onChange, onBlur, value } }) => (
+ <Field
+ label="Street Address"
+ placeholder="Street Address"
+ onBlur={onBlur}
+ onChangeText={onChange}
+ value={value}
+ />
+ )}
+ name="streetAddress"
+ />
+ <Controller
+ control={control}
+ rules={{ required: true }}
+ render={({ field: { onChange, onBlur, value } }) => (
+ <Field
+ label="Zip Code"
+ placeholder="Zip Code"
+ onBlur={onBlur}
+ onChangeText={onChange}
+ value={value}
+ />
+ )}
+ name="zipCode"
+ />
+ <Controller
+ control={control}
+ rules={{ required: true }}
+ render={({ field: { onChange, onBlur, value } }) => (
+ <Field
+ label="Country"
+ placeholder="Country"
+ onBlur={onBlur}
+ onChangeText={onChange}
+ value={value}
+ />
+ )}
+ name="country"
+ />
+ <Controller
+ control={control}
+ rules={{ required: true }}
+ render={({ field: { onChange, onBlur, value } }) => (
+ <Field
+ label="Credit Card Number"
+ placeholder="Credit Card Number"
+ onBlur={onBlur}
+ onChangeText={onChange}
+ value={value}
+ keyboardType="numeric"
+ />
+ )}
+ name="creditCardNumber"
+ />
+ <Controller
+ control={control}
+ rules={{ required: true }}
+ render={({ field: { onChange, onBlur, value } }) => (
+ <Field
+ label="Expiration Month"
+ placeholder="Month"
+ onBlur={onBlur}
+ onChangeText={onChange}
+ value={value.toString()}
+ keyboardType="numeric"
+ />
+ )}
+ name="creditCardExpirationMonth"
+ />
+ <Controller
+ control={control}
+ rules={{ required: true }}
+ render={({ field: { onChange, onBlur, value } }) => (
+ <Field
+ label="Expiration Year"
+ placeholder="Year"
+ onBlur={onBlur}
+ onChangeText={onChange}
+ value={value.toString()}
+ keyboardType="numeric"
+ />
+ )}
+ name="creditCardExpirationYear"
+ />
+ <Controller
+ control={control}
+ rules={{ required: true }}
+ render={({ field: { onChange, onBlur, value } }) => (
+ <Field
+ label="CVV"
+ placeholder="CVV"
+ onBlur={onBlur}
+ onChangeText={onChange}
+ value={value.toString()}
+ keyboardType="numeric"
+ />
+ )}
+ name="creditCardCvv"
+ />
+ <ThemedView style={styles.submitContainer}>
+ <Pressable style={styles.submit} onPress={handleSubmit(onSubmit)}>
+ <ThemedText style={styles.submitText}>Place Order</ThemedText>
+ </Pressable>
+ </ThemedView>
+ </ThemedScrollView>
+ );
+};
+
+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 (
+ <ThemedView style={styles.container}>
+ <ThemedText style={styles.header}>
+ Your shopping cart is empty!
+ </ThemedText>
+ <ThemedText style={styles.subHeader}>
+ Items you add to your shopping cart will appear here.
+ </ThemedText>
+ </ThemedView>
+ );
+};
+
+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 (
+ <ThemedView style={styles.container}>
+ <ThemedText>{label}:</ThemedText>
+ <TextInput style={{ color }} {...otherProps} />
+ </ThemedView>
+ );
+}
+
+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<string>("");
+
+ 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 (
+ <ThemedView style={styles.container}>
+ <ThemedView>
+ {imageSrc && <Image style={styles.image} source={{ uri: imageSrc }} />}
+ </ThemedView>
+ <ThemedView style={styles.productInfo}>
+ <ThemedText style={styles.name}>{name}</ThemedText>
+ <ThemedText style={styles.price}>$ {price}</ThemedText>
+ <Pressable style={styles.add} onPress={onClickAdd}>
+ <ThemedText style={styles.addText}>Add to Cart</ThemedText>
+ </Pressable>
+ </ThemedView>
+ </ThemedView>
+ );
+};
+
+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 (
+ <ThemedScrollView>
+ {productList.map((product) => (
+ <ProductCard
+ key={product.id}
+ product={product}
+ onClickAdd={() => onAddItem(product.id)}
+ />
+ ))}
+ </ThemedScrollView>
+ );
+};
+
+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<string>;
+ set: (value: string) => Promise<void>;
+};
+
+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 (
+ <ThemedView style={styles.container}>
+ <ThemedText>{name}:</ThemedText>
+ {loading ? (
+ <ThemedText>Fetching current value...</ThemedText>
+ ) : (
+ <>
+ <TextInput
+ style={{ color }}
+ onChangeText={setText}
+ value={text}
+ {...otherProps}
+ />
+ <Pressable style={styles.apply} onPress={onApply}>
+ <ThemedText style={styles.applyText}>Apply</ThemedText>
+ </Pressable>
+ </>
+ )}
+ </ThemedView>
+ );
+}
+
+
+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 <ScrollView style={[{ backgroundColor }, style]} {...otherProps} />;
+}
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 (
+ <Text
+ style={[
+ { color },
+ type === "default" ? styles.default : undefined,
+ type === "title" ? styles.title : undefined,
+ type === "defaultSemiBold" ? styles.defaultSemiBold : undefined,
+ type === "subtitle" ? styles.subtitle : undefined,
+ type === "link" ? styles.link : undefined,
+ style,
+ ]}
+ {...rest}
+ />
+ );
+}
+
+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 <View style={[{ backgroundColor }, style]} {...otherProps} />;
+}
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<ComponentProps<typeof Ionicons>["name"]>) {
+ return <Ionicons size={28} style={[{ marginBottom: -3 }, style]} {...rest} />;
+}