Complete service api integration

feature/app-setup
vinay kumar 2025-07-11 12:22:33 +05:30
parent 5cde8dbbc6
commit 5c2e3c8979
5 changed files with 120 additions and 76 deletions

View File

@ -97,6 +97,9 @@ export default function HomeScreen() {
useLayoutEffect(() => { useLayoutEffect(() => {
navigation.setOptions({ navigation.setOptions({
headerStyle: {
backgroundColor: "#F3F5F8",
},
headerTitle: () => ( headerTitle: () => (
<View style={styles.headerTitleContainer}> <View style={styles.headerTitleContainer}>
<Text style={styles.title}>{model}</Text> <Text style={styles.title}>{model}</Text>
@ -106,7 +109,7 @@ export default function HomeScreen() {
headerRight: () => ( headerRight: () => (
<View style={styles.rightContainer}> <View style={styles.rightContainer}>
<Pressable <Pressable
style={styles.iconContainer} style={styles.supportButton}
onPress={() => { onPress={() => {
console.log("Support Pressed"); console.log("Support Pressed");
setIsSupportModalVisible(true); setIsSupportModalVisible(true);
@ -357,6 +360,9 @@ const styles = StyleSheet.create({
paddingBottom: 110, paddingBottom: 110,
}, },
iconContainer: { iconContainer: {
backgroundColor: "#FCFCFC",
},
supportButton: {
backgroundColor: "#F3F5F8", backgroundColor: "#F3F5F8",
}, },
headerTitleContainer: { headerTitleContainer: {

View File

@ -17,6 +17,11 @@ import * as Yup from "yup";
import ChevronRight from "../../assets/icons/chevron_rightside.svg"; import ChevronRight from "../../assets/icons/chevron_rightside.svg";
import AddPhoto from "../../assets/icons/add_photo_alternate.svg"; import AddPhoto from "../../assets/icons/add_photo_alternate.svg";
import IssueSelectorModal from "@/components/service/IssueSelectorModal"; import IssueSelectorModal from "@/components/service/IssueSelectorModal";
import { uploadImage } from "@/utils/User";
import api from "@/services/axiosClient";
import { useSnackbar } from "@/contexts/Snackbar";
import { BASE_URL } from "@/constants/config";
import { Overlay } from "@/components/common/Overlay";
interface FormValues { interface FormValues {
serviceType: string | null; serviceType: string | null;
@ -37,6 +42,7 @@ const validationSchema = Yup.object().shape({
export default function ServiceFormScreen(): JSX.Element { export default function ServiceFormScreen(): JSX.Element {
const [isFocus, setIsFocus] = useState<boolean>(false); const [isFocus, setIsFocus] = useState<boolean>(false);
const [isIssueSelectorVisible, setIssueSelectorVisible] = useState(false); const [isIssueSelectorVisible, setIssueSelectorVisible] = useState(false);
const { showSnackbar } = useSnackbar();
function toggleIssueSelector() { function toggleIssueSelector() {
setIssueSelectorVisible(!isIssueSelectorVisible); setIssueSelectorVisible(!isIssueSelectorVisible);
@ -113,11 +119,44 @@ export default function ServiceFormScreen(): JSX.Element {
<Formik <Formik
initialValues={initialValues} initialValues={initialValues}
validationSchema={validationSchema} validationSchema={validationSchema}
onSubmit={( onSubmit={async (
values: FormValues, values: FormValues,
actions: FormikHelpers<FormValues> actions: FormikHelpers<FormValues>
) => { ) => {
console.log(values); try {
const uploadedPhotoUrls: string[] = [];
for (const uri of values.photos) {
const uploadedUrl = await uploadImage(uri);
uploadedPhotoUrls.push(uploadedUrl);
}
const payload = {
service_type: values.serviceType,
issue_types: values.issues,
scheduled_time: values.date?.toISOString(),
photos: uploadedPhotoUrls,
comments: values.comments,
};
const response = await api.post(
`${BASE_URL}/api/v1/schedule-maintenance`,
payload
);
if (!response.data.success) {
throw new Error(response.data?.message || "Submission failed");
}
console.log("Submission successful:", response.data);
actions.resetForm();
showSnackbar("Service request submitted successfully", "success");
} catch (error) {
console.error("Error during submission:", error);
showSnackbar("Failed to submit service request", "error");
} finally {
actions.setSubmitting(false);
}
}} }}
> >
{({ {({
@ -128,6 +167,7 @@ export default function ServiceFormScreen(): JSX.Element {
setFieldValue, setFieldValue,
errors, errors,
touched, touched,
isSubmitting,
}) => ( }) => (
<View style={styles.formContainer}> <View style={styles.formContainer}>
<View style={styles.inputContainer}> <View style={styles.inputContainer}>
@ -265,6 +305,7 @@ export default function ServiceFormScreen(): JSX.Element {
setFieldValue("issues", selectedIssues); setFieldValue("issues", selectedIssues);
}} }}
/> />
{isSubmitting && <Overlay isUploading={isSubmitting} />}
</View> </View>
)} )}
</Formik> </Formik>

View File

@ -18,17 +18,10 @@ import { logout } from "@/store/authSlice";
import * as ImagePicker from "expo-image-picker"; import * as ImagePicker from "expo-image-picker";
import { useSnackbar } from "@/contexts/Snackbar"; import { useSnackbar } from "@/contexts/Snackbar";
import { AWS, BASE_URL, USER_PROFILE } from "@/constants/config"; import { AWS, BASE_URL, USER_PROFILE } from "@/constants/config";
import { bytesToMB, handleUpload, updateUserProfile } from "@/utils/User"; import { bytesToMB, updateUserProfile, uploadImage } from "@/utils/User";
import api from "@/services/axiosClient";
import { PresignedUrlDataItem } from "@/utils/User";
import { setUserData } from "@/store/userSlice"; import { setUserData } from "@/store/userSlice";
import { Overlay } from "@/components/common/Overlay"; import { Overlay } from "@/components/common/Overlay";
interface PresignedUrlResponse {
message: string;
data?: PresignedUrlDataItem[];
}
export default function ProfileScreen() { export default function ProfileScreen() {
const [isLangaugeModalVisible, setLanguageModalVisible] = const [isLangaugeModalVisible, setLanguageModalVisible] =
React.useState(false); React.useState(false);
@ -51,51 +44,6 @@ export default function ProfileScreen() {
router.replace("/auth/login"); router.replace("/auth/login");
}; };
const uploadImage = async (fileName: string, uri: string) => {
try {
setIsUploading(true);
const getPresignedUrl = `${BASE_URL}/api/v1/generate-presigned-urls`;
const response = await api.post<PresignedUrlResponse>(getPresignedUrl, {
files: [fileName],
});
const uploadData = response?.data?.data?.[0];
if (!uploadData) throw new Error("Presigned URL not received");
const { url, fields, originalFileName } = uploadData;
const presignedUrl: PresignedUrlDataItem = {
url,
fields,
originalFileName,
};
await handleUpload(uri, presignedUrl);
const region = AWS.REGION;
const bucketName = AWS.BUCKET_NAME;
const objectKey = presignedUrl.fields.key;
const uploadedImageUrl = `https://s3.${region}.amazonaws.com/${bucketName}/${encodeURIComponent(
objectKey
)}`;
console.log("Uploaded image URL:", uploadedImageUrl);
await updateUserProfile({
profile_url: uploadedImageUrl,
mobile: mobileNumber,
});
dispatch(setUserData({ profile_url: uri }));
showSnackbar("Image uploaded successfully", "success");
} catch (error) {
console.error("Error uploading image:", error);
showSnackbar("Error uploading image", "error");
} finally {
setIsUploading(false);
}
};
const handlePickImage = async () => { const handlePickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({ let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images, mediaTypes: ImagePicker.MediaTypeOptions.Images,
@ -105,25 +53,40 @@ export default function ProfileScreen() {
}); });
if (!result.canceled) { if (!result.canceled) {
const { uri, fileSize } = result.assets[0]; try {
console.log(uri, "File size:", fileSize); setIsUploading(true);
if (!fileSize) { const { uri, fileSize } = result.assets[0];
showSnackbar("Image size is not available", "error"); console.log(uri, "File size:", fileSize);
return; if (!fileSize) {
} showSnackbar("Image size is not available", "error");
const size = bytesToMB(fileSize); return;
if (size > USER_PROFILE.MAX_IMAGE_SIZE_IN_MB) { }
showSnackbar( const size = bytesToMB(fileSize);
`Image size exceeds ${USER_PROFILE.MAX_IMAGE_SIZE_IN_MB}MB limit`, if (size > USER_PROFILE.MAX_IMAGE_SIZE_IN_MB) {
"error" showSnackbar(
); `Image size exceeds ${USER_PROFILE.MAX_IMAGE_SIZE_IN_MB}MB limit`,
throw new Error( "error"
`Image size exceeds ${USER_PROFILE.MAX_IMAGE_SIZE_IN_MB}MB limit` );
); throw new Error(
} `Image size exceeds ${USER_PROFILE.MAX_IMAGE_SIZE_IN_MB}MB limit`
);
}
const uploadedImageUrl = await uploadImage(uri);
const fileName = uri.split("/").pop() || "image.jpg"; console.log("Uploaded image URL:", uploadedImageUrl);
await uploadImage(fileName, uri); await updateUserProfile({
profile_url: uploadedImageUrl,
mobile: mobileNumber,
});
dispatch(setUserData({ profile_url: uri }));
showSnackbar("Image uploaded successfully", "success");
} catch (error) {
console.error("Error uploading image:", error);
showSnackbar("Image upload failed", "error");
} finally {
setIsUploading(false);
}
} }
}; };
return ( return (

View File

@ -56,7 +56,6 @@ export default function IssueSelectorModal({
/> />
</View> </View>
{/* Selection Counter and Clear Button */}
<View style={styles.counterBar}> <View style={styles.counterBar}>
<Text <Text
style={styles.counterText} style={styles.counterText}

View File

@ -1,4 +1,4 @@
import { BASE_URL } from "@/constants/config"; import { AWS, BASE_URL } from "@/constants/config";
import api from "@/services/axiosClient"; import api from "@/services/axiosClient";
import axios from "axios"; import axios from "axios";
@ -25,6 +25,11 @@ export function bytesToMB(bytes: number) {
return bytes / MB; return bytes / MB;
} }
interface PresignedUrlResponse {
message: string;
data?: PresignedUrlDataItem[];
}
export const handleUpload = async ( export const handleUpload = async (
uri: string, uri: string,
presignedUrl: PresignedUrlDataItem presignedUrl: PresignedUrlDataItem
@ -125,3 +130,33 @@ export const updateUserProfile = ({
return api.put(`${BASE_URL}/api/v1/update-user-information`, payload); return api.put(`${BASE_URL}/api/v1/update-user-information`, payload);
}; };
export const uploadImage = async (uri: string) => {
const fileName = uri.split("/").pop() || "image.jpg";
const getPresignedUrl = `${BASE_URL}/api/v1/generate-presigned-urls`;
const response = await api.post<PresignedUrlResponse>(getPresignedUrl, {
files: [fileName],
});
const uploadData = response?.data?.data?.[0];
if (!uploadData) throw new Error("Presigned URL not received");
const { url, fields, originalFileName } = uploadData;
const presignedUrl: PresignedUrlDataItem = {
url,
fields,
originalFileName,
};
await handleUpload(uri, presignedUrl);
const region = AWS.REGION;
const bucketName = AWS.BUCKET_NAME;
const objectKey = presignedUrl.fields.key;
const uploadedImageUrl = `https://s3.${region}.amazonaws.com/${bucketName}/${encodeURIComponent(
objectKey
)}`;
return uploadedImageUrl;
};