diff --git a/babel.config.js b/babel.config.js
index 6cc5823..c35ae46 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -5,10 +5,20 @@ module.exports = {
[
'module-resolver',
{
- extensions: ['.ios.js', '.android.js', '.ios.jsx', '.android.jsx', '.js', '.jsx', '.json', '.ts', '.tsx'],
+ extensions: [
+ '.ios.js',
+ '.android.js',
+ '.ios.jsx',
+ '.android.jsx',
+ '.js',
+ '.jsx',
+ '.json',
+ '.ts',
+ '.tsx',
+ ],
root: ['.'],
alias: {
- "@redux": "./src/redux",
+ '@redux': './src/redux',
'@lang': './src/lang',
'@pages': './src/pages',
'@api': './src/api',
@@ -20,7 +30,8 @@ module.exports = {
'@navigation': './src/navigation',
'@configs': './src/configs',
'@helper': './src/helper',
- '@user': './src/user'
+ '@user': './src/user',
+ '@event': './src/event',
},
},
],
diff --git a/package-lock.json b/package-lock.json
index e24ae7b..286b706 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -32,6 +32,7 @@
"react-native-user-agent": "^2.3.1",
"react-native-vector-icons": "^10.0.2",
"react-redux": "^8.1.3",
+ "react-string-replace": "^1.1.1",
"realm": "^12.3.1",
"redux": "^4.2.1"
},
@@ -19494,6 +19495,14 @@
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
}
},
+ "node_modules/react-string-replace": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/react-string-replace/-/react-string-replace-1.1.1.tgz",
+ "integrity": "sha512-26TUbLzLfHQ5jO5N7y3Mx88eeKo0Ml0UjCQuX4BMfOd/JX+enQqlKpL1CZnmjeBRvQE8TR+ds9j1rqx9CxhKHQ==",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
"node_modules/react-test-renderer": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.2.0.tgz",
diff --git a/package.json b/package.json
index 231743f..cf27f34 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
"react-native-user-agent": "^2.3.1",
"react-native-vector-icons": "^10.0.2",
"react-redux": "^8.1.3",
+ "react-string-replace": "^1.1.1",
"realm": "^12.3.1",
"redux": "^4.2.1"
},
diff --git a/src/App.tsx b/src/App.tsx
index fe185c6..772bcf3 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -11,6 +11,8 @@ import configDarkTheme from '@configs/colors';
import Navigation from '@navigation/navigation';
import {MyStatusBar} from '@components/MyStatusBar';
import {SafeAreaView} from 'react-native';
+import {appVarActions} from '@configs/appVarReducer';
+import {saveVarChanges} from '@helper/appData';
const App = () => {
useEffect(() => {
@@ -52,7 +54,7 @@ const OtherProviders = () => {
return (
-
+
diff --git a/src/components/MyButton.tsx b/src/components/MyButton.tsx
index c65d963..9027bbe 100644
--- a/src/components/MyButton.tsx
+++ b/src/components/MyButton.tsx
@@ -120,6 +120,7 @@ export function MyButton({
alignItems: 'center',
padding: 10,
borderRadius: 10,
+ opacity: disabled ? currentTheme.opacity[60] : 1,
}}>
diff --git a/src/components/MyInput.tsx b/src/components/MyInput.tsx
index 25bf403..d8c4625 100644
--- a/src/components/MyInput.tsx
+++ b/src/components/MyInput.tsx
@@ -1,9 +1,15 @@
-import {KeyboardTypeOptions, TextInput, View} from 'react-native';
+import {
+ KeyboardTypeOptions,
+ StyleSheet,
+ TextInput,
+ TouchableOpacity,
+ View,
+} from 'react-native';
import {MyIcon} from './MyIcon';
import {useSelector} from 'react-redux';
import {RootState} from '@redux/store';
import {Text} from '@gluestack-ui/themed';
-import {useRef, useState} from 'react';
+import {ReactNode, useRef, useState} from 'react';
interface MyIconInputProps {
text: string;
@@ -13,6 +19,9 @@ interface MyIconInputProps {
value?: string;
onChangeText?: (text: string) => void;
disableContainer?: boolean;
+ maxLength?: number;
+ rightComponent?: ReactNode;
+ helper?: ReactNode;
}
export function MyIconInput({
@@ -23,42 +32,106 @@ export function MyIconInput({
value,
onChangeText,
disableContainer,
+ maxLength,
+ rightComponent,
+ helper,
}: MyIconInputProps) {
const currentTheme = useSelector(
(state: RootState) => state.nonSaveVariables.theme.colors,
);
+ const [password, setPassword] = useState('');
+ const [isPasswordVisible, setIsPasswordVisible] = useState(false);
+
+ const togglePasswordVisibility = () => {
+ setIsPasswordVisible(!isPasswordVisible);
+ };
+
return (
-
-
+
+
+
+
+
+
+
+ {text}
+
+
+
+
+
+ {rightComponent && (
+ {rightComponent}
+ )}
+
+
-
- {text}
-
-
+ {helper}
+
+ );
+}
+
+export interface MyInputErrorProps {
+ iconName?: string;
+ text: string;
+}
+
+export function MyInputError({
+ iconName,
+ text,
+}: MyInputErrorProps): React.ReactElement {
+ const theme = useSelector(
+ (state: RootState) => state.nonSaveVariables.theme.colors,
+ );
+
+ return (
+
+
+
+ {text}
);
}
diff --git a/src/components/MyToast.tsx b/src/components/MyToast.tsx
index 316c030..d57ae29 100644
--- a/src/components/MyToast.tsx
+++ b/src/components/MyToast.tsx
@@ -1,10 +1,20 @@
-import {Alert, CloseIcon, useToast} from '@gluestack-ui/themed';
+import {
+ Alert,
+ CloseIcon,
+ InfoIcon,
+ Toast,
+ ToastDescription,
+ ToastTitle,
+ useToast,
+} from '@gluestack-ui/themed';
import {HStack} from '@gluestack-ui/themed';
import {Text} from '@gluestack-ui/themed';
import {VStack} from '@gluestack-ui/themed';
import {AlertIcon} from '@gluestack-ui/themed';
-import {MyButton} from './MyButton';
+import {MyButton, MyIconButton} from './MyButton';
+import {AlertText} from '@gluestack-ui/themed';
+/*
const Toast = () => {
const toast = useToast();
const ToastDetails = [
@@ -37,7 +47,7 @@ const Toast = () => {
description: 'Please enter a valid email address',
},
];
-};
+}; */
interface toastType {
id?: string;
@@ -46,7 +56,7 @@ interface toastType {
title: any;
description: any;
isClosable?: boolean;
- rest: any;
+ rest?: any;
}
interface alertType extends toastType {
@@ -64,9 +74,10 @@ function showToast(toast: any, item: toastType) {
rest,
}: alertType) => (
@@ -85,15 +96,17 @@ function showToast(toast: any, item: toastType) {
? '$white'
: variant !== 'outline'
? '$black'
- : null
+ : '$white'
}>
{title}
{isClosable ? (
- toast.close(id)}
/>
) : /*
{description}
diff --git a/src/components/map/types.ts b/src/components/map/types.ts
index 6afe2e6..0b765e2 100644
--- a/src/components/map/types.ts
+++ b/src/components/map/types.ts
@@ -1,9 +1,12 @@
-type EventID = string;
+import { EventID, EventType } from '@event/types';
+
+
interface BasicEvent {
id: EventID;
latitude: number;
longitude: number;
+ type: EventType | "cluster";
}
interface PA_Point_Cluster extends BasicEvent {
diff --git a/src/configs/appNonSaveVar.ts b/src/configs/appNonSaveVar.ts
index d05f213..b095b11 100644
--- a/src/configs/appNonSaveVar.ts
+++ b/src/configs/appNonSaveVar.ts
@@ -6,12 +6,15 @@ import {AccountName} from './types';
import {getVersionByNum, VersionType} from '@helper/version';
import configDarkTheme, {ThemeTokensType} from '@configs/colors';
+import { EventID, PAEvent } from '@event/types';
+import { PA_Point } from '@components/map/types';
+
-import {PA_Point} from '@components/map/cluster/getData';
export const APP_VERSION = getVersionByNum(1);
export const AppVarMaxBackups: number = 10;
export const maxCachedUsers = 30;
+export const maxCachedEvents = 30;
export enum appStatus {
IS_LOADING,
@@ -31,6 +34,7 @@ export interface NON_SAVE_VARS {
theme: ThemeTokensType;
connectionStatus: connectionStatus;
cachedUsers: {[key: AccountName]: User};
+ cachedEvents: {[key: EventID]: PAEvent};
chats: {[key: roomId]: chatEntity};
chatActivity: roomId[];
selectedChat: roomId | 'none';
@@ -43,6 +47,7 @@ export const non_save_vars: NON_SAVE_VARS = {
theme: configDarkTheme.tokens,
connectionStatus: connectionStatus.UNKNOWN,
cachedUsers: {},
+ cachedEvents: {},
chats: {},
chatActivity: [],
selectedChat: 'none',
diff --git a/src/configs/appNonSaveVarReducer.ts b/src/configs/appNonSaveVarReducer.ts
index d6b5679..8b3e202 100644
--- a/src/configs/appNonSaveVarReducer.ts
+++ b/src/configs/appNonSaveVarReducer.ts
@@ -5,8 +5,9 @@ import {ThemeTokensType} from '@configs/colors';
import {chatEntity, roomId} from '@configs/chat/types';
import {User} from '@user/types';
-import {AccountName} from './types';
-import {PA_Point} from '@components/map/cluster/getData';
+import {AccountName, EventId} from './types';
+import {PA_Point} from '@components/map/types';
+import {PAEvent} from '@event/types';
export const appNonSaveVariablesSlice = createSlice({
name: 'non_save_vars',
@@ -24,6 +25,13 @@ export const appNonSaveVariablesSlice = createSlice({
removeCachedUser: (state, action: PayloadAction) => {
delete state.cachedUsers[action.payload];
},
+ setCachedEvent: (state, action: PayloadAction) => {
+ state.cachedEvents[action.payload.UUID] = action.payload;
+ },
+ removeCachedEvent: (state, action: PayloadAction) => {
+ delete state.cachedEvents[action.payload];
+ },
+
setSelectedChat: (state, action: PayloadAction) => {
state.selectedChat = action.payload;
},
diff --git a/src/configs/types.ts b/src/configs/types.ts
index aaf9c1c..390f2eb 100644
--- a/src/configs/types.ts
+++ b/src/configs/types.ts
@@ -17,11 +17,14 @@ export type XAuthorization = string;
//export type UserId = string;
//export type WebSocketSessionId = string;
+export type EventId = string;
+
export const accountNameOptions = {
minLength: 4,
maxLength: 24,
isAllowed: (text: string): boolean => {
- return text.match('^[a-zA-Z0-9_.]+$') !== null;
+ // allows usernames that start and end with a lowercase letter or digit, with optional dots or underscores in the middle, and it is case-insensitive
+ return text.match(/^[a-z0-9](?:[._]*[a-z0-9])*$/i) !== null;
},
};
@@ -38,7 +41,17 @@ export const passwordOptions = {
maxLength: 64,
minBits: 50,
isAllowed: (text: string): boolean => {
- return /\W/.test(text) && /[a-zA-Z]/.test(text) && /[a-zA-Z]/.test(text);
+ // return /\W/.test(text) && /[a-zA-Z]/.test(text) && /[a-zA-Z]/.test(text);
+
+ /*
+ Contains at least one uppercase letter
+ Contains at least one lowercase letter
+ Contains at least one digit (number)
+ Contains at least one special character
+ */
+ return /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+={}\[\]|;:'",.<>\/?]).*$/.test(
+ text,
+ );
},
};
diff --git a/src/event/EventManager.ts b/src/event/EventManager.ts
new file mode 100644
index 0000000..c5c524a
--- /dev/null
+++ b/src/event/EventManager.ts
@@ -0,0 +1,331 @@
+import {maxCachedEvents} from '@configs/appNonSaveVar';
+import {appNonSaveVarActions} from '@configs/appNonSaveVarReducer';
+import {AccountName, XAuthorization} from '@configs/types';
+import {makeRequest, apiBackendRequest} from '@helper/request';
+import BigDataManager from '@helper/storage/BigDataManager';
+import {RootState, store} from '@redux/store';
+import {useSelector} from 'react-redux';
+import {
+ BasicEventProp,
+ createEventProp,
+ EventBannerPicture,
+ EventBannerPictureType,
+ EventID,
+ EventType,
+ NumToEventType,
+ PAEvent,
+} from './types';
+
+import {SourceProp} from '@user/types';
+
+let cachedEventList: EventID[] = [];
+
+async function getEvent(
+ UUID: EventID,
+ save?: boolean,
+): Promise {
+ if (UUID === 'none') {
+ return undefined;
+ }
+
+ let event: PAEvent | undefined;
+
+ let state = store.getState();
+ let eventIsInCache = false;
+
+ {
+ const eve = state.nonSaveVariables.cachedEvents[UUID];
+ if (eve !== undefined) {
+ event = eve;
+ eventIsInCache = true;
+ }
+ }
+
+
+ if (event === undefined) {
+ const eveDBKeys = BigDataManager.databases.events.keys;
+ const eve = await BigDataManager.databases.events.getEntry(UUID);
+
+ if (eve !== undefined && eve !== null) {
+ let EventBannerPicture: EventBannerPicture = {
+ lq:
+ eve[eveDBKeys.EventBannerPictureBinaryLQ].byteLength !== 0
+ ? createEventProp(
+ SourceProp.offline,
+ //Buffer.from(eve[eveDBKeys.EventBannerPictureBinaryLQ]),
+ new Blob([eve[eveDBKeys.EventBannerPictureBinaryLQ] as unknown as string]),
+ )
+ : createEventProp(
+ SourceProp.online,
+ undefined,
+ eve[eveDBKeys.EventBannerPicture],
+ ),
+ hq:
+ eve[eveDBKeys.EventBannerPictureBinaryHQ].byteLength !== 0
+ ? createEventProp(
+ SourceProp.offline,
+ //Buffer.from(eve[eveDBKeys.EventBannerPictureBinaryHQ]),
+ new Blob([eve[eveDBKeys.EventBannerPictureBinaryHQ] as unknown as string]),
+ )
+ : createEventProp(
+ SourceProp.online,
+ undefined,
+ eve[eveDBKeys.EventBannerPicture],
+ ),
+ };
+
+ event = {
+ UUID,
+ Name: createEventProp(SourceProp.offline, eve[eveDBKeys.Name]),
+ Description: createEventProp(
+ SourceProp.offline,
+ eve[eveDBKeys.Description],
+ ),
+ StartDate: createEventProp(
+ SourceProp.offline,
+ eve[eveDBKeys.StartDate],
+ ),
+ EndDate: createEventProp(SourceProp.offline, eve[eveDBKeys.EndDate]),
+ Latitude: createEventProp(SourceProp.offline, eve[eveDBKeys.Latitude]),
+ Longitude: createEventProp(
+ SourceProp.offline,
+ eve[eveDBKeys.Longitude],
+ ),
+ Type: createEventProp(SourceProp.offline, NumToEventType(eve[eveDBKeys.Type])),
+ Theme: createEventProp(SourceProp.offline, eve[eveDBKeys.Theme]),
+ FriendList: createEventProp(
+ SourceProp.offline,
+ eve[eveDBKeys.FriendList],
+ ),
+ UserLength: createEventProp(
+ SourceProp.offline,
+ eve[eveDBKeys.UserLength],
+ ),
+ ButtonAction: createEventProp(
+ SourceProp.offline,
+ eve[eveDBKeys.ButtonAction],
+ ),
+ lastUpdateTimestamp: eve[eveDBKeys.lastUpdateTimestamp],
+ EventBannerPicture,
+ };
+ }
+ }
+
+ if (event === undefined) {
+ try {
+ /*const resp = await makeRequest({
+ path: apiBackendRequest.GET_USER_PROFILE,
+ requestGET: {':UUID': UUID},
+ response: {
+ Description: '',
+ FollowersCount: 0,
+ FollowingCount: 0,
+ Eventname: '',
+ XpLevel: 0,
+ XpPoints: 0,
+ AvatarUrl: '',
+ },
+ });*/
+
+ /* Name: BasicEventProp;
+ Description: BasicEventProp;
+ StartDate: BasicEventProp;
+ EndDate: BasicEventProp;
+ Latitude: BasicEventProp;
+ Longitude: BasicEventProp;
+ Type: BasicEventProp;
+ Theme: BasicEventProp;
+ lastUpdateTimestamp: timestamp;
+ FriendList: BasicEventProp;
+ UserLength: BasicEventProp;
+ EventBannerPicture: EventBannerPicture;
+ ButtonAction: BasicEventProp; */
+
+ const resp = {response:{
+ Name: 'test event',
+ Description: 'test description',
+ StartDate: 1702230005,
+ EndDate: new Date().getTime() + 100000,
+ Latitude: 48.7758,
+ Longitude: 9.1829,
+ Type: 0,
+ Theme: 0,
+ FriendList: [],
+ UserLength: 5,
+ pic: 'https://www.w3schools.com/w3css/img_lights.jpg',
+ ButtonAction: '{"url":"https://www.w3schools.com/w3css/img_lights.jpg"}',
+ }};
+
+ event = {
+ UUID,
+ Description: createEventProp(
+ SourceProp.cached,
+ resp.response.Description,
+ ),
+ lastUpdateTimestamp: Math.floor(new Date().getTime() / 1000),
+ EventBannerPicture: {
+ lq: createEventProp(
+ SourceProp.online,
+ undefined,
+ resp.response.pic + "?type=low",
+ ),
+ hq: createEventProp(
+ SourceProp.online,
+ undefined,
+ resp.response.pic,
+ ),
+ },
+ Name: createEventProp(
+ SourceProp.cached,
+ resp.response.Name,
+ ),
+ StartDate: createEventProp(
+ SourceProp.cached,
+ resp.response.StartDate,
+ ),
+ EndDate: createEventProp(
+ SourceProp.cached,
+ resp.response.EndDate,
+ ),
+ Latitude: createEventProp(
+ SourceProp.cached,
+ resp.response.Latitude,
+ ),
+ Longitude: createEventProp(
+ SourceProp.cached,
+ resp.response.Longitude,
+ ),
+ Type: createEventProp(
+ SourceProp.cached,
+ NumToEventType(resp.response.Type),
+ ),
+ Theme: createEventProp(
+ SourceProp.cached,
+ resp.response.Theme,
+ ),
+ FriendList: createEventProp(
+ SourceProp.cached,
+ resp.response.FriendList,
+ ),
+ UserLength: createEventProp(
+ SourceProp.cached,
+ resp.response.UserLength,
+ ),
+ ButtonAction: createEventProp(
+ SourceProp.cached,
+ resp.response.ButtonAction,
+ ),
+
+ };
+
+ //BigDataManager.setEntry('events', event);
+ } catch (error: any) {
+ console.error(error.status);
+ }
+ }
+
+ if (eventIsInCache === false && event !== undefined) {
+ console.log('save in cache');
+ store.dispatch(appNonSaveVarActions.setCachedEvent(event));
+ cachedEventList.push(event.UUID);
+
+ if (cachedEventList.length > maxCachedEvents) {
+ let eveId = cachedEventList[0];
+ cachedEventList.shift();
+ console.log('eveId', eveId);
+
+ store.dispatch(appNonSaveVarActions.removeCachedEvent(eveId));
+ }
+ }
+
+ return event;
+}
+
+enum GetParam {
+ CACHE = 0,
+ SAVE,
+}
+
+let getEventList: {[key: AccountName]: GetParam} = {};
+
+async function refreshEvents() {
+ for (let UUID in getEventList) {
+ const param = getEventList[UUID];
+ delete getEventList[UUID];
+
+ await getEvent(UUID);
+ }
+}
+
+setInterval(refreshEvents, 500);
+
+function addEventToGetQueue(UUID: EventID, param: GetParam) {
+ if (getEventList[UUID] === undefined) {
+ getEventList[UUID] = param;
+ } else if (getEventList[UUID] < param) {
+ getEventList[UUID] = param;
+ }
+}
+
+function getEventSelector(UUID: EventID) {
+ addEventToGetQueue(UUID, GetParam.CACHE);
+
+ const myEvent = useSelector(
+ (state: RootState) => state.nonSaveVariables.cachedEvents[UUID],
+ );
+
+ if (myEvent === undefined) {
+ return initUndefinedEvent(UUID);
+ }
+
+ return myEvent;
+}
+
+function getEventSelectorPicture(UUID: EventID): EventBannerPicture {
+ addEventToGetQueue(UUID, GetParam.CACHE);
+
+ const myEvent = useSelector(
+ (state: RootState) =>
+ state.nonSaveVariables.cachedEvents[UUID]?.EventBannerPicture,
+ );
+
+ if (myEvent === undefined) {
+ return {
+ lq: createEventProp(SourceProp.online),
+ hq: createEventProp(SourceProp.online),
+ };
+ }
+
+ return myEvent;
+}
+
+function initUndefinedEvent(UUID: EventID): PAEvent {
+ return {
+ UUID,
+ Name: createEventProp(SourceProp.online),
+ Description: createEventProp(SourceProp.online),
+ StartDate: createEventProp(SourceProp.online),
+ EndDate: createEventProp(SourceProp.online),
+ Latitude: createEventProp(SourceProp.online),
+ Longitude: createEventProp(SourceProp.online),
+ Type: createEventProp(SourceProp.online),
+ Theme: createEventProp(SourceProp.online),
+ lastUpdateTimestamp: 0,
+ FriendList: createEventProp(SourceProp.online),
+ UserLength: createEventProp(SourceProp.online),
+ EventBannerPicture: {
+ lq: createEventProp(SourceProp.online),
+ hq: createEventProp(SourceProp.online),
+ },
+ ButtonAction: createEventProp(SourceProp.online),
+ };
+ }
+
+
+const EventManager = {
+ getEvent,
+ getEventSelector,
+ getEventSelectorPicture,
+ initUndefinedEvent,
+};
+export default EventManager;
diff --git a/src/event/types.ts b/src/event/types.ts
new file mode 100644
index 0000000..35cfa87
--- /dev/null
+++ b/src/event/types.ts
@@ -0,0 +1,71 @@
+import {SourceProp} from '@user/types';
+
+import {EventId, timestamp} from '@configs/types';
+
+export type EventID = string;
+export type EventType = 'event' | 'eventStore';
+
+export enum EventThemes {
+ default = 0,
+ small = 1,
+}
+
+export interface BasicEventProp {
+ source: SourceProp;
+ url?: string;
+ data?: T1;
+}
+
+export function createEventProp(
+ source: SourceProp,
+ data?: T1,
+ url?: string,
+): BasicEventProp {
+ return {source, data, url};
+}
+
+export type EventBannerPictureType = BasicEventProp;
+
+export interface EventBannerPicture {
+ lq: EventBannerPictureType; //low quality
+ hq?: EventBannerPictureType; //high quality
+}
+
+export interface PAEvent {
+ UUID: EventId;
+ Name: BasicEventProp;
+ Description: BasicEventProp;
+ StartDate: BasicEventProp;
+ EndDate: BasicEventProp;
+ Latitude: BasicEventProp;
+ Longitude: BasicEventProp;
+ Type: BasicEventProp;
+ Theme: BasicEventProp;
+ lastUpdateTimestamp: timestamp;
+ FriendList: BasicEventProp;
+ UserLength: BasicEventProp;
+ EventBannerPicture: EventBannerPicture;
+ ButtonAction: BasicEventProp;
+}
+
+export function NumToEventType(num: number): EventType {
+ switch (num) {
+ case 0:
+ return 'event';
+ case 1:
+ return 'eventStore';
+ default:
+ return 'event';
+ }
+}
+
+export function EventTypeToNum(type: EventType): number {
+ switch (type) {
+ case 'event':
+ return 0;
+ case 'eventStore':
+ return 1;
+ default:
+ return 0;
+ }
+}
\ No newline at end of file
diff --git a/src/helper/request.ts b/src/helper/request.ts
index 4ad93bc..4709059 100644
--- a/src/helper/request.ts
+++ b/src/helper/request.ts
@@ -23,6 +23,7 @@ export enum apiBackendRequest {
GET_USER_PROFILE = '/users/:accountName',
LOGOUT = '/user/logout',
SIGN_UP = '/user/signup',
+ CHECK_ACCOUNT_NAME = '/user/check/:accountName',
/*REGISTER_STEP_1 = '/admin/users/email',
REGISTER_RESEND_MAIL = '/admin/users/email/resend',
REGISTER_STEP_2 = '/verify/email/:xToken/:verifyId',
@@ -147,6 +148,15 @@ interface GET_USER_PROFILE extends defaultRequest {
};
}
+interface CHECK_ACCOUNT_NAME extends defaultRequest {
+ path: apiBackendRequest.CHECK_ACCOUNT_NAME;
+
+ requestGET: {
+ ':accountName': AccountName;
+ };
+ response: {};
+}
+
interface APP_START extends defaultRequest {
path: apiBackendRequest.APP_START;
@@ -180,7 +190,8 @@ type FetchTypes =
| LOGIN*/
| GET_USER_PROFILE
| APP_START
- | LOGOUT;
+ | LOGOUT
+ | CHECK_ACCOUNT_NAME;
/*
function isA(obj: any): obj is REGISTER_STEP_1 {
return obj.request !== undefined;
diff --git a/src/helper/storage/bdm/migration.ts b/src/helper/storage/bdm/migration.ts
index f0c9f13..235b634 100644
--- a/src/helper/storage/bdm/migration.ts
+++ b/src/helper/storage/bdm/migration.ts
@@ -40,6 +40,10 @@ export const DBMigration: {[key in databaseNames]: any} = {
chatRoomInfos: (Schema: typeof DBSchemas.chatRoomInfos) => {
const callback: MigrationCallback = (oldRealm, newRealm) => {};
+ return callback;
+ },events: (Schema: typeof DBSchemas.events) => {
+ const callback: MigrationCallback = (oldRealm, newRealm) => {};
+
return callback;
},
};
diff --git a/src/helper/storage/bdm/schemas.ts b/src/helper/storage/bdm/schemas.ts
index d836b2d..9736c4c 100644
--- a/src/helper/storage/bdm/schemas.ts
+++ b/src/helper/storage/bdm/schemas.ts
@@ -1,8 +1,9 @@
import chat from './schemas/chat';
import users from './schemas/users';
import chatRoomInfos from './schemas/chatRoomInfos';
+import events from './schemas/events';
-const DBSchemas = {users, chat, chatRoomInfos};
+const DBSchemas = {users, chat, chatRoomInfos, events};
export const SkipDBSchemas = [chat.details.name];
export type databaseConfType = typeof DBSchemas[keyof typeof DBSchemas];
diff --git a/src/helper/storage/bdm/schemas/events.ts b/src/helper/storage/bdm/schemas/events.ts
new file mode 100644
index 0000000..da8da44
--- /dev/null
+++ b/src/helper/storage/bdm/schemas/events.ts
@@ -0,0 +1,103 @@
+import {filterParam, getAllEntries, getEntry} from '../get';
+import {DBMigration} from '../migration';
+import {setEntry} from '../set';
+
+import {databaseConf, possibleDBKeys} from '../types';
+
+enum keys {
+ UUID = 'a',
+ Name = 'b',
+ Description = 'c',
+ StartDate = 'd',
+ EndDate = 'e',
+ Latitude = 'f',
+ Longitude = 'g',
+ Type = 'h',
+ Theme = 'i',
+ lastUpdateTimestamp = 'j',
+ FriendList = 'k',
+ UserLength = 'l',
+ EventBannerPicture = 'm', //URL
+ EventBannerPictureBinaryLQ = 'n',
+ EventBannerPictureBinaryHQ = 'o',
+ ButtonAction = 'p',
+}
+
+const name = 'events';
+const primaryKey: keyof typeof propsDefault = keys.UUID;
+
+const propsType: {[key in keyof typeof propsDefault]: string} = {
+ [keys.UUID]: 'string',
+ [keys.Name]: 'string',
+ [keys.Description]: 'string',
+ [keys.StartDate]: 'int',
+ [keys.EndDate]: 'int',
+ [keys.Latitude]: 'double',
+ [keys.Longitude]: 'double',
+ [keys.Type]: 'int',
+ [keys.Theme]: 'int',
+ [keys.lastUpdateTimestamp]: 'int',
+ [keys.FriendList]: 'string[]',
+ [keys.UserLength]: 'int',
+ [keys.EventBannerPicture]: 'string', //URL
+ [keys.EventBannerPictureBinaryLQ]: 'data',
+ [keys.EventBannerPictureBinaryHQ]: 'data',
+ [keys.ButtonAction]: 'string',
+};
+
+const propsDefault = {
+ [keys.UUID]: '',
+ [keys.Name]: '',
+ [keys.Description]: '',
+ [keys.StartDate]: 0,
+ [keys.EndDate]: 0,
+ [keys.Latitude]: 0,
+ [keys.Longitude]: 0,
+ [keys.Type]: 0,
+ [keys.Theme]: 0,
+ [keys.lastUpdateTimestamp]: 0,
+ [keys.FriendList]: ['none'],
+ [keys.UserLength]: 0,
+ [keys.EventBannerPicture]: '', //URL
+ [keys.EventBannerPictureBinaryLQ]: new ArrayBuffer(0),
+ [keys.EventBannerPictureBinaryHQ]: new ArrayBuffer(0),
+ [keys.ButtonAction]: '',
+};
+
+const thisSchema: databaseConf = {
+ filePath: name,
+ version: 1,
+ keys,
+ migration: () => {
+ return DBMigration[name](thisSchema);
+ },
+ setEntry: (val: typeof thisSchema.defaultProps, suffix?: string) => {
+ return setEntry(
+ thisSchema,
+ val,
+ suffix,
+ );
+ },
+ getEntry: (key: possibleDBKeys, suffix?: string) => {
+ return getEntry(
+ thisSchema,
+ key,
+ suffix,
+ );
+ },
+ getAllEntries: (filter?: filterParam, suffix?: string) => {
+ return getAllEntries(
+ thisSchema,
+ filter,
+ suffix,
+ );
+ },
+ defaultProps: propsDefault,
+ details: {
+ name,
+ properties: propsType,
+ primaryKey,
+ },
+};
+
+export default thisSchema;
diff --git a/src/helper/storage/bdm/types.ts b/src/helper/storage/bdm/types.ts
index 766d1ec..b71d848 100644
--- a/src/helper/storage/bdm/types.ts
+++ b/src/helper/storage/bdm/types.ts
@@ -1,7 +1,7 @@
import MyUserManager from '@user/MyUserManager';
import {filterParam} from './get';
-export type databaseNames = 'users' | 'chat' | 'chatRoomInfos';
+export type databaseNames = 'users' | 'chat' | 'chatRoomInfos' | 'events';
export type possibleDBKeys = string;
export interface databaseConf {
@@ -31,10 +31,10 @@ export interface databaseNameSuffix {
export function mergeDBName(nameObj: databaseNameSuffix, web?: 'web'): string {
if (web === 'web') {
return nameObj.suffix === undefined
- ? nameObj.name + '-' + MyUserManager.getSelectedUserId()
+ ? nameObj.name + '-' + MyUserManager.getSelectedUserAccount()
: nameObj.name +
'-' +
- MyUserManager.getSelectedUserId() +
+ MyUserManager.getSelectedUserAccount() +
('_' + nameObj.suffix);
}
diff --git a/src/lang/default.ts b/src/lang/default.ts
index 508657d..28261bf 100644
--- a/src/lang/default.ts
+++ b/src/lang/default.ts
@@ -65,6 +65,7 @@ export default interface LangFormat {
title: string;
description: string;
inputUsername: string;
+ error: string;
};
signUpStepPhoneNumber: {
title: string;
@@ -79,12 +80,17 @@ export default interface LangFormat {
title: string;
description: string;
inputPassword: string;
+ errorLength: string;
+ errorPasswordInvalid: string;
};
signUpStepAccountName: {
title: string;
description: string;
inputAccountName: string;
buttonGetStarted: string;
+ signUpError: {[key: number]: string};
+ errorLength: string;
+ errorAccountNameInvalid: string;
};
};
profile: {
diff --git a/src/lang/en.ts b/src/lang/en.ts
index 61130fd..ad2d530 100644
--- a/src/lang/en.ts
+++ b/src/lang/en.ts
@@ -65,6 +65,7 @@ export const lang: LangFormat = {
title: "Let's get started, what's your name?",
description: 'The name will be displayed on your profil overview',
inputUsername: 'Username',
+ error: 'At least ${minLength} characters are required',
},
signUpStepPhoneNumber: {
title: 'Create your account using your phone number',
@@ -77,8 +78,11 @@ export const lang: LangFormat = {
},
signUpStepPassword: {
title: "You'll need a password",
- description: 'Make sure it’s 8 characters or more.',
+ description: 'Make sure it’s ${minLength} characters or more.',
inputPassword: 'PASSWORD',
+ errorLength: 'Password must be at least ${minLength} characters long',
+ errorPasswordInvalid:
+ 'Must include at least on uppercase letter, one lowercase letter, one number and one special character',
},
signUpStepAccountName: {
title: 'Next, create your account name',
@@ -86,6 +90,15 @@ export const lang: LangFormat = {
'Your account name is unique and is used for friends to find you.',
inputAccountName: 'ACCOUNT NAME',
buttonGetStarted: 'Get Started',
+ errorLength: 'Account name must be at least ${minLength} characters long',
+ errorAccountNameInvalid:
+ 'Account name can only contain \n20a-z, 0-9, underscores and dots',
+ signUpError: {
+ 400: 'Invalid account name',
+ 401: 'Invalid credentials',
+ 500: 'Server error',
+ 502: 'Server not reachable',
+ },
},
},
profile: {
@@ -119,13 +132,13 @@ export const lang: LangFormat = {
changeUsername: {
username: 'USERNAME',
info: 'You can use a-z, 0-9 and underscores.',
- info2: 'Minimum length is 3 characters.',
+ info2: 'Minimum length is ${minLength} characters.',
},
changePassword: {
currentPassword: 'CURRENT PASSWORD',
newPassword: 'NEW PASSWORD',
repeatNewPassword: 'REPEAT NEW PASSWORD',
- info: 'Make sure it’s 8 characters or more.',
+ info: 'Make sure it’s ${minLength} characters or more.',
info2: 'You will be logged out after changing your password.',
},
help: {
diff --git a/src/navigation/tabs/main/MapTab.tsx b/src/navigation/tabs/main/MapTab.tsx
index 843379e..a54715c 100644
--- a/src/navigation/tabs/main/MapTab.tsx
+++ b/src/navigation/tabs/main/MapTab.tsx
@@ -11,6 +11,7 @@ import {useSelector} from 'react-redux';
import {Map} from '@pages/map/map';
import {EventID} from '@components/map/types';
+import EventPage from '@pages/event/EventPage';
export const MapTabName = 'Map';
@@ -47,13 +48,20 @@ function MapTab() {
/>
);
diff --git a/src/pages/event/EventPage.tsx b/src/pages/event/EventPage.tsx
new file mode 100644
index 0000000..ec0eaa1
--- /dev/null
+++ b/src/pages/event/EventPage.tsx
@@ -0,0 +1,7 @@
+import {Text} from '@gluestack-ui/themed';
+
+function EventPage() {
+ return EventPage;
+}
+
+export default EventPage;
diff --git a/src/pages/map/map.tsx b/src/pages/map/map.tsx
index 299a16a..d5d5547 100644
--- a/src/pages/map/map.tsx
+++ b/src/pages/map/map.tsx
@@ -5,7 +5,8 @@ import React, {useState} from 'react'; // Add useState import
import DisplayMarkerList from '@components/map/DisplayMarkerList';
-import getLocationData, {PA_Point} from '@components/map/cluster/getData';
+import getLocationData from '@components/map/cluster/getData';
+import {PA_Point} from '@components/map/types';
import {store} from '@redux/store';
import {appNonSaveVarActions} from '@configs/appNonSaveVarReducer';
import {Position} from '@rnmapbox/maps/src/types/Position';
diff --git a/src/pages/welcome/login/login.tsx b/src/pages/welcome/login/login.tsx
index 53439db..d1e3ab9 100644
--- a/src/pages/welcome/login/login.tsx
+++ b/src/pages/welcome/login/login.tsx
@@ -9,7 +9,7 @@ import {
navigateToHome,
} from '@navigation/registration/registration';
import {useNavigation} from '@react-navigation/native';
-import {RootState} from '@redux/store';
+import {RootState, store} from '@redux/store';
import MyUserManager from '@user/MyUserManager';
import {useState} from 'react';
import {View} from 'react-native';
@@ -60,8 +60,6 @@ export function Login() {
text={lang.buttonLogin}
style={{marginBottom: 20}}
onPress={() => {
- console.log('login');
-
setIsLoading(true);
makeRequest({
@@ -95,13 +93,18 @@ export function Login() {
console.log('reason', reason);
setIsLoading(false);
+ let text =
+ store.getState().appVariables.lang.registration
+ .signUpStepAccountName.signUpError[
+ reason.status as number
+ ];
+
showToast(toast, {
title: 'Failed',
variant: 'solid',
action: 'error',
- description: undefined,
+ description: text,
isClosable: true,
- rest: {colorScheme: 'primary'},
});
});
}}
diff --git a/src/pages/welcome/signUp/signUp.tsx b/src/pages/welcome/signUp/signUp.tsx
index 8aeefb8..5b4f455 100644
--- a/src/pages/welcome/signUp/signUp.tsx
+++ b/src/pages/welcome/signUp/signUp.tsx
@@ -1,8 +1,16 @@
-import {MyButton} from '@components/MyButton';
-import {MyIconInput} from '@components/MyInput';
+import {MyButton, MyIconButton} from '@components/MyButton';
+import {MyIcon} from '@components/MyIcon';
+import {MyIconInput, MyInputError} from '@components/MyInput';
import {MyScreenContainer} from '@components/MyScreenContainer';
import {MyTitle} from '@components/MyTitle';
+import showToast from '@components/MyToast';
import {appVarActions} from '@configs/appVarReducer';
+import {
+ accountNameOptions,
+ passwordOptions,
+ userNameOptions,
+} from '@configs/types';
+import {Spinner, set, useToast} from '@gluestack-ui/themed';
import {ToBase64} from '@helper/base64';
import {apiBackendRequest, makeRequest} from '@helper/request';
import {RootScreenNavigationProp} from '@navigation/navigation';
@@ -13,10 +21,11 @@ import {
import {useNavigation} from '@react-navigation/native';
import {RootState, store} from '@redux/store';
import MyUserManager from '@user/MyUserManager';
-import {useState} from 'react';
+import {useEffect, useState} from 'react';
import {Text} from 'react-native';
import {View} from 'react-native';
import {useSelector} from 'react-redux';
+import reactStringReplace from 'react-string-replace';
function Title({text, description}: {text: string; description?: string}) {
return (
@@ -38,6 +47,17 @@ export function SignUpStepUsername() {
);
const [username, setUsername] = useState('');
+ const [inputTouched, setInputTouched] = useState(false);
+
+ const usernameValid = username.length < userNameOptions.minLength;
+
+ const errorText = reactStringReplace(
+ lang.signUpStepUsername.error,
+ '${minLength}',
+ (match, i) => {
+ return userNameOptions.minLength.toString();
+ },
+ );
return (
setUsername(text)}
+ onChangeText={text => {
+ setUsername(text);
+ setInputTouched(true);
+ }}
+ maxLength={userNameOptions.maxLength}
+ helper={
+ inputTouched &&
+ usernameValid &&
+ }
/>
{
let rp = {...registerProcess};
@@ -173,6 +202,49 @@ export function SignUpStepPassword() {
);
const [password, setPassword] = useState('');
+ const [inputTouched, setInputTouched] = useState(false);
+
+ const [isPasswordVisible, setIsPasswordVisible] = useState(false);
+
+ const togglePasswordVisibility = () => {
+ setIsPasswordVisible(!isPasswordVisible);
+ };
+
+ const passwordValid = () => {
+ if (password.length < passwordOptions.minLength) {
+ return false;
+ } else if (!passwordOptions.isAllowed(password)) {
+ return false;
+ } else {
+ return true;
+ }
+ };
+
+ const descriptionText = reactStringReplace(
+ lang.signUpStepPassword.description,
+ '${minLength}',
+ () => {
+ return passwordOptions.minLength.toString();
+ },
+ );
+
+ const errorLengthText = reactStringReplace(
+ lang.signUpStepPassword.errorLength,
+ '${minLength}',
+ () => {
+ return passwordOptions.minLength.toString();
+ },
+ );
+
+ const errorText = () => {
+ if (password.length < passwordOptions.minLength) {
+ return errorLengthText.join('');
+ } else if (!passwordOptions.isAllowed(password)) {
+ return lang.signUpStepPassword.errorPasswordInvalid;
+ } else {
+ return '';
+ }
+ };
return (
setPassword(text)}
+ maxLength={passwordOptions.maxLength}
+ onChangeText={text => {
+ setPassword(text);
+ setInputTouched(true);
+ }}
+ rightComponent={
+
+ }
+ helper={
+ inputTouched &&
+ !passwordValid() &&
+ }
/>
{
let rp = {...registerProcess};
@@ -216,11 +306,22 @@ export function SignUpStepPassword() {
);
}
+enum AccountNameAvailable {
+ Loading,
+ Available,
+ NotAvailable,
+}
+
export function SignUpStepAccountName() {
const lang = useSelector(
(state: RootState) => state.appVariables.lang.registration,
);
+ const currentTheme = useSelector(
+ (state: RootState) => state.nonSaveVariables.theme.colors,
+ );
+
const navigation = useNavigation();
+ const toast = useToast();
const registerProcess = useSelector(
(state: RootState) => state.appVariables.preferences.RegisterProcess,
@@ -228,6 +329,72 @@ export function SignUpStepAccountName() {
const [isLoading, setIsLoading] = useState(false);
const [accountName, setAccountName] = useState('');
+ const [isAccountNameAvailable, setIsAccountNameAvailable] = useState(
+ AccountNameAvailable.Loading,
+ );
+ const [inputTouched, setInputTouched] = useState(false);
+
+ const accountNameValid = () => {
+ if (accountName.length < accountNameOptions.minLength) {
+ return false;
+ } else if (!accountNameOptions.isAllowed(accountName)) {
+ return false;
+ } else {
+ return true;
+ }
+ };
+
+ const errorText = () => {
+ if (accountName.length < accountNameOptions.minLength) {
+ return reactStringReplace(
+ lang.signUpStepAccountName.errorLength,
+ '${minLength}',
+ () => {
+ return accountNameOptions.minLength.toString();
+ },
+ ).join('');
+ } else if (!accountNameOptions.isAllowed(accountName)) {
+ return lang.signUpStepAccountName.errorAccountNameInvalid;
+ } else {
+ return '';
+ }
+ };
+
+ const rightComponent = () => {
+ const closeIcon = (
+
+ );
+
+ if (!accountNameValid()) {
+ return closeIcon;
+ } else if (isAccountNameAvailable === AccountNameAvailable.Loading) {
+ return ;
+ } else if (isAccountNameAvailable === AccountNameAvailable.Available) {
+ return ;
+ } else {
+ return closeIcon;
+ }
+ };
+
+ useEffect(() => {
+ if (!accountNameValid()) return;
+
+ const delay = 400;
+ const timeoutId = setTimeout(() => {
+ makeRequest({
+ path: apiBackendRequest.CHECK_ACCOUNT_NAME,
+ requestGET: {':accountName': accountName},
+ response: {},
+ })
+ .then(() => setIsAccountNameAvailable(AccountNameAvailable.Available))
+ .catch(() =>
+ setIsAccountNameAvailable(AccountNameAvailable.NotAvailable),
+ );
+ }, delay);
+
+ // Cleanup the timeout on component unmount or when inputValue changes
+ return () => clearTimeout(timeoutId);
+ }, [accountName]);
return (
setAccountName(text)}
+ onChangeText={text => {
+ setAccountName(text);
+ setInputTouched(true);
+ setIsAccountNameAvailable(AccountNameAvailable.Loading);
+ }}
+ maxLength={accountNameOptions.maxLength}
+ rightComponent={rightComponent()}
+ helper={
+ inputTouched &&
+ !accountNameValid() &&
+ }
/>
{
+ setIsLoading(true);
+
let rp = {...registerProcess};
rp.AccountName = accountName;
store.dispatch(appVarActions.setRegisterProcess(rp));
- console.log('registerProcess', rp);
-
makeRequest({
path: apiBackendRequest.SIGN_UP,
request: rp,
@@ -273,8 +454,6 @@ export function SignUpStepAccountName() {
},
})
.then(resp => {
- console.log('response', resp);
-
MyUserManager.createNewMyUser(
accountName,
resp.response.Username,
@@ -288,8 +467,27 @@ export function SignUpStepAccountName() {
console.log('catch', err);
});
})
- .catch(error => {
- console.log('error', error);
+ .catch(reason => {
+ console.log('error', reason);
+
+ setIsLoading(false);
+
+ let text =
+ store.getState().appVariables.lang.registration
+ .signUpStepAccountName.signUpError[
+ reason.status as number
+ ];
+
+ console.log('text', text, reason.status);
+
+ showToast(toast, {
+ title: 'Failed',
+ variant: 'solid',
+ action: 'error',
+ description: text,
+ isClosable: true,
+ //rest: {colorScheme: 'primary'},
+ });
});
}}
/>
diff --git a/src/user/UserManager.ts b/src/user/UserManager.ts
index 6b8404c..8d3c576 100644
--- a/src/user/UserManager.ts
+++ b/src/user/UserManager.ts
@@ -65,7 +65,8 @@ async function getUser(
usr[usrDBKeys.ProfilePictureBinaryLQ].byteLength !== 0
? createUserProp(
SourceProp.offline,
- new Blob([usr[usrDBKeys.ProfilePictureBinaryLQ]]),
+ Buffer.from(usr[usrDBKeys.ProfilePictureBinaryLQ]),
+ //new Blob([usr[usrDBKeys.ProfilePictureBinaryLQ]]),
)
: createUserProp(
SourceProp.online,
@@ -76,7 +77,8 @@ async function getUser(
usr[usrDBKeys.ProfilePictureBinaryHQ].byteLength !== 0
? createUserProp(
SourceProp.offline,
- new Blob([usr[usrDBKeys.ProfilePictureBinaryHQ]]),
+ Buffer.from(usr[usrDBKeys.ProfilePictureBinaryHQ]),
+ //new Blob([usr[usrDBKeys.ProfilePictureBinaryHQ]]),
)
: createUserProp(
SourceProp.online,
diff --git a/tsconfig.json b/tsconfig.json
index d687fbd..faca364 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -5,6 +5,11 @@
// ... other configs, if any
"baseUrl": ".",
"target": "ESNext",
+ "allowSyntheticDefaultImports": true,
+ "allowJs": true,
+ "moduleResolution": "node",
+ "jsx": "react-native",
+ "strict": true,
"paths": {
"@redux/*": ["src/redux/*"],
"@lang/*": ["src/lang/*"],
@@ -18,7 +23,8 @@
"@navigation/*": ["src/navigation/*"],
"@configs/*": ["src/configs/*"],
"@helper/*": ["src/helper/*"],
- "@user/*": ["src/user/*"]
+ "@user/*": ["src/user/*"],
+ "@event/*": ["src/event/*"]
}
}
}