Skip to content

Commit

Permalink
feat: upload product
Browse files Browse the repository at this point in the history
  • Loading branch information
mrevanzak committed Nov 19, 2023
1 parent 92ca3df commit b25d06e
Show file tree
Hide file tree
Showing 15 changed files with 425 additions and 45 deletions.
12 changes: 11 additions & 1 deletion apps/expo/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
3 changes: 3 additions & 0 deletions apps/expo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
23 changes: 13 additions & 10 deletions apps/expo/src/app/(app)/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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,
}}
>
<Tabs.Screen
name="home"
// redirect={!isSignedIn}
options={{
header: (props) => <Header {...props} />,
tabBarButton: (props) => (
<Button avoidInnerPadding flex-1 {...props} />
),
Expand All @@ -48,21 +52,20 @@ export default function TabsLayout() {
name="listing"
// redirect={!isSignedIn}
options={{
headerTitle: "Tambah Produk",
tabBarItemStyle: {
top: "-10%",
borderRadius: 120,
bottom: 23,
backgroundColor: Colors.white,
alignSelf: "center",
aspectRatio: 1,
borderRadius: 50,
},
tabBarIconStyle: {
left: 2,
bottom: Platform.OS === "ios" ? 2 : 0,
},
tabBarIcon: ({ focused }) => (
<Button
avoidInnerPadding
avoidMinWidth
backgroundColor="transparent"
>
<TouchableOpacity>
<Ionicons
name={
focused
Expand All @@ -72,7 +75,7 @@ export default function TabsLayout() {
size={70}
color={colors.secondary}
/>
</Button>
</TouchableOpacity>
),
}}
/>
Expand Down
4 changes: 3 additions & 1 deletion apps/expo/src/app/(app)/(tabs)/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<View flex bg-primary>
Expand Down
218 changes: 206 additions & 12 deletions apps/expo/src/app/(app)/(tabs)/listing.tsx
Original file line number Diff line number Diff line change
@@ -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<string | undefined>();
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<z.infer<typeof schema>>({
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 (
<SafeAreaView>
{/* Changes page title visible on the header */}
{/* <Stack.Screen options={{ title: "Home Page" }} /> */}
</SafeAreaView>
<View bg-white padding-s4 flex>
<KeyboardAwareScrollView>
<FormProvider {...methods}>
<View
marginB-s4
br40
className="border-primary border bg-white"
padding-s4
flex
>
<Text text70 primary>
Upload Gambar
</Text>
<View flex center paddingV-s6 className="space-y-2">
{image ? (
<>
<AnimatedImage
source={{ uri: image }}
style={{ width: 200, height: 200 }}
loader={
<ActivityIndicator
color={colors.secondary}
size="small"
/>
}
/>
<Button
onPress={onSelectImage}
label="Ganti Gambar"
bg-primary
/>
</>
) : (
<Button
onPress={onSelectImage}
bg-primary
outline
outlineColor={colors.primary}
borderRadius={BorderRadiuses.br40}
padding-s2
iconSource={() => (
<MaterialCommunityIcons
name="file-image-plus"
size={40}
color={colors.primary}
/>
)}
/>
)}
</View>
</View>
<Input
id="name"
label="Nama Produk"
placeholder="Masukan nama produk"
/>
<Input
id="description"
label="Deskripsi Produk"
multiline
placeholder="Masukan deskripsi produk"
/>
<Input
id="price"
label="Harga Produk"
placeholder="Masukan harga produk"
inputMode="numeric"
/>
<Input
id="stock"
label="Stok Produk"
placeholder="Masukan stok produk"
inputMode="numeric"
/>
<Picker
id="categoryId"
label="Kategori Produk"
placeholder="Pilih kategori produk"
topBarProps={{ title: "Kategori" }}
items={
data?.map((category) => ({
label: category.name,
value: category.id,
})) ?? [{ label: "Loading...", value: "loading" }]
}
/>
</FormProvider>
<Button
label="Simpan"
onPress={onSubmit}
bg-primary
br40
disabled={uploading}
/>
</KeyboardAwareScrollView>
</View>
);
}
2 changes: 1 addition & 1 deletion apps/expo/src/app/(app)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default function AuthLayout() {
<Stack.Screen
name="(tabs)"
options={{
header: (props) => <Header {...props} />,
headerShown: false,
contentStyle: {
backgroundColor: "white",
},
Expand Down
Loading

0 comments on commit b25d06e

Please sign in to comment.