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.
Files changed (46) hide show
  1. package/bin/index.js +3 -0
  2. package/bun.lock +82 -0
  3. package/package.json +26 -0
  4. package/src/copy-template.js +156 -0
  5. package/src/create-expo.js +10 -0
  6. package/src/index.js +39 -0
  7. package/src/install-deps.js +22 -0
  8. package/src/utils/logger.js +29 -0
  9. package/tailwind.config.js +69 -0
  10. package/template/src/app/_layout.tsx +61 -0
  11. package/template/src/app/auth/_layout.tsx +41 -0
  12. package/template/src/app/auth/change_pass.tsx +138 -0
  13. package/template/src/app/auth/change_pass_modal.tsx +74 -0
  14. package/template/src/app/auth/forgot.tsx +124 -0
  15. package/template/src/app/auth/index.tsx +274 -0
  16. package/template/src/app/auth/opt_verify.tsx +145 -0
  17. package/template/src/app/auth/register.tsx +334 -0
  18. package/template/src/app/auth/reset_pass.tsx +152 -0
  19. package/template/src/app/common/image.tsx +202 -0
  20. package/template/src/app/common/openurl.tsx +42 -0
  21. package/template/src/app/home/_layout.tsx +29 -0
  22. package/template/src/app/home/drawer/_layout.tsx +27 -0
  23. package/template/src/app/home/tabs/_layout.tsx +75 -0
  24. package/template/src/app/home/tabs/index.tsx +11 -0
  25. package/template/src/app/index.tsx +11 -0
  26. package/template/src/app/modals/confirmation_logout_modal.tsx +78 -0
  27. package/template/src/app/modals/payment_modal.tsx +105 -0
  28. package/template/src/app/modals/success_modal.tsx +72 -0
  29. package/template/src/app/modals/toaster.tsx +31 -0
  30. package/template/src/app/settings/_layout.tsx +19 -0
  31. package/template/src/app/settings/about_us.tsx +61 -0
  32. package/template/src/app/settings/privacy_policy.tsx +61 -0
  33. package/template/src/app/settings/terms_and_conditions.tsx +60 -0
  34. package/template/src/hooks/useCheckLocation.ts +36 -0
  35. package/template/src/hooks/useDocPicker.ts +83 -0
  36. package/template/src/hooks/useImgePicker.ts +70 -0
  37. package/template/src/hooks/useSuggestionLocation.ts +36 -0
  38. package/template/src/hooks/useUploadProgress.ts +127 -0
  39. package/template/src/redux/api-config/baseApi.ts +89 -0
  40. package/template/src/redux/api-slices/authSlices.ts +175 -0
  41. package/template/src/redux/interface/common.ts +19 -0
  42. package/template/src/redux/interface/interface.ts +196 -0
  43. package/template/src/redux/interface/tag-types.ts +13 -0
  44. package/template/src/redux/service/demo.ts +27 -0
  45. package/template/src/redux/store.ts +17 -0
  46. 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
+ };