diff --git a/apps/expo/app.config.ts b/apps/expo/app.config.ts index bd24536..5650fdc 100644 --- a/apps/expo/app.config.ts +++ b/apps/expo/app.config.ts @@ -40,7 +40,17 @@ const defineConfig = (): ExpoConfig => ({ tsconfigPaths: true, typedRoutes: true, }, - plugins: ["expo-router", "./expo-plugins/with-modify-gradle.js"], + plugins: [ + "expo-router", + "./expo-plugins/with-modify-gradle.js", + [ + "expo-image-picker", + { + photosPermission: + "Allow $(PRODUCT_NAME) to access your photos in order to upload photo of your product", + }, + ], + ], }); export default defineConfig; diff --git a/apps/expo/package.json b/apps/expo/package.json index ef2a558..56c1ee9 100644 --- a/apps/expo/package.json +++ b/apps/expo/package.json @@ -21,15 +21,18 @@ "@gorhom/bottom-sheet": "^4", "@hookform/resolvers": "^3.3.0", "@shopify/flash-list": "1.4.3", + "@supabase/storage-js": "^2.5.4", "@tanstack/react-query": "^5.0.5", "@trpc/client": "next", "@trpc/react-query": "next", "@trpc/server": "next", + "base64-arraybuffer": "^1.0.2", "expo": "^49.0.13", "expo-auth-session": "~5.0.2", "expo-clipboard": "~4.3.1", "expo-constants": "~14.4.2", "expo-crypto": "~12.4.1", + "expo-image-picker": "~14.3.2", "expo-linear-gradient": "~12.3.0", "expo-linking": "~5.0.2", "expo-router": "2.0.8", diff --git a/apps/expo/src/app/(app)/(tabs)/_layout.tsx b/apps/expo/src/app/(app)/(tabs)/_layout.tsx index a78dcd2..bbc0811 100644 --- a/apps/expo/src/app/(app)/(tabs)/_layout.tsx +++ b/apps/expo/src/app/(app)/(tabs)/_layout.tsx @@ -1,7 +1,9 @@ import React from "react"; import { Platform } from "react-native"; +import { TouchableOpacity } from "react-native-gesture-handler"; import { Button, Colors, Spacings } from "react-native-ui-lib"; import { Redirect, Tabs } from "expo-router"; +import Header from "@/components/Header"; import colors from "@/utils/colors"; import { useUser } from "@clerk/clerk-expo"; import Ionicons from "@expo/vector-icons/Ionicons"; @@ -18,20 +20,22 @@ export default function TabsLayout() { initialRouteName="home" screenOptions={{ tabBarShowLabel: false, + tabBarItemStyle: { + paddingTop: Platform.OS === "ios" ? Spacings.s3 : 0, + }, tabBarStyle: { backgroundColor: colors.primary, borderTopLeftRadius: 15, borderTopRightRadius: 15, - paddingVertical: Platform.OS === "ios" ? Spacings.s3 : 0, height: 70, }, - headerShown: false, }} >
, tabBarButton: (props) => ( + ), }} /> diff --git a/apps/expo/src/app/(app)/(tabs)/home.tsx b/apps/expo/src/app/(app)/(tabs)/home.tsx index 945dc0c..a2066ba 100644 --- a/apps/expo/src/app/(app)/(tabs)/home.tsx +++ b/apps/expo/src/app/(app)/(tabs)/home.tsx @@ -14,7 +14,9 @@ import colors from "@/utils/colors"; import { FlashList } from "@shopify/flash-list"; export default function HomeScreen() { - const { data, refetch, isFetching } = api.category.getCategories.useQuery(); + const { data, refetch, isFetching } = api.category.getCategories.useQuery({ + partial: false, + }); return ( diff --git a/apps/expo/src/app/(app)/(tabs)/listing.tsx b/apps/expo/src/app/(app)/(tabs)/listing.tsx index e27e308..f475d7a 100644 --- a/apps/expo/src/app/(app)/(tabs)/listing.tsx +++ b/apps/expo/src/app/(app)/(tabs)/listing.tsx @@ -1,19 +1,213 @@ -import React from "react"; -import { SafeAreaView } from "react-native-safe-area-context"; +import React, { useEffect, useState } from "react"; +import { ActivityIndicator } from "react-native"; +import { + AnimatedImage, + BorderRadiuses, + Button, + KeyboardAwareScrollView, + Text, + View, +} from "react-native-ui-lib"; +import Constants from "expo-constants"; +import * as FileSystem from "expo-file-system"; +import * as ImagePicker from "expo-image-picker"; +import { useRouter } from "expo-router"; +import Input from "@/components/forms/Input"; +import Picker from "@/components/forms/Picker"; +import { api } from "@/utils/api"; +import colors from "@/utils/colors"; +import { storageClient } from "@/utils/supabase"; +import { useUser } from "@clerk/clerk-expo"; +import { MaterialCommunityIcons } from "@expo/vector-icons"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { decode } from "base64-arraybuffer"; +import { FormProvider, useForm } from "react-hook-form"; +import { z } from "zod"; -export default function ListingScreen() { - // const utils = api.useContext(); +const schema = z.object({ + name: z.string().min(3), + description: z.string().min(3), + price: z.coerce.number(), + stock: z.coerce.number(), + categoryId: z.string(), +}); - // const postQuery = api.post.all.useQuery(); +export default function UploadProductScreen() { + const router = useRouter(); + const [image, setImage] = useState(); + const [uploading, setUploading] = useState(false); + const { user } = useUser(); - // const deletePostMutation = api.post.delete.useMutation({ - // onSettled: () => utils.post.all.invalidate(), - // }); + const { data } = api.category.getCategories.useQuery({ partial: true }); + const { mutate } = api.product.addProduct.useMutation({}); + + const methods = useForm>({ + resolver: zodResolver(schema), + }); + const { handleSubmit } = methods; + const onSubmit = handleSubmit(async (data) => { + setUploading(true); + const filePath = `${user?.id}/${data.name}.png`; + + await onUpload(filePath); + const imgUrl = storageClient.from("products").getPublicUrl(filePath); + + mutate( + { + ...data, + image: imgUrl.data.publicUrl, + }, + { + onSuccess: () => { + setUploading(false); + router.push("/home"); + }, + }, + ); + }); + + const onUpload = async (filePath: string) => { + if (!image) return; + const base64 = await FileSystem.readAsStringAsync(image, { + encoding: "base64", + }); + + const { error } = await storageClient + .from("products") + .upload(filePath, decode(base64), { + contentType: "image/png", + }); + if (error) { + console.log(error); + return; + } + }; + + const onSelectImage = async () => { + const options: ImagePicker.ImagePickerOptions = { + mediaTypes: ImagePicker.MediaTypeOptions.Images, + allowsEditing: true, + quality: 1, + }; + + const result = await ImagePicker.launchImageLibraryAsync(options); + if (!result.canceled) { + setImage(result.assets[0]?.uri); + } + }; + + useEffect(() => { + void async function checkPermission() { + if (Constants.platform?.ios) { + const cameraRollStatus = + await ImagePicker.requestMediaLibraryPermissionsAsync(); + const cameraStatus = await ImagePicker.requestCameraPermissionsAsync(); + if ( + cameraRollStatus.status !== ImagePicker.PermissionStatus.GRANTED || + cameraStatus.status !== ImagePicker.PermissionStatus.GRANTED + ) { + alert("Sorry, we need these permissions to make this work!"); + } + } + }; + }, []); return ( - - {/* Changes page title visible on the header */} - {/* */} - + + + + + + Upload Gambar + + + {image ? ( + <> + + } + /> +