@towsila/expo-ui-library 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 (104) hide show
  1. package/README.md +142 -0
  2. package/package.json +55 -0
  3. package/src/assets/customIcons/arrow.svg +3 -0
  4. package/src/assets/customIcons/arrow_back.svg +3 -0
  5. package/src/assets/customIcons/ban.svg +4 -0
  6. package/src/assets/customIcons/checkmark.svg +4 -0
  7. package/src/assets/customIcons/close.svg +5 -0
  8. package/src/assets/customIcons/dark_mode.svg +3 -0
  9. package/src/assets/customIcons/dark_suspended.svg +67 -0
  10. package/src/assets/customIcons/document.svg +4 -0
  11. package/src/assets/customIcons/email.svg +3 -0
  12. package/src/assets/customIcons/eye.svg +5 -0
  13. package/src/assets/customIcons/eye_off.svg +7 -0
  14. package/src/assets/customIcons/humberger.svg +5 -0
  15. package/src/assets/customIcons/information.svg +6 -0
  16. package/src/assets/customIcons/light_mode.svg +18 -0
  17. package/src/assets/customIcons/light_suspended.svg +67 -0
  18. package/src/assets/customIcons/lock_closed.svg +4 -0
  19. package/src/assets/customIcons/logout.svg +4 -0
  20. package/src/assets/customIcons/person.svg +4 -0
  21. package/src/assets/customIcons/phone.svg +3 -0
  22. package/src/assets/customIcons/phone_dark.svg +36 -0
  23. package/src/assets/customIcons/phone_light.svg +36 -0
  24. package/src/assets/customIcons/shield.svg +4 -0
  25. package/src/assets/customIcons/trash.svg +9 -0
  26. package/src/assets/customIcons/warning.svg +5 -0
  27. package/src/assets/icons/google.svg +9 -0
  28. package/src/assets/images/bg_dark.svg +5 -0
  29. package/src/assets/images/bg_dark_v1.svg +5 -0
  30. package/src/assets/images/bg_light.svg +5 -0
  31. package/src/assets/images/bg_light_v1.svg +5 -0
  32. package/src/assets/images/en.jpg +0 -0
  33. package/src/assets/images/fr.jpg +0 -0
  34. package/src/components/icons/ban.tsx +77 -0
  35. package/src/components/icons/chatIcon.tsx +19 -0
  36. package/src/components/icons/homeIcon.tsx +25 -0
  37. package/src/components/icons/profileIcon.tsx +29 -0
  38. package/src/components/icons/settingsIcon.tsx +22 -0
  39. package/src/components/reusable/templatePage.tsx +94 -0
  40. package/src/components/reusable/templateSubPage.tsx +66 -0
  41. package/src/components/screens/AccountStatusScreen.tsx +102 -0
  42. package/src/components/screens/AddReportScreen.tsx +135 -0
  43. package/src/components/screens/LegalScreen.tsx +41 -0
  44. package/src/components/screens/VerificationCodeScreen.tsx +66 -0
  45. package/src/components/screens/aboutScreen.tsx +139 -0
  46. package/src/components/screens/forgotPasswordScreen.tsx +60 -0
  47. package/src/components/screens/loginScreen.tsx +165 -0
  48. package/src/components/screens/onBoarding.tsx +178 -0
  49. package/src/components/screens/resetPassword.tsx +115 -0
  50. package/src/components/screens/settingScreen.tsx +85 -0
  51. package/src/components/screens/signUpScreen.tsx +119 -0
  52. package/src/components/screens/splashScreen.tsx +170 -0
  53. package/src/components/ui/OTPInput.tsx +73 -0
  54. package/src/components/ui/PageTopBar.tsx +41 -0
  55. package/src/components/ui/asidebar.tsx +151 -0
  56. package/src/components/ui/badge.tsx +21 -0
  57. package/src/components/ui/button.tsx +58 -0
  58. package/src/components/ui/customPopup.tsx +176 -0
  59. package/src/components/ui/dynamicForm.tsx +144 -0
  60. package/src/components/ui/error.tsx +23 -0
  61. package/src/components/ui/input.tsx +121 -0
  62. package/src/components/ui/languageSelect.tsx +99 -0
  63. package/src/components/ui/pageIndicator.tsx +58 -0
  64. package/src/components/ui/passwordRequirmentCard.tsx +55 -0
  65. package/src/components/ui/sharpButton.tsx +67 -0
  66. package/src/components/ui/sharpContainer.tsx +180 -0
  67. package/src/components/ui/sharpDynamicForm.tsx +148 -0
  68. package/src/components/ui/sharpInput.tsx +148 -0
  69. package/src/components/ui/sharpOtpInput.tsx +228 -0
  70. package/src/components/ui/sharpSelect.tsx +155 -0
  71. package/src/components/ui/successDialog.tsx +97 -0
  72. package/src/components/ui/termsPopup.tsx +56 -0
  73. package/src/components/ui/themeToggle.tsx +98 -0
  74. package/src/components/ui/toast.tsx +163 -0
  75. package/src/components/ui/warningModel.tsx +132 -0
  76. package/src/constants/language.ts +15 -0
  77. package/src/context/LanguageContext.tsx +49 -0
  78. package/src/context/LocationPermissionProvider.tsx +106 -0
  79. package/src/context/ThemeContext.tsx +70 -0
  80. package/src/hooks/useCountdown.ts +21 -0
  81. package/src/hooks/useForgotPasswordForm.ts +46 -0
  82. package/src/hooks/useForm.ts +52 -0
  83. package/src/hooks/useLoginForm.ts +25 -0
  84. package/src/hooks/useResetPasswordForm.ts +75 -0
  85. package/src/hooks/useSignup.tsx +34 -0
  86. package/src/hooks/useSubmitReport.ts +47 -0
  87. package/src/hooks/useUpdateUser.tsx +81 -0
  88. package/src/icons/index.ts +36 -0
  89. package/src/index.ts +62 -0
  90. package/src/service/createApi.ts +57 -0
  91. package/src/service/sendEmail.ts +11 -0
  92. package/src/tokens/themes.ts +19 -0
  93. package/src/tokens/typography.ts +16 -0
  94. package/src/types/formFields.ts +13 -0
  95. package/src/types/icon.ts +6 -0
  96. package/src/types/language.ts +7 -0
  97. package/src/types/menu.ts +9 -0
  98. package/src/types/settingsFields.tsx +9 -0
  99. package/src/types/svg.d.ts +6 -0
  100. package/src/types/validateForm.ts +8 -0
  101. package/src/utils/helpers/groupSettings.ts +21 -0
  102. package/src/utils/inputValidator/customValidator.ts +36 -0
  103. package/src/utils/inputValidator/generalTerms.ts +19 -0
  104. package/src/utils/inputValidator/registerValidator.ts +31 -0
@@ -0,0 +1,57 @@
1
+ import * as SecureStore from "expo-secure-store";
2
+
3
+ type ApiClientConfig = {
4
+ baseUrl: string;
5
+ accessTokenKey?: string; // SecureStore key for access token
6
+ refreshTokenKey?: string; // SecureStore key for refresh token
7
+ /**
8
+ * Called when a 401 is received. Should perform the refresh call,
9
+ * store the new tokens, and return the new access token (or null/throw on failure).
10
+ * Left to each app since refresh endpoints/payloads can differ.
11
+ */
12
+ onRefreshToken: () => Promise<string | null>;
13
+ };
14
+
15
+ export function createApiClient(config: ApiClientConfig) {
16
+ const {
17
+ baseUrl,
18
+ accessTokenKey = "access_token",
19
+ onRefreshToken,
20
+ } = config;
21
+
22
+ async function apiRequest(path: string, options: RequestInit = {}) {
23
+ const token = await SecureStore.getItemAsync(accessTokenKey);
24
+
25
+ const res = await fetch(`${baseUrl}${path}`, {
26
+ ...options,
27
+ headers: {
28
+ "Content-Type": "application/json",
29
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
30
+ ...options.headers,
31
+ },
32
+ });
33
+
34
+ if (res.status === 401) {
35
+ const newToken = await onRefreshToken();
36
+
37
+ if (!newToken) {
38
+ // refresh failed — return the original 401 response so the
39
+ // caller's existing error handling (res.ok checks) still works
40
+ return res;
41
+ }
42
+
43
+ return await fetch(`${baseUrl}${path}`, {
44
+ ...options,
45
+ headers: {
46
+ "Content-Type": "application/json",
47
+ Authorization: `Bearer ${newToken}`,
48
+ ...options.headers,
49
+ },
50
+ });
51
+ }
52
+
53
+ return res;
54
+ }
55
+
56
+ return { apiRequest };
57
+ }
@@ -0,0 +1,11 @@
1
+ import { Linking } from "react-native";
2
+
3
+ export const sendEmail = async (email:string) => {
4
+
5
+ const url = `mailto:${email}`;
6
+
7
+ const canOpen = await Linking.canOpenURL(url);
8
+ if (canOpen) {
9
+ await Linking.openURL(url);
10
+ }
11
+ };
@@ -0,0 +1,19 @@
1
+ export type AppTheme = {
2
+ primary: string,
3
+ text: {
4
+ primary: string,
5
+ secondary: string,
6
+ placeholder: string,
7
+ },
8
+ background: {
9
+ main: string,
10
+ card: string,
11
+ },
12
+ semantic: {
13
+ border: string,
14
+ accent: string,
15
+ success: string,
16
+ warning: string,
17
+ error: string,
18
+ },
19
+ }
@@ -0,0 +1,16 @@
1
+ export const Fonts = {
2
+ montserrat: {
3
+ regular: "Montserrat_400Regular",
4
+ semiBold: "Montserrat_600SemiBold",
5
+ bold: "Montserrat_700Bold",
6
+ },
7
+ };
8
+
9
+ export const textSize = {
10
+ xs: 13,
11
+ sm: 14,
12
+ md: 16,
13
+ lg: 18,
14
+ xl: 20,
15
+ xxl: 24,
16
+ };
@@ -0,0 +1,13 @@
1
+ import { Ionicons } from "@expo/vector-icons";
2
+
3
+ export type Field ={
4
+ name : string;
5
+ placeholder: string;
6
+ textContentType: string;
7
+ secureTextEntry?: boolean;
8
+ icon?: keyof typeof Ionicons.glyphMap;
9
+ label:string;
10
+ type?: string;
11
+ svgIcon?: (color: string) => React.ReactNode;
12
+ [key: string]: any;
13
+ };
@@ -0,0 +1,6 @@
1
+ export type IconType={
2
+ width?: number;
3
+ height?:number;
4
+ fillColor?: string;
5
+ borderColor?: string;
6
+ }
@@ -0,0 +1,7 @@
1
+ import { ImageSourcePropType } from "react-native";
2
+ import {Language} from "../context/LanguageContext"
3
+ export type LanguageType = {
4
+ code: Language;
5
+ name: string;
6
+ logo: ImageSourcePropType;
7
+ }
@@ -0,0 +1,9 @@
1
+ import { Ionicons } from "@expo/vector-icons";
2
+
3
+ export type Menu = {
4
+ icon?: keyof typeof Ionicons.glyphMap;
5
+ iconActive?: keyof typeof Ionicons.glyphMap;
6
+ iconSvg?: (color: string)=>React.ReactNode;
7
+ name: string;
8
+ link: string;
9
+ }
@@ -0,0 +1,9 @@
1
+ export interface Settings {
2
+ id: string;
3
+ title: string;
4
+ iconLeft: React.ReactNode;
5
+ iconRight?: React.ReactNode;
6
+ route?: string; // for navigation
7
+ isDarkModeToggle?: boolean; // marks the special dark mode row
8
+ category?: number; //give the fields that will be grouped in the same categorie the same number
9
+ }
@@ -0,0 +1,6 @@
1
+ declare module "*.svg" {
2
+ import React from "react";
3
+ import { SvgProps } from "react-native-svg";
4
+ const content: React.FC<SvgProps>;
5
+ export default content;
6
+ }
@@ -0,0 +1,8 @@
1
+ export type FieldConfig = {
2
+ name: string;
3
+ required?: boolean;
4
+ type?: string;
5
+ minLength?: number;
6
+ maxLength?: number;
7
+ label?: string;
8
+ };
@@ -0,0 +1,21 @@
1
+ import { Settings } from "../../types/settingsFields";
2
+
3
+ export function groupSettings(fields: Settings[]) {
4
+ const ungrouped: Settings[] = [];
5
+ const grouped: Map<number, { items: Settings[] }> = new Map();
6
+
7
+ fields.forEach((field) => {
8
+ if (field.category === undefined) {
9
+ ungrouped.push(field);
10
+ } else {
11
+ if (!grouped.has(field.category)) {
12
+ grouped.set(field.category, {
13
+ items: [],
14
+ });
15
+ }
16
+ grouped.get(field.category)!.items.push(field);
17
+ }
18
+ });
19
+
20
+ return { ungrouped, groups: Array.from(grouped.values()) };
21
+ }
@@ -0,0 +1,36 @@
1
+ import { registerValidator } from "../../utils/inputValidator/registerValidator";
2
+
3
+ registerValidator("text", (value, field) => {
4
+ if (field.required !== false && !value.trim())
5
+ return `${field.label ?? field.name} is required`;
6
+ return "";
7
+ });
8
+
9
+ registerValidator("email", (value) => {
10
+ if (!value.trim()) return "Email is required";
11
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
12
+ if (!emailRegex.test(value.trim())) return "Invalid email address";
13
+ return "";
14
+ });
15
+
16
+ registerValidator("password", (value, field) => {
17
+ const min = field.minLength ?? 8;
18
+ if (!value) return "Password is required";
19
+ if (value.length < min) return `Password must be at least ${min} characters`;
20
+ if (!/[!@#$%^&*(),.?":{}|<>]/.test(value)) return "Password must contain at least one special character";
21
+ return "";
22
+ });
23
+
24
+ registerValidator("confirmPassword", (value, _, formState) => {
25
+ if (!value) return "Please confirm your password";
26
+ if (value.length < 8) return "Password must be at least 8 characters";
27
+ if (value !== formState["password"]) return "Passwords do not match";
28
+ return "";
29
+ });
30
+
31
+ registerValidator("phone", (value, field) => {
32
+ if (!value.trim()) return `${field.label ?? "Phone"} is required`;
33
+ const phoneRegex = /^0[567]\d{8}$/;
34
+ if (!phoneRegex.test(value.trim())) return "Invalid phone number";
35
+ return "";
36
+ });
@@ -0,0 +1,19 @@
1
+ // General (all users)
2
+
3
+
4
+
5
+ export const serviceTerms:string[] = [
6
+ "You must be at least 18 years old",
7
+ "You must provide a valid email and an active Algerian phone number",
8
+ "All information must be accurate and up to date",
9
+ "You are solely responsible for the confidentiality of your login credentials",
10
+ "Any fraudulent or abusive use will result in immediate account suspension or deletion",
11
+ "Disputes are subject to the jurisdiction of Algiers courts, under Algerian law",
12
+ "Your location is only shared with the Provider after your explicit confirmation",
13
+ "You must provide accurate and up-to-date information about your qualifications, experience, and services",
14
+ "You get a free 30-day trial — no payment info required",
15
+ " Subscriptions start at 5,000 DA/month (Basic) or 10,000 DA/month (Premium)",
16
+ "You are solely responsible for the quality, safety, and legality of your services",
17
+ "Any false information about your capabilities or availability leads to immediate suspension",
18
+ "Annual subscriptions are non-refundable after 14 days",
19
+ ];
@@ -0,0 +1,31 @@
1
+ import { FieldConfig } from "../../types/validateForm";
2
+ type FormState = Record<string, string>;
3
+ export type ValidatorFn = (value: string, field: FieldConfig, formState: FormState) => string;
4
+
5
+ const validatorRegistry: Record<string, ValidatorFn> = {};
6
+
7
+ export function registerValidator(type: string, fn: ValidatorFn): void {
8
+ validatorRegistry[type] = fn;
9
+ }
10
+
11
+ export function validateForm(
12
+ fields: FieldConfig[],
13
+ formState: FormState
14
+ ): Record<string, string> {
15
+ const errors: Record<string, string> = {};
16
+
17
+ fields.forEach((field) => {
18
+ const type = field.type ?? "text";
19
+ const validator = validatorRegistry[type];
20
+
21
+ if (!validator) {
22
+ console.warn(`No validator registered for type: "${type}"`);
23
+ return;
24
+ }
25
+
26
+ const error = validator(formState[field.name] ?? "", field, formState);
27
+ if (error) errors[field.name] = error;
28
+ });
29
+
30
+ return errors;
31
+ }