app-expo-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/index.js +3 -0
- package/bun.lock +82 -0
- package/package.json +26 -0
- package/src/copy-template.js +156 -0
- package/src/create-expo.js +10 -0
- package/src/index.js +39 -0
- package/src/install-deps.js +22 -0
- package/src/utils/logger.js +29 -0
- package/tailwind.config.js +69 -0
- package/template/src/app/_layout.tsx +61 -0
- package/template/src/app/auth/_layout.tsx +41 -0
- package/template/src/app/auth/change_pass.tsx +138 -0
- package/template/src/app/auth/change_pass_modal.tsx +74 -0
- package/template/src/app/auth/forgot.tsx +124 -0
- package/template/src/app/auth/index.tsx +274 -0
- package/template/src/app/auth/opt_verify.tsx +145 -0
- package/template/src/app/auth/register.tsx +334 -0
- package/template/src/app/auth/reset_pass.tsx +152 -0
- package/template/src/app/common/image.tsx +202 -0
- package/template/src/app/common/openurl.tsx +42 -0
- package/template/src/app/home/_layout.tsx +29 -0
- package/template/src/app/home/drawer/_layout.tsx +27 -0
- package/template/src/app/home/tabs/_layout.tsx +75 -0
- package/template/src/app/home/tabs/index.tsx +11 -0
- package/template/src/app/index.tsx +11 -0
- package/template/src/app/modals/confirmation_logout_modal.tsx +78 -0
- package/template/src/app/modals/payment_modal.tsx +105 -0
- package/template/src/app/modals/success_modal.tsx +72 -0
- package/template/src/app/modals/toaster.tsx +31 -0
- package/template/src/app/settings/_layout.tsx +19 -0
- package/template/src/app/settings/about_us.tsx +61 -0
- package/template/src/app/settings/privacy_policy.tsx +61 -0
- package/template/src/app/settings/terms_and_conditions.tsx +60 -0
- package/template/src/hooks/useCheckLocation.ts +36 -0
- package/template/src/hooks/useDocPicker.ts +83 -0
- package/template/src/hooks/useImgePicker.ts +70 -0
- package/template/src/hooks/useSuggestionLocation.ts +36 -0
- package/template/src/hooks/useUploadProgress.ts +127 -0
- package/template/src/redux/api-config/baseApi.ts +89 -0
- package/template/src/redux/api-slices/authSlices.ts +175 -0
- package/template/src/redux/interface/common.ts +19 -0
- package/template/src/redux/interface/interface.ts +196 -0
- package/template/src/redux/interface/tag-types.ts +13 -0
- package/template/src/redux/service/demo.ts +27 -0
- package/template/src/redux/store.ts +17 -0
- package/template/src/utils/utils.ts +27 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Stack } from "expo-router";
|
|
2
|
+
|
|
3
|
+
export const unstable_settings = {
|
|
4
|
+
initialRouteName: "index",
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export default function RootLayout() {
|
|
8
|
+
return (
|
|
9
|
+
<Stack
|
|
10
|
+
screenOptions={{
|
|
11
|
+
headerShown: false,
|
|
12
|
+
}}
|
|
13
|
+
>
|
|
14
|
+
<Stack.Screen name="about_us" />
|
|
15
|
+
<Stack.Screen name="terms_and_conditions" />
|
|
16
|
+
<Stack.Screen name="privacy_policy" />
|
|
17
|
+
</Stack>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Image, ScrollView, View } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { ImageAssets } from "@/assets/images/image";
|
|
4
|
+
import BackButton from "@/src/lib/backHeader/BackButton";
|
|
5
|
+
import TButton from "@/src/lib/buttons/TButton";
|
|
6
|
+
import tw from "@/src/lib/tailwind";
|
|
7
|
+
import { _WIDTH } from "@/src/utils/utils";
|
|
8
|
+
import { router } from "expo-router";
|
|
9
|
+
import RenderHtml from "react-native-render-html";
|
|
10
|
+
|
|
11
|
+
const AboutUs = () => {
|
|
12
|
+
const source = {
|
|
13
|
+
html: `
|
|
14
|
+
<div>
|
|
15
|
+
<p>App Name</p>
|
|
16
|
+
<p>App Version</p>
|
|
17
|
+
<p>App Description</p>
|
|
18
|
+
</div>
|
|
19
|
+
`,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<View style={tw`flex-1 bg-base`}>
|
|
24
|
+
<BackButton
|
|
25
|
+
onPress={() => router.dismiss()}
|
|
26
|
+
title="About us"
|
|
27
|
+
titleStyle={tw`text-white`}
|
|
28
|
+
/>
|
|
29
|
+
<ScrollView>
|
|
30
|
+
<View style={tw`px-5 mt-5 justify-center items-center`}>
|
|
31
|
+
<Image
|
|
32
|
+
source={ImageAssets.about_us}
|
|
33
|
+
style={tw` h-52 aspect-square`}
|
|
34
|
+
resizeMode="contain"
|
|
35
|
+
/>
|
|
36
|
+
<TButton
|
|
37
|
+
disabled
|
|
38
|
+
title="About us "
|
|
39
|
+
offGradient
|
|
40
|
+
containerStyle={tw`bg-secondary`}
|
|
41
|
+
/>
|
|
42
|
+
</View>
|
|
43
|
+
<View style={tw`gap-6 px-5 my-5`}>
|
|
44
|
+
<RenderHtml
|
|
45
|
+
contentWidth={_WIDTH}
|
|
46
|
+
baseStyle={{
|
|
47
|
+
color: tw.color("gray-300"),
|
|
48
|
+
fontFamily: "InterRegular",
|
|
49
|
+
fontSize: 16,
|
|
50
|
+
letterSpacing: 0.5,
|
|
51
|
+
lineHeight: 24,
|
|
52
|
+
}}
|
|
53
|
+
source={source}
|
|
54
|
+
/>
|
|
55
|
+
</View>
|
|
56
|
+
</ScrollView>
|
|
57
|
+
</View>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export default AboutUs;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Image, ScrollView, View } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { ImageAssets } from "@/assets/images/image";
|
|
4
|
+
import BackButton from "@/src/lib/backHeader/BackButton";
|
|
5
|
+
import TButton from "@/src/lib/buttons/TButton";
|
|
6
|
+
import tw from "@/src/lib/tailwind";
|
|
7
|
+
import { _WIDTH } from "@/src/utils/utils";
|
|
8
|
+
import { router } from "expo-router";
|
|
9
|
+
import RenderHtml from "react-native-render-html";
|
|
10
|
+
|
|
11
|
+
const Privacy_policy = () => {
|
|
12
|
+
const source = {
|
|
13
|
+
html: `
|
|
14
|
+
<div>
|
|
15
|
+
<p>App Name</p>
|
|
16
|
+
<p>App Version</p>
|
|
17
|
+
<p>App Description</p>
|
|
18
|
+
</div>
|
|
19
|
+
`,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<View style={tw`flex-1 bg-base`}>
|
|
24
|
+
<BackButton
|
|
25
|
+
onPress={() => router.dismiss()}
|
|
26
|
+
title="Privacy policy"
|
|
27
|
+
titleStyle={tw`text-white`}
|
|
28
|
+
/>
|
|
29
|
+
<ScrollView>
|
|
30
|
+
<View style={tw`px-5 mt-5 justify-center items-center`}>
|
|
31
|
+
<Image
|
|
32
|
+
source={ImageAssets.privacy_policy}
|
|
33
|
+
style={tw` h-52 aspect-square`}
|
|
34
|
+
resizeMode="contain"
|
|
35
|
+
/>
|
|
36
|
+
<TButton
|
|
37
|
+
disabled
|
|
38
|
+
title="Privacy policy "
|
|
39
|
+
offGradient
|
|
40
|
+
containerStyle={tw`bg-secondary`}
|
|
41
|
+
/>
|
|
42
|
+
</View>
|
|
43
|
+
<View style={tw`gap-6 px-5 my-5`}>
|
|
44
|
+
<RenderHtml
|
|
45
|
+
contentWidth={_WIDTH}
|
|
46
|
+
baseStyle={{
|
|
47
|
+
color: tw.color("gray-300"),
|
|
48
|
+
fontFamily: "InterRegular",
|
|
49
|
+
fontSize: 16,
|
|
50
|
+
letterSpacing: 0.5,
|
|
51
|
+
lineHeight: 24,
|
|
52
|
+
}}
|
|
53
|
+
source={source}
|
|
54
|
+
/>
|
|
55
|
+
</View>
|
|
56
|
+
</ScrollView>
|
|
57
|
+
</View>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export default Privacy_policy;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Image, ScrollView, View } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { ImageAssets } from "@/assets/images/image";
|
|
4
|
+
import BackButton from "@/src/lib/backHeader/BackButton";
|
|
5
|
+
import TButton from "@/src/lib/buttons/TButton";
|
|
6
|
+
import tw from "@/src/lib/tailwind";
|
|
7
|
+
import { _WIDTH } from "@/src/utils/utils";
|
|
8
|
+
import { router } from "expo-router";
|
|
9
|
+
import RenderHtml from "react-native-render-html";
|
|
10
|
+
|
|
11
|
+
const Terms_And_Conditions = () => {
|
|
12
|
+
const source = {
|
|
13
|
+
html: `
|
|
14
|
+
<div>
|
|
15
|
+
<p>App Name</p>
|
|
16
|
+
<p>App Version</p>
|
|
17
|
+
<p>App Description</p>
|
|
18
|
+
</div>
|
|
19
|
+
`,
|
|
20
|
+
};
|
|
21
|
+
return (
|
|
22
|
+
<View style={tw`flex-1 bg-base`}>
|
|
23
|
+
<BackButton
|
|
24
|
+
onPress={() => router.dismiss()}
|
|
25
|
+
title="Terms & Conditions"
|
|
26
|
+
titleStyle={tw`text-white`}
|
|
27
|
+
/>
|
|
28
|
+
<ScrollView>
|
|
29
|
+
<View style={tw`px-5 mt-5 justify-center items-center`}>
|
|
30
|
+
<Image
|
|
31
|
+
source={ImageAssets.terms_and_conditions}
|
|
32
|
+
style={tw` h-52 aspect-square`}
|
|
33
|
+
resizeMode="contain"
|
|
34
|
+
/>
|
|
35
|
+
<TButton
|
|
36
|
+
disabled
|
|
37
|
+
title="Terms & Conditions "
|
|
38
|
+
offGradient
|
|
39
|
+
containerStyle={tw`bg-secondary`}
|
|
40
|
+
/>
|
|
41
|
+
</View>
|
|
42
|
+
<View style={tw`gap-6 px-5 my-5`}>
|
|
43
|
+
<RenderHtml
|
|
44
|
+
contentWidth={_WIDTH}
|
|
45
|
+
baseStyle={{
|
|
46
|
+
color: tw.color("gray-300"),
|
|
47
|
+
fontFamily: "InterRegular",
|
|
48
|
+
fontSize: 16,
|
|
49
|
+
letterSpacing: 0.5,
|
|
50
|
+
lineHeight: 24,
|
|
51
|
+
}}
|
|
52
|
+
source={source}
|
|
53
|
+
/>
|
|
54
|
+
</View>
|
|
55
|
+
</ScrollView>
|
|
56
|
+
</View>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default Terms_And_Conditions;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as Location from "expo-location";
|
|
2
|
+
|
|
3
|
+
import { router } from "expo-router";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
|
|
6
|
+
interface Ilocation {
|
|
7
|
+
longitude: number;
|
|
8
|
+
latitude: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const useCheckLocation = () => {
|
|
12
|
+
const [location, setLocation] = useState<Ilocation | null>(null);
|
|
13
|
+
const [loading, setLoading] = useState(false);
|
|
14
|
+
|
|
15
|
+
const getLocation = async () => {
|
|
16
|
+
setLoading(true);
|
|
17
|
+
let { status } = await Location.requestForegroundPermissionsAsync();
|
|
18
|
+
if (status !== "granted") {
|
|
19
|
+
// router?.dismiss();
|
|
20
|
+
router?.push("/modals/toaster?content=Location access denied");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let location = await Location.getCurrentPositionAsync({});
|
|
24
|
+
// console.log(location);
|
|
25
|
+
|
|
26
|
+
const { latitude, longitude } = location.coords;
|
|
27
|
+
|
|
28
|
+
setLocation({
|
|
29
|
+
latitude,
|
|
30
|
+
longitude,
|
|
31
|
+
});
|
|
32
|
+
setLoading(false);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return { location, loading, getLocation, setLoading };
|
|
36
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as DocumentPicker from "expo-document-picker";
|
|
2
|
+
|
|
3
|
+
import { createAudioPlayer } from "expo-audio";
|
|
4
|
+
|
|
5
|
+
interface UseDocPickerReturn {
|
|
6
|
+
pickDocument: ({
|
|
7
|
+
multiple,
|
|
8
|
+
}: UseDocPickerProps) => Promise<
|
|
9
|
+
| DocumentPicker.DocumentPickerAsset
|
|
10
|
+
| DocumentPicker.DocumentPickerAsset[]
|
|
11
|
+
| null
|
|
12
|
+
>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const DocPickerType = {
|
|
16
|
+
audio: "audio/*",
|
|
17
|
+
pdf: "application/pdf",
|
|
18
|
+
images: "image/*",
|
|
19
|
+
videos: "video/*",
|
|
20
|
+
allFiles: "*/*",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
interface UseDocPickerProps {
|
|
24
|
+
// You can add props here if needed in the future
|
|
25
|
+
multiple?: boolean;
|
|
26
|
+
type?: (typeof DocPickerType)[keyof typeof DocPickerType];
|
|
27
|
+
allowDuration: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const useDocPicker = (): UseDocPickerReturn => {
|
|
31
|
+
const pickDocument = async ({
|
|
32
|
+
multiple,
|
|
33
|
+
type = DocPickerType.audio,
|
|
34
|
+
allowDuration,
|
|
35
|
+
}: UseDocPickerProps): Promise<
|
|
36
|
+
| DocumentPicker.DocumentPickerAsset
|
|
37
|
+
| DocumentPicker.DocumentPickerAsset[]
|
|
38
|
+
| null
|
|
39
|
+
> => {
|
|
40
|
+
// No permissions request is necessary for launching the document picker
|
|
41
|
+
const result = await DocumentPicker.getDocumentAsync({
|
|
42
|
+
multiple: !!multiple,
|
|
43
|
+
type: type,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// console.log(result);
|
|
47
|
+
|
|
48
|
+
if (result.canceled) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!result.canceled && allowDuration) {
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
let enter = true;
|
|
55
|
+
const player = createAudioPlayer(result?.assets![0]?.uri);
|
|
56
|
+
|
|
57
|
+
player.addListener("playbackStatusUpdate", (status) => {
|
|
58
|
+
if (status.isLoaded && status.duration > 0.1) {
|
|
59
|
+
if (player?.duration > allowDuration && enter) {
|
|
60
|
+
enter = false;
|
|
61
|
+
alert("Please select a file with a duration less than 3 minutes");
|
|
62
|
+
resolve(null);
|
|
63
|
+
} else if (enter) {
|
|
64
|
+
enter = false;
|
|
65
|
+
resolve(result.assets);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!result.canceled && !allowDuration) {
|
|
73
|
+
return result.assets;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Ensure we never return undefined
|
|
77
|
+
return null;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
pickDocument,
|
|
82
|
+
};
|
|
83
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as ImagePicker from "expo-image-picker";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
|
|
5
|
+
interface UseImagePickerReturn {
|
|
6
|
+
pickImage: ({ multiple }: UseImagePickerProps) => Promise<void>;
|
|
7
|
+
image: ImagePicker.ImagePickerAsset | ImagePicker.ImagePickerAsset[] | null;
|
|
8
|
+
closeImage: () => void;
|
|
9
|
+
setImage: React.Dispatch<
|
|
10
|
+
React.SetStateAction<
|
|
11
|
+
ImagePicker.ImagePickerAsset | ImagePicker.ImagePickerAsset[] | null
|
|
12
|
+
>
|
|
13
|
+
>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface UseImagePickerProps {
|
|
17
|
+
// You can add props here if needed in the future
|
|
18
|
+
multiple?: boolean;
|
|
19
|
+
ratio?: [number, number];
|
|
20
|
+
editing?: boolean;
|
|
21
|
+
quality?: number;
|
|
22
|
+
mediaTypes?: ImagePicker.MediaType[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const useImagePicker = (): UseImagePickerReturn => {
|
|
26
|
+
const [image, setImage] = React.useState<
|
|
27
|
+
ImagePicker.ImagePickerAsset | ImagePicker.ImagePickerAsset[] | null
|
|
28
|
+
>(null);
|
|
29
|
+
|
|
30
|
+
const pickImage = async ({
|
|
31
|
+
multiple,
|
|
32
|
+
ratio,
|
|
33
|
+
editing = false,
|
|
34
|
+
quality = 1,
|
|
35
|
+
mediaTypes = ["images"],
|
|
36
|
+
}: UseImagePickerProps) => {
|
|
37
|
+
// No permissions request is necessary for launching the image library
|
|
38
|
+
let result;
|
|
39
|
+
if (ratio?.length === 2) {
|
|
40
|
+
result = await ImagePicker.launchImageLibraryAsync({
|
|
41
|
+
mediaTypes: mediaTypes,
|
|
42
|
+
allowsEditing: editing,
|
|
43
|
+
aspect: ratio,
|
|
44
|
+
quality: quality,
|
|
45
|
+
});
|
|
46
|
+
} else {
|
|
47
|
+
result = await ImagePicker.launchImageLibraryAsync({
|
|
48
|
+
mediaTypes: mediaTypes,
|
|
49
|
+
allowsEditing: editing,
|
|
50
|
+
quality: quality,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// console.log(result);
|
|
55
|
+
|
|
56
|
+
if (!result.canceled && !multiple) {
|
|
57
|
+
setImage(result.assets[0]);
|
|
58
|
+
} else if (!result.canceled && multiple) {
|
|
59
|
+
setImage(result.assets);
|
|
60
|
+
} else {
|
|
61
|
+
setImage(null);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const closeImage = () => {
|
|
66
|
+
setImage(null);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return { pickImage, image, closeImage, setImage };
|
|
70
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
const GOOGLE_MAPS_API_KEY = "AIzaSyCZWDL-UblMpEOnoFf2UphrUbjUb6nLhxM";
|
|
4
|
+
export const useSuggestionLocation = () => {
|
|
5
|
+
const [locationSuggestions, setLocationSuggestions] = useState<any[]>([]);
|
|
6
|
+
const [suggestionLoading, setSuggestionLoading] = useState(false);
|
|
7
|
+
|
|
8
|
+
const handleSearchLocation = async (query: string) => {
|
|
9
|
+
try {
|
|
10
|
+
setSuggestionLoading(true);
|
|
11
|
+
const response = await axios.get(
|
|
12
|
+
`https://maps.googleapis.com/maps/api/place/textsearch/json?query=${query}&key=${GOOGLE_MAPS_API_KEY}`
|
|
13
|
+
);
|
|
14
|
+
// console.log(response);
|
|
15
|
+
setLocationSuggestions(response?.data?.results);
|
|
16
|
+
setSuggestionLoading(false);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
// console.log()
|
|
19
|
+
console.log(error);
|
|
20
|
+
setSuggestionLoading(false);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const clearSuggestions = () => {
|
|
25
|
+
setLocationSuggestions([]);
|
|
26
|
+
setSuggestionLoading(false);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
locationSuggestions,
|
|
31
|
+
handleSearchLocation,
|
|
32
|
+
clearSuggestions,
|
|
33
|
+
setLocationSuggestions,
|
|
34
|
+
suggestionLoading,
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { useUploadNewfileMutation } from "@/src/redux/apiSlices/mediaSlices";
|
|
3
|
+
|
|
4
|
+
interface FileUploadProgress {
|
|
5
|
+
fileName: string;
|
|
6
|
+
progress: number;
|
|
7
|
+
status: "existing" | "uploading" | "completed" | "failed";
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const useUploadProgress = () => {
|
|
12
|
+
const [uploadFile] = useUploadNewfileMutation();
|
|
13
|
+
const [uploadProgress, setUploadProgress] = React.useState<
|
|
14
|
+
FileUploadProgress[]
|
|
15
|
+
>([]);
|
|
16
|
+
const [isUploading, setIsUploading] = React.useState(false);
|
|
17
|
+
|
|
18
|
+
const uploadFilesWithProgress = async (files: any[]) => {
|
|
19
|
+
setIsUploading(true);
|
|
20
|
+
|
|
21
|
+
const existingIds: string[] = [];
|
|
22
|
+
const localFiles: any[] = [];
|
|
23
|
+
|
|
24
|
+
// ✅ Separate backend + local files
|
|
25
|
+
files.forEach((file) => {
|
|
26
|
+
if (file?._id && !file.mimeType) {
|
|
27
|
+
existingIds.push(file._id);
|
|
28
|
+
} else if (file?.mimeType) {
|
|
29
|
+
localFiles.push(file);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// ✅ Initialize state only once
|
|
34
|
+
setUploadProgress(
|
|
35
|
+
files.map((file) => ({
|
|
36
|
+
fileName: file.name || file.url?.split("/")?.pop() || "backend_file",
|
|
37
|
+
progress: file._id ? 100 : 0,
|
|
38
|
+
status: file._id ? "existing" : "uploading",
|
|
39
|
+
}))
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const uploadedLocalIds: string[] = [];
|
|
43
|
+
const errors: string[] = [];
|
|
44
|
+
|
|
45
|
+
// ✅ Upload local files sequentially with stable updates
|
|
46
|
+
for (let i = 0; i < localFiles.length; i++) {
|
|
47
|
+
const file = localFiles[i];
|
|
48
|
+
|
|
49
|
+
const matchIndex = files.findIndex(
|
|
50
|
+
(f) => f.uri === file.uri || f.url === file.url
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// ✅ Reliable progress update
|
|
55
|
+
setUploadProgress((prev) => {
|
|
56
|
+
const updated = [...prev];
|
|
57
|
+
updated[matchIndex] = {
|
|
58
|
+
...updated[matchIndex],
|
|
59
|
+
progress: 30,
|
|
60
|
+
status: "uploading",
|
|
61
|
+
};
|
|
62
|
+
return updated;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const formData = new FormData();
|
|
66
|
+
formData.append("audio", {
|
|
67
|
+
uri: file.uri,
|
|
68
|
+
name: file.name,
|
|
69
|
+
type: file.mimeType,
|
|
70
|
+
} as any);
|
|
71
|
+
|
|
72
|
+
const uploadedMedia = await uploadFile(formData).unwrap();
|
|
73
|
+
|
|
74
|
+
setUploadProgress((prev) => {
|
|
75
|
+
const updated = [...prev];
|
|
76
|
+
updated[matchIndex] = {
|
|
77
|
+
...updated[matchIndex],
|
|
78
|
+
progress: 100,
|
|
79
|
+
status: "completed",
|
|
80
|
+
};
|
|
81
|
+
return updated;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (uploadedMedia?.data) {
|
|
85
|
+
const ids = uploadedMedia.data.map((item: any) => item._id);
|
|
86
|
+
uploadedLocalIds.push(...ids);
|
|
87
|
+
}
|
|
88
|
+
} catch (err: any) {
|
|
89
|
+
setUploadProgress((prev) => {
|
|
90
|
+
const updated = [...prev];
|
|
91
|
+
updated[matchIndex] = {
|
|
92
|
+
...updated[matchIndex],
|
|
93
|
+
progress: 0,
|
|
94
|
+
status: "failed",
|
|
95
|
+
error: err?.data?.message || "Upload failed",
|
|
96
|
+
};
|
|
97
|
+
return updated;
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
errors.push(err?.data?.message || "Upload failed");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
setIsUploading(false);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
success: errors.length === 0,
|
|
108
|
+
audioIds: [...existingIds, ...uploadedLocalIds],
|
|
109
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const totalProgress =
|
|
114
|
+
uploadProgress.length > 0
|
|
115
|
+
? Math.round(
|
|
116
|
+
uploadProgress.reduce((sum, f) => sum + f.progress, 0) /
|
|
117
|
+
uploadProgress.length
|
|
118
|
+
)
|
|
119
|
+
: 0;
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
uploadFilesWithProgress,
|
|
123
|
+
uploadProgress,
|
|
124
|
+
totalProgress,
|
|
125
|
+
isUploading,
|
|
126
|
+
};
|
|
127
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { BaseQueryFn, createApi } from "@reduxjs/toolkit/query/react";
|
|
2
|
+
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
|
|
3
|
+
|
|
4
|
+
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
5
|
+
import { tagTypesList } from "../interface/tag-types";
|
|
6
|
+
|
|
7
|
+
// const BaseURl = "http://10.10.10.110:3000";
|
|
8
|
+
const BaseURl = "http://72.244.153.25:3000";
|
|
9
|
+
|
|
10
|
+
interface BaseQueryArgs extends AxiosRequestConfig {
|
|
11
|
+
url: string;
|
|
12
|
+
method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
13
|
+
body?: any;
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Type for the args that will be passed to axios (base query arguments)
|
|
18
|
+
|
|
19
|
+
const baseQueryWithRath: BaseQueryFn<BaseQueryArgs, unknown, unknown> = async (
|
|
20
|
+
args,
|
|
21
|
+
api,
|
|
22
|
+
extraOptions,
|
|
23
|
+
) => {
|
|
24
|
+
try {
|
|
25
|
+
const token = await AsyncStorage.getItem("token");
|
|
26
|
+
// console.log(args?.headers);
|
|
27
|
+
|
|
28
|
+
// console.log(token, "token from base url............");
|
|
29
|
+
|
|
30
|
+
const result: AxiosResponse = await axios({
|
|
31
|
+
baseURL: BaseURl + "/api/v1",
|
|
32
|
+
...args,
|
|
33
|
+
url: args.url,
|
|
34
|
+
method: args.method,
|
|
35
|
+
data: args.body,
|
|
36
|
+
headers: {
|
|
37
|
+
Authorization: token ? `Bearer ${token}` : "",
|
|
38
|
+
...args.headers,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// console.log(result?.headers);
|
|
43
|
+
// Check if response data is a string and malformed
|
|
44
|
+
|
|
45
|
+
// console.log(result.request, "result from base url");
|
|
46
|
+
|
|
47
|
+
// if (result?.status === 403) {
|
|
48
|
+
// AsyncStorage.removeItem("token");
|
|
49
|
+
// AsyncStorage.removeItem("user");
|
|
50
|
+
// }
|
|
51
|
+
|
|
52
|
+
// if (result?.status === 401) {
|
|
53
|
+
// AsyncStorage.removeItem("token");
|
|
54
|
+
// AsyncStorage.removeItem("user");
|
|
55
|
+
// }
|
|
56
|
+
|
|
57
|
+
return { data: result?.data };
|
|
58
|
+
} catch (error: any) {
|
|
59
|
+
return {
|
|
60
|
+
error: error?.response?.data,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Define the `createApi` with appropriate types
|
|
66
|
+
export const api = createApi({
|
|
67
|
+
keepUnusedDataFor: 0,
|
|
68
|
+
reducerPath: "api",
|
|
69
|
+
baseQuery: baseQueryWithRath,
|
|
70
|
+
endpoints: () => ({}),
|
|
71
|
+
tagTypes: tagTypesList,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// export const imageUrl = 'http://192.168.12.160:7000/';
|
|
75
|
+
// export const imageUrl = "http://103.186.20.114:8050";
|
|
76
|
+
|
|
77
|
+
export const getImageUrl = (url: string) => {
|
|
78
|
+
if (!url) return "";
|
|
79
|
+
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
80
|
+
return url;
|
|
81
|
+
}
|
|
82
|
+
const base = BaseURl!.replace(/\/+$/, "");
|
|
83
|
+
const path = url.replace(/^\/+/, "");
|
|
84
|
+
return `${base}/${path}`;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const getMedia = (url: string) => {
|
|
88
|
+
return `${BaseURl}${url}`;
|
|
89
|
+
};
|