-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(expo): checkout screen and functionallity
- Loading branch information
Showing
11 changed files
with
280 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,133 @@ | ||
import React from "react"; | ||
import { Text, TouchableOpacity, View } from "react-native-ui-lib"; | ||
import { Link } from "expo-router"; | ||
import { | ||
Button, | ||
Spacings, | ||
Text, | ||
TouchableOpacity, | ||
View, | ||
} from "react-native-ui-lib"; | ||
import { Link, useLocalSearchParams } from "expo-router"; | ||
import Input from "@/components/forms/Input"; | ||
import RadioButton from "@/components/forms/RadioButton"; | ||
import { api } from "@/utils/api"; | ||
import colors from "@/utils/colors"; | ||
import rupiahFormatter from "@/utils/rupiahFormatter"; | ||
import { Ionicons } from "@expo/vector-icons"; | ||
import { zodResolver } from "@hookform/resolvers/zod"; | ||
import { FormProvider, useForm } from "react-hook-form"; | ||
import { z } from "zod"; | ||
|
||
const schema = z.object({ | ||
note: z.string().optional(), | ||
courier: z.string(), | ||
}); | ||
|
||
const ONGKIR = 40000; | ||
|
||
export default function CheckoutScreen() { | ||
const { data } = api.user.getDefaultAddress.useQuery(); | ||
const { productId } = useLocalSearchParams(); | ||
|
||
const { data: product } = api.product.showProduct.useQuery({ | ||
id: productId as string, | ||
}); | ||
const { data: address } = api.user.getDefaultAddress.useQuery(); | ||
const { mutate, isPending } = api.order.checkout.useMutation(); | ||
|
||
const methods = useForm<z.infer<typeof schema>>({ | ||
resolver: zodResolver(schema), | ||
}); | ||
const { handleSubmit } = methods; | ||
const onSubmit = handleSubmit((data) => { | ||
if (!address || !product) return; | ||
mutate( | ||
{ | ||
note: data.note, | ||
addressId: address.id, | ||
productId: productId as string, | ||
totalPrice: product.price + 10000, | ||
courier: data.courier, | ||
}, | ||
{ | ||
onSuccess: () => { | ||
console.log("success"); | ||
// void utils.user.getAddresses.invalidate(); | ||
}, | ||
}, | ||
); | ||
}); | ||
|
||
return ( | ||
<View bg-white br50 flex padding-s4 className="rounded-b-none"> | ||
<Link href="/address/" asChild> | ||
<TouchableOpacity row spread paddingV-s2 className="border-b"> | ||
<TouchableOpacity | ||
row | ||
spread | ||
paddingV-s2 | ||
paddingH-s4 | ||
br40 | ||
className="border-primary mb-4 border" | ||
> | ||
<Text text70 primary> | ||
Alamat | ||
</Text> | ||
<View row> | ||
<Text className={data?.title ? "" : "text-gray-400"} text70> | ||
{data?.title ?? "Select address"} | ||
<Text className={address?.title ? "" : "text-red-400"} text70> | ||
{address?.title ?? "Select address"} | ||
</Text> | ||
<Ionicons name="chevron-forward" size={24} color={colors.primary} /> | ||
</View> | ||
</TouchableOpacity> | ||
</Link> | ||
<FormProvider {...methods}> | ||
<Input | ||
id="note" | ||
label="Catatan" | ||
multiline | ||
placeholder="Masukan catatan" | ||
/> | ||
<View | ||
paddingV-s2 | ||
paddingH-s4 | ||
br40 | ||
className="border-primary mb-4 border" | ||
> | ||
<Text text80 primary marginB-s1> | ||
Kurir | ||
</Text> | ||
<RadioButton | ||
id="courier" | ||
options={["JNE", "TIKI", "SICEPAT"]} | ||
containerStyle={{ | ||
paddingVertical: Spacings.s1, | ||
}} | ||
size={Spacings.s5} | ||
/> | ||
</View> | ||
</FormProvider> | ||
<View paddingV-s2 paddingH-s4 br40 className="border-primary mb-4 border"> | ||
<Text text80 primary marginB-s1> | ||
Rincian | ||
</Text> | ||
<View row spread> | ||
<Text text80>Subtotal</Text> | ||
<Text text80>{rupiahFormatter(product?.price)}</Text> | ||
</View> | ||
<View row spread> | ||
<Text text80>Ongkos Kirim</Text> | ||
<Text text80>{rupiahFormatter(ONGKIR)}</Text> | ||
</View> | ||
<View row spread> | ||
<Text text80>Total</Text> | ||
<Text text80>{rupiahFormatter(product?.price ?? 0 + ONGKIR)}</Text> | ||
</View> | ||
</View> | ||
<Button | ||
label="Beli" | ||
onPress={onSubmit} | ||
bg-primary | ||
br40 | ||
disabled={isPending} | ||
/> | ||
</View> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import type { RadioButtonProps as UIRadioButtonProps } from "react-native-ui-lib"; | ||
import { Text, RadioButton as UIRadioButton } from "react-native-ui-lib"; | ||
import colors from "@/utils/colors"; | ||
import { Controller, get, useFormContext } from "react-hook-form"; | ||
|
||
type RadioButtonProps = { | ||
/** | ||
* id to be initialized with React Hook Form, | ||
* must be the same with the pre-defined types. | ||
*/ | ||
id: string; | ||
|
||
/** | ||
* value to be passed to the onChange function | ||
* when the button is pressed. | ||
*/ | ||
options: string[]; | ||
} & UIRadioButtonProps; | ||
|
||
export default function RadioButton({ | ||
id, | ||
options, | ||
...props | ||
}: RadioButtonProps) { | ||
const { | ||
control, | ||
setValue, | ||
formState: { errors }, | ||
} = useFormContext(); | ||
const error = get(errors, id) as Error; | ||
|
||
return ( | ||
<Controller | ||
control={control} | ||
name={id} | ||
render={({ field }) => ( | ||
<> | ||
{error && ( | ||
<Text red40 text80 marginB-s2> | ||
{error.message} | ||
</Text> | ||
)} | ||
{options.map((option, index) => ( | ||
<UIRadioButton | ||
label={option} | ||
key={index} | ||
color={colors.primary} | ||
selected={field.value === option} | ||
onPress={() => setValue(id, option)} | ||
{...props} | ||
/> | ||
))} | ||
</> | ||
)} | ||
/> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,17 @@ | ||
import { authRouter } from "./router/auth"; | ||
import { categoryRouter } from "./router/category"; | ||
import { orderRouter } from "./router/orders"; | ||
import { productRouter } from "./router/product"; | ||
import { userRouter } from "./router/user"; | ||
import { createTRPCRouter } from "./trpc"; | ||
|
||
|
||
export const appRouter = createTRPCRouter({ | ||
auth: authRouter, | ||
product: productRouter, | ||
category: categoryRouter, | ||
user: userRouter, | ||
order: orderRouter, | ||
}); | ||
|
||
// export type definition of API | ||
export type AppRouter = typeof appRouter; | ||
export type AppRouter = typeof appRouter; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { insertOrderParams, orders } from "@vivat/db/schema/orders"; | ||
|
||
import { createTRPCRouter, protectedProcedure } from "../trpc"; | ||
|
||
export const orderRouter = createTRPCRouter({ | ||
checkout: protectedProcedure | ||
.input(insertOrderParams) | ||
.mutation(async ({ input, ctx }) => { | ||
return await ctx.db.insert(orders).values(input); | ||
}), | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { relations, sql } from "drizzle-orm"; | ||
import { int, mysqlEnum, timestamp, varchar } from "drizzle-orm/mysql-core"; | ||
import { createInsertSchema } from "drizzle-zod"; | ||
|
||
import { mySqlTable } from "./_table"; | ||
import { addresses } from "./addresses"; | ||
import { products } from "./products"; | ||
|
||
export const orders = mySqlTable("orders", { | ||
id: varchar("id", { length: 36 }) | ||
.notNull() | ||
.primaryKey() | ||
.default(sql`(UUID())`), | ||
status: mysqlEnum("status", [ | ||
"pending", | ||
"payment", | ||
"confirmed", | ||
"shipped", | ||
"delivered", | ||
"cancelled", | ||
"done", | ||
]) | ||
.notNull() | ||
.default("pending"), | ||
totalPrice: int("total_price").notNull(), | ||
courier: varchar("courier", { length: 255 }).notNull(), | ||
note: varchar("note", { length: 255 }), | ||
createdAt: timestamp("created_at") | ||
.default(sql`CURRENT_TIMESTAMP`) | ||
.notNull(), | ||
updatedAt: timestamp("updated_at").onUpdateNow(), | ||
productId: varchar("product_id", { length: 255 }).notNull(), | ||
addressId: varchar("address_id", { length: 255 }).notNull(), | ||
}); | ||
|
||
export const ordersRelations = relations(orders, ({ one }) => ({ | ||
product: one(products, { | ||
fields: [orders.productId], | ||
references: [products.id], | ||
}), | ||
address: one(addresses, { | ||
fields: [orders.addressId], | ||
references: [addresses.id], | ||
}), | ||
})); | ||
|
||
export const insertOrderSchema = createInsertSchema(orders); | ||
export const insertOrderParams = insertOrderSchema.omit({ | ||
id: true, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.