diff --git a/app/(application)/profile/[id]/create.tsx b/app/(application)/profile/[id]/create.tsx new file mode 100644 index 0000000..a519137 --- /dev/null +++ b/app/(application)/profile/[id]/create.tsx @@ -0,0 +1,97 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { + AvatarCustom, + ButtonCustom, + Spacing, + StackCustom, + SelectCustom, + TextInputCustom, + ViewWrapper, +} from "@/components"; +import InformationBox from "@/components/Box/InformationBox"; +import ButtonUpload from "@/components/Button/ButtonUpload"; +import LandscapeFrameUploaded from "@/components/Image/LandscapeFrameUploaded"; +import { View } from "react-native"; +import { useState } from "react"; +import { router } from "expo-router"; + +export default function CreateProfile() { + const [data, setData] = useState({ + name: "", + email: "", + address: "", + gender: "", + }); + + const handlerSave = () => { + console.log("data create profile >>", data); + // router.back(); + }; + + return ( + + Simpan + + } + > + + + + + + console.log("pressed")} /> + + + + + + + + + console.log("pressed")} /> + + + + setData({ ...data, name: text })} + /> + setData({ ...data, email: text })} + /> + setData({ ...data, address: text })} + /> + setData({ ...(data as any), gender: value })} + /> + + + + ); +} diff --git a/app/(application)/profile/[id]/index.tsx b/app/(application)/profile/[id]/index.tsx index 945f18f..1a0295f 100644 --- a/app/(application)/profile/[id]/index.tsx +++ b/app/(application)/profile/[id]/index.tsx @@ -45,6 +45,11 @@ export default function Profile() { // path: `/(application)/profile/dashboard-admin`, // }, { icon: "log-out", label: "Keluar", color: "red", path: "" }, + { + icon: "create-outline", + label: "Create profile", + path: `/(application)/profile/${id}/create`, + }, ]; // Animasi menggunakan translateY (lebih kompatibel) diff --git a/app/(application)/profile/_layout.tsx b/app/(application)/profile/_layout.tsx index eda4d6d..be16cde 100644 --- a/app/(application)/profile/_layout.tsx +++ b/app/(application)/profile/_layout.tsx @@ -24,6 +24,10 @@ export default function ProfileLayout() { name="[id]/update-background" options={{ title: "Update Latar Belakang" }} /> + ); diff --git a/components/Alert/AlertCustom.tsx b/components/Alert/AlertCustom.tsx index d6732e4..4d3b7dd 100644 --- a/components/Alert/AlertCustom.tsx +++ b/components/Alert/AlertCustom.tsx @@ -110,7 +110,7 @@ const styles = StyleSheet.create({ alertButton: { flex: 1, padding: 10, - borderRadius: 5, + borderRadius: 50, marginHorizontal: 5, alignItems: "center", }, diff --git a/components/Box/InformationBox.tsx b/components/Box/InformationBox.tsx new file mode 100644 index 0000000..4cd996a --- /dev/null +++ b/components/Box/InformationBox.tsx @@ -0,0 +1,33 @@ +import { MainColor } from "@/constants/color-palet"; +import { Ionicons } from "@expo/vector-icons"; +import Grid from "../Grid/GridCustom"; +import TextCustom from "../Text/TextCustom"; +import BaseBox from "./BaseBox"; + +export default function InformationBox({ text }: { text: string }) { + return ( + <> + + + + + + + + {text + ? text + : "Lorem ipsum dolor sit amet consectetur adipisicing elit."} + + + + + + ); +} diff --git a/components/Avatar/AvatarCustom.tsx b/components/Image/AvatarCustom.tsx similarity index 87% rename from components/Avatar/AvatarCustom.tsx rename to components/Image/AvatarCustom.tsx index d86c7e9..17d8b67 100644 --- a/components/Avatar/AvatarCustom.tsx +++ b/components/Image/AvatarCustom.tsx @@ -1,7 +1,8 @@ import { MainColor } from "@/constants/color-palet"; +import DUMMY_IMAGE from "@/constants/dummy-image-value"; import { Image, ImageSourcePropType, StyleSheet } from "react-native"; -type Size = "base" | "sm" | "md" | "lg"; +type Size = "base" | "sm" | "md" | "lg" | "xl"; interface AvatarCustomProps { source?: ImageSourcePropType; @@ -13,10 +14,11 @@ const sizeMap = { sm: 60, md: 80, lg: 100, + xl: 120, }; export default function AvatarCustom({ - source = require("@/assets/images/dummy/dummy-avatar.png"), + source = DUMMY_IMAGE.avatar, size = "base", }: AvatarCustomProps) { const dimension = sizeMap[size]; diff --git a/components/Image/LandscapeFrameUploaded.tsx b/components/Image/LandscapeFrameUploaded.tsx new file mode 100644 index 0000000..02439de --- /dev/null +++ b/components/Image/LandscapeFrameUploaded.tsx @@ -0,0 +1,20 @@ +import DUMMY_IMAGE from "@/constants/dummy-image-value"; +import { Image } from "react-native"; +import BaseBox from "../Box/BaseBox"; + +export default function LandscapeFrameUploaded() { + return ( + + + + ); +} diff --git a/components/Select/SelectCustom.tsx b/components/Select/SelectCustom.tsx index 8ca8a33..557a304 100644 --- a/components/Select/SelectCustom.tsx +++ b/components/Select/SelectCustom.tsx @@ -22,6 +22,7 @@ type SelectProps = { placeholder?: string; data: SelectItem[]; value?: string | number | null; + required?: boolean; // <-- new prop onChange: (value: string | number) => void; }; @@ -30,16 +31,27 @@ const SelectCustom: React.FC = ({ placeholder = "Pilih opsi", data, value, + required = false, // <-- default false onChange, }) => { const [modalVisible, setModalVisible] = useState(false); const selectedItem = data.find((item) => item.value === value); + const hasError = required && value === null; // <-- check if empty and required + return ( - {label && {label}} - setModalVisible(true)}> + {label && ( + + {label} + {required && *} + + )} + setModalVisible(true)} + > {selectedItem?.label || placeholder} @@ -70,6 +82,11 @@ const SelectCustom: React.FC = ({ + + {/* Optional Error Message */} + {hasError && ( + Harap pilih salah satu + )} ); }; @@ -86,6 +103,10 @@ const styles = StyleSheet.create({ color: MainColor.white, fontWeight: "500", }, + requiredIndicator: { + color: "red", + fontWeight: "bold", + }, input: { borderWidth: 1, borderColor: "#ccc", @@ -95,6 +116,9 @@ const styles = StyleSheet.create({ justifyContent: "center", backgroundColor: MainColor.white, }, + inputError: { + borderColor: "red", + }, text: { fontSize: TEXT_SIZE_MEDIUM, }, @@ -120,4 +144,9 @@ const styles = StyleSheet.create({ borderBottomWidth: 1, borderBottomColor: "#eee", }, + errorMessage: { + marginTop: 4, + fontSize: 12, + color: "red", + }, }); diff --git a/components/TextInput/TextInputCustom.tsx b/components/TextInput/TextInputCustom.tsx index 0ad9e4e..0be5c29 100644 --- a/components/TextInput/TextInputCustom.tsx +++ b/components/TextInput/TextInputCustom.tsx @@ -1,5 +1,3 @@ -// components/TextInputCustom.tsx - import Ionicons from "@expo/vector-icons/Ionicons"; import React, { useState } from "react"; import { @@ -32,15 +30,18 @@ export const TextInputCustom = ({ iconRight, label, required = false, - error = "", + error: externalError = "", secureTextEntry = false, fontColor = "#000", disabled = false, borderRadius = 8, style, + keyboardType, + onChangeText, ...rest }: Props) => { const [isPasswordVisible, setIsPasswordVisible] = useState(false); + const [internalError, setInternalError] = useState(""); // Helper untuk render ikon const renderIcon = (icon: IconType) => { @@ -52,6 +53,23 @@ export const TextInputCustom = ({ ); }; + // Validasi email jika keyboardType = email-address + const handleTextChange = (text: string) => { + if (keyboardType === "email-address") { + const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(text); + if (!isValid) { + setInternalError("Masukkan email yang valid"); + } else { + setInternalError(""); + } + } + + // Panggil onChangeText eksternal jika ada + if (onChangeText) { + onChangeText(text); + } + }; + return ( {label && ( @@ -65,7 +83,7 @@ export const TextInputCustom = ({ textInputStyles.inputContainer, disabled && textInputStyles.disabled, { borderRadius }, - error ? textInputStyles.errorBorder : null, + externalError || internalError ? textInputStyles.errorBorder : null, style, ]} > @@ -76,6 +94,8 @@ export const TextInputCustom = ({ style={[textInputStyles.input, { color: fontColor }]} editable={!disabled} secureTextEntry={secureTextEntry && !isPasswordVisible} + keyboardType={keyboardType} + onChangeText={handleTextChange} {...rest} /> {secureTextEntry && ( @@ -94,7 +114,12 @@ export const TextInputCustom = ({ {renderIcon(iconRight)} )} - {error ? {error} : null} + {/* Prioritaskan error eksternal */} + {externalError || internalError ? ( + + {externalError || internalError} + + ) : null} ); }; diff --git a/components/_ShareComponent/ViewWrapper.tsx b/components/_ShareComponent/ViewWrapper.tsx index a85dd27..c054f72 100644 --- a/components/_ShareComponent/ViewWrapper.tsx +++ b/components/_ShareComponent/ViewWrapper.tsx @@ -47,42 +47,11 @@ const ViewWrapper = ({ {tabBarComponent ? tabBarComponent : null} {bottomBarComponent ? ( - - {bottomBarComponent} - + {bottomBarComponent} ) : null} - - // - // - // - // {withBackground ? ( - // - // {children} - // - // ) : ( - // {children} - // )} - // - // {tabBarComponent} - // - // ); }; diff --git a/components/index.ts b/components/index.ts index 8fb4ec2..c4d1325 100644 --- a/components/index.ts +++ b/components/index.ts @@ -18,7 +18,7 @@ import Grid from "./Grid/GridCustom"; // Box import BaseBox from "./Box/BaseBox"; // Avatar -import AvatarCustom from "./Avatar/AvatarCustom" +import AvatarCustom from "./Image/AvatarCustom" // Stack import StackCustom from "./Stack/StackCustom"; // Select diff --git a/screens/Authentication/LoginView.tsx b/screens/Authentication/LoginView.tsx index 74601c5..9fcaf5f 100644 --- a/screens/Authentication/LoginView.tsx +++ b/screens/Authentication/LoginView.tsx @@ -28,10 +28,10 @@ export default function LoginView() { const randomAlfabet = Math.random().toString(36).substring(2, 8); const randomNumber = Math.floor(Math.random() * 1000000); const id = randomAlfabet + randomNumber + fixNumber; - console.log("user id :", id); + console.log("login user id :", id); - // router.navigate("/verification"); - router.navigate(`/(application)/profile/${id}`); + router.navigate("/verification"); + // router.navigate(`/(application)/profile/${id}`); // router.navigate("/(application)/home"); // router.navigate(`/(application)/profile/${id}/edit`); diff --git a/screens/Profile/AvatarAndBackground.tsx b/screens/Profile/AvatarAndBackground.tsx index b48fe04..0673df8 100644 --- a/screens/Profile/AvatarAndBackground.tsx +++ b/screens/Profile/AvatarAndBackground.tsx @@ -1,12 +1,13 @@ import { AvatarCustom } from "@/components"; import { AccentColor } from "@/constants/color-palet"; +import DUMMY_IMAGE from "@/constants/dummy-image-value"; import { View, ImageBackground, StyleSheet } from "react-native"; const AvatarAndBackground = () => { return ( @@ -15,7 +16,7 @@ const AvatarAndBackground = () => { {/* Avatar yang sedikit keluar */}