finshed sign up

master
Netcup Gituser 2023-12-10 23:18:46 +01:00
parent aebc1e4b79
commit 51b02065ee
27 changed files with 983 additions and 79 deletions

View File

@ -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',
},
},
],

9
package-lock.json generated
View File

@ -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",

View File

@ -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"
},

View File

@ -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 (
<NavigationContainer theme={navigationTheme}>
<GluestackUIProvider config={configDarkTheme}>
<GluestackUIProvider config={themeConfig}>
<MainComponent />
</GluestackUIProvider>
</NavigationContainer>

View File

@ -120,6 +120,7 @@ export function MyButton({
alignItems: 'center',
padding: 10,
borderRadius: 10,
opacity: disabled ? currentTheme.opacity[60] : 1,
}}>
<ButtonText />
</View>

View File

@ -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 (
<View
style={[
{
flexDirection: 'row',
alignItems: 'center',
},
!disableContainer && {
backgroundColor: currentTheme.backgroundDark300,
borderRadius: 10,
},
]}>
<View style={{marginLeft: 12}}>
<MyIcon name={iconName} size={30} />
<View
style={{
flexDirection: 'row',
alignItems: 'center',
}}>
<View style={{marginLeft: 12}}>
<MyIcon name={iconName} size={30} />
</View>
<View style={{flex: 1, margin: 12, gap: 2}}>
<Text size="sm" color={currentTheme.textLight200}>
{text}
</Text>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
backgroundColor: currentTheme.backgroundDark400,
borderRadius: 10,
}}>
<TextInput
style={{
flex: 1,
height: 40,
paddingLeft: 10,
paddingRight: 10,
}}
keyboardType={keyboardType}
secureTextEntry={secureTextEntry}
value={value}
onChangeText={onChangeText}
maxLength={maxLength}
/>
{rightComponent && (
<View style={{paddingRight: 10}}>{rightComponent}</View>
)}
</View>
</View>
</View>
<View style={{flex: 1, margin: 12, gap: 2}}>
<Text size='sm' color={currentTheme.textLight200}>{text}</Text>
<TextInput
style={{
backgroundColor: currentTheme.backgroundDark400,
height: 40,
borderRadius: 10,
paddingLeft: 10,
}}
keyboardType={keyboardType}
secureTextEntry={secureTextEntry}
value={value}
onChangeText={onChangeText}
/>
</View>
<View style={{paddingLeft: 10, paddingRight: 20}}>{helper}</View>
</View>
);
}
export interface MyInputErrorProps {
iconName?: string;
text: string;
}
export function MyInputError({
iconName,
text,
}: MyInputErrorProps): React.ReactElement<MyInputErrorProps> {
const theme = useSelector(
(state: RootState) => state.nonSaveVariables.theme.colors,
);
return (
<View
style={{
paddingLeft: 10,
paddingBottom: 10,
paddingRight: 10,
flexDirection: 'row',
gap: 10,
alignItems: 'center',
}}>
<MyIcon
name={iconName === undefined ? 'info' : iconName}
color={theme.red500}
size={18}
/>
<Text color="$red500">{text}</Text>
</View>
);
}

View File

@ -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) => (
<Alert
maxWidth="95%"
alignSelf="center"
flexDirection="row"
// maxWidth="95%"
//alignSelf="center"
//flexDirection="row"
bg="$secondary600"
action={action}
variant={variant}
{...rest}>
@ -85,15 +96,17 @@ function showToast(toast: any, item: toastType) {
? '$white'
: variant !== 'outline'
? '$black'
: null
: '$white'
}>
{title}
</Text>
</HStack>
{isClosable ? (
<MyButton
type="secondary"
text="X"
<MyIconButton
MyIconProps={{
name: 'close',
size: 24,
}}
onPress={() => toast.close(id)}
/>
) : /*<IconButton
@ -113,7 +126,7 @@ function showToast(toast: any, item: toastType) {
? '$white'
: variant !== 'outline'
? '$black'
: null
: '$white'
}>
{description}
</Text>

View File

@ -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 {

View File

@ -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',

View File

@ -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<AccountName>) => {
delete state.cachedUsers[action.payload];
},
setCachedEvent: (state, action: PayloadAction<PAEvent>) => {
state.cachedEvents[action.payload.UUID] = action.payload;
},
removeCachedEvent: (state, action: PayloadAction<EventId>) => {
delete state.cachedEvents[action.payload];
},
setSelectedChat: (state, action: PayloadAction<roomId>) => {
state.selectedChat = action.payload;
},

View File

@ -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,
);
},
};

331
src/event/EventManager.ts Normal file
View File

@ -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<PAEvent | undefined> {
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<string>;
Description: BasicEventProp<string>;
StartDate: BasicEventProp<timestamp>;
EndDate: BasicEventProp<timestamp>;
Latitude: BasicEventProp<number>;
Longitude: BasicEventProp<number>;
Type: BasicEventProp<EventType>;
Theme: BasicEventProp<EventThemes>;
lastUpdateTimestamp: timestamp;
FriendList: BasicEventProp<string[]>;
UserLength: BasicEventProp<number>;
EventBannerPicture: EventBannerPicture;
ButtonAction: BasicEventProp<string>; */
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;

71
src/event/types.ts Normal file
View File

@ -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<T1> {
source: SourceProp;
url?: string;
data?: T1;
}
export function createEventProp<T1>(
source: SourceProp,
data?: T1,
url?: string,
): BasicEventProp<T1> {
return {source, data, url};
}
export type EventBannerPictureType = BasicEventProp<Blob | undefined>;
export interface EventBannerPicture {
lq: EventBannerPictureType; //low quality
hq?: EventBannerPictureType; //high quality
}
export interface PAEvent {
UUID: EventId;
Name: BasicEventProp<string>;
Description: BasicEventProp<string>;
StartDate: BasicEventProp<timestamp>;
EndDate: BasicEventProp<timestamp>;
Latitude: BasicEventProp<number>;
Longitude: BasicEventProp<number>;
Type: BasicEventProp<EventType>;
Theme: BasicEventProp<EventThemes>;
lastUpdateTimestamp: timestamp;
FriendList: BasicEventProp<string[]>;
UserLength: BasicEventProp<number>;
EventBannerPicture: EventBannerPicture;
ButtonAction: BasicEventProp<string>;
}
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;
}
}

View File

@ -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;

View File

@ -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;
},
};

View File

@ -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];

View File

@ -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<typeof propsDefault, typeof keys> = {
filePath: name,
version: 1,
keys,
migration: () => {
return DBMigration[name](thisSchema);
},
setEntry: (val: typeof thisSchema.defaultProps, suffix?: string) => {
return setEntry<typeof thisSchema, typeof thisSchema.defaultProps>(
thisSchema,
val,
suffix,
);
},
getEntry: (key: possibleDBKeys, suffix?: string) => {
return getEntry<typeof thisSchema, typeof thisSchema.defaultProps>(
thisSchema,
key,
suffix,
);
},
getAllEntries: (filter?: filterParam, suffix?: string) => {
return getAllEntries<typeof thisSchema, typeof thisSchema.defaultProps>(
thisSchema,
filter,
suffix,
);
},
defaultProps: propsDefault,
details: {
name,
properties: propsType,
primaryKey,
},
};
export default thisSchema;

View File

@ -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<props, enums> {
@ -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);
}

View File

@ -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: {

View File

@ -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 its 8 characters or more.',
description: 'Make sure its ${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 its 8 characters or more.',
info: 'Make sure its ${minLength} characters or more.',
info2: 'You will be logged out after changing your password.',
},
help: {

View File

@ -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() {
/>
<MapStack.Screen
name="Event"
options={{headerShown: true}}
component={MapScreen}
options={{
animation: 'slide_from_right',
title: 'lang',
headerShown: true,
headerStyle: headerStyle,
headerShadowVisible: false,
headerTitleAlign: 'center',
}}
component={EventPage}
/>
<MapStack.Screen
name="EventStore"
options={{headerShown: true}}
component={MapScreen}
component={EventPage}
/>
</MapStack.Navigator>
);

View File

@ -0,0 +1,7 @@
import {Text} from '@gluestack-ui/themed';
function EventPage() {
return <Text>EventPage</Text>;
}
export default EventPage;

View File

@ -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';

View File

@ -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'},
});
});
}}

View File

@ -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 (
<MyScreenContainer
@ -55,13 +75,22 @@ export function SignUpStepUsername() {
text={lang.signUpStepUsername.inputUsername}
iconName="person"
value={username}
onChangeText={text => setUsername(text)}
onChangeText={text => {
setUsername(text);
setInputTouched(true);
}}
maxLength={userNameOptions.maxLength}
helper={
inputTouched &&
usernameValid && <MyInputError text={errorText.join('')} />
}
/>
<MyButton
type="secondary"
text={lang.buttonNext}
style={{marginBottom: 20}}
disabled={username.length < userNameOptions.minLength}
onPress={() => {
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 (
<MyScreenContainer
@ -182,22 +254,40 @@ export function SignUpStepPassword() {
<ContentContainer>
<Title
text={lang.signUpStepPassword.title}
description={lang.signUpStepPassword.description}
description={descriptionText.join('')}
/>
<View style={{gap: 12, marginTop: 20}}>
<MyIconInput
text={lang.signUpStepPassword.inputPassword}
iconName="lock"
secureTextEntry
secureTextEntry={!isPasswordVisible}
value={password}
onChangeText={text => setPassword(text)}
maxLength={passwordOptions.maxLength}
onChangeText={text => {
setPassword(text);
setInputTouched(true);
}}
rightComponent={
<MyIconButton
MyIconProps={{
name: isPasswordVisible ? 'visibility-off' : 'visibility',
size: 24,
}}
onPress={togglePasswordVisibility}
/>
}
helper={
inputTouched &&
!passwordValid() && <MyInputError text={errorText()} />
}
/>
<MyButton
type="secondary"
text={lang.buttonNext}
style={{marginBottom: 2}}
disabled={!passwordValid()}
onPress={() => {
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<RootScreenNavigationProp>();
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 = (
<MyIcon name="close" size={24} color={currentTheme.red600} />
);
if (!accountNameValid()) {
return closeIcon;
} else if (isAccountNameAvailable === AccountNameAvailable.Loading) {
return <Spinner />;
} else if (isAccountNameAvailable === AccountNameAvailable.Available) {
return <MyIcon name="check" size={24} color={currentTheme.green400} />;
} 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 (
<MyScreenContainer
@ -246,7 +413,17 @@ export function SignUpStepAccountName() {
text={lang.signUpStepAccountName.inputAccountName}
iconName="person"
value={accountName}
onChangeText={text => setAccountName(text)}
onChangeText={text => {
setAccountName(text);
setInputTouched(true);
setIsAccountNameAvailable(AccountNameAvailable.Loading);
}}
maxLength={accountNameOptions.maxLength}
rightComponent={rightComponent()}
helper={
inputTouched &&
!accountNameValid() && <MyInputError text={errorText()} />
}
/>
<MyButton
@ -254,15 +431,19 @@ export function SignUpStepAccountName() {
text={lang.signUpStepAccountName.buttonGetStarted}
style={{marginBottom: 2}}
isLoading={isLoading}
disabled={
!accountNameValid() ||
isAccountNameAvailable !== AccountNameAvailable.Available
}
onPress={() => {
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'},
});
});
}}
/>

View File

@ -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,

View File

@ -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/*"]
}
}
}