From cc569a818726a6d9da653e48ec0cd602fbed82a0 Mon Sep 17 00:00:00 2001 From: Netcup Gituser Date: Sun, 17 Dec 2023 22:53:39 +0100 Subject: [PATCH] appstart, create event, common inputs --- src/components/MyCommonInputs.tsx | 259 ++++++++++++++++++++++ src/configs/appNonSaveVar.ts | 10 +- src/configs/appNonSaveVarReducer.ts | 18 +- src/configs/appVar.ts | 6 +- src/configs/appVarReducer.ts | 31 ++- src/configs/types.ts | 5 +- src/helper/appData.ts | 1 - src/helper/request.ts | 7 +- src/lang/default.ts | 47 +++- src/lang/en.ts | 52 ++++- src/navigation/navigation.tsx | 38 +++- src/navigation/tabs/main/MapTab.tsx | 16 +- src/pages/calendar/calendar.tsx | 2 - src/pages/event/CreateEvent/Overview.tsx | 57 +++++ src/pages/event/CreateEventNavigation.tsx | 53 +++++ src/pages/map/map.tsx | 138 +++++++++++- src/pages/profile/profile.tsx | 72 +++--- src/pages/welcome/login/login.tsx | 29 +-- src/pages/welcome/signUp/signUp.tsx | 213 +++--------------- src/user/MyUserManager.ts | 19 +- src/user/UserManager.ts | 79 +++---- src/user/types.ts | 6 +- 22 files changed, 815 insertions(+), 343 deletions(-) create mode 100644 src/components/MyCommonInputs.tsx create mode 100644 src/pages/event/CreateEvent/Overview.tsx create mode 100644 src/pages/event/CreateEventNavigation.tsx diff --git a/src/components/MyCommonInputs.tsx b/src/components/MyCommonInputs.tsx new file mode 100644 index 0000000..e1cbc65 --- /dev/null +++ b/src/components/MyCommonInputs.tsx @@ -0,0 +1,259 @@ +import {useEffect, useState} from 'react'; +import {MyIconInput, MyInputError} from './MyInput'; +import {useSelector} from 'react-redux'; +import {RootState} from '@redux/store'; +import { + accountNameOptions, + passwordOptions, + userNameOptions, +} from '@configs/types'; +import reactStringReplace from 'react-string-replace'; +import {MyIconButton} from './MyButton'; +import {MyIcon} from './MyIcon'; +import {Spinner} from '@gluestack-ui/themed'; +import {apiBackendRequest, makeRequest} from '@helper/request'; + +export function usernameValid(username: string) { + if (username.length < userNameOptions.minLength) { + return false; + } else if (!userNameOptions.isAllowed(username)) { + return false; + } else { + return true; + } +} + +interface MyUsernameInputProps { + username: string; + setUsername: (username: string) => void; +} + +export function MyUsernameInput(props: MyUsernameInputProps) { + const lang = useSelector( + (state: RootState) => state.appVariables.lang.commonInputs.username, + ); + + const [inputTouched, setInputTouched] = useState(false); + + const errorText = () => { + if (props.username.length < userNameOptions.minLength) { + return reactStringReplace( + lang.errorLength, + '${minLength}', + (match, i) => { + return userNameOptions.minLength.toString(); + }, + ).join(''); + } else if (!userNameOptions.isAllowed(props.username)) { + return lang.errorUsernameInvalid; + } + return ''; + }; + + return ( + { + props.setUsername(text); + setInputTouched(true); + }} + maxLength={userNameOptions.maxLength} + helper={ + inputTouched && + !usernameValid(props.username) && + } + /> + ); +} + +export function passwordValid(password: string) { + if (password.length < passwordOptions.minLength) { + return false; + } else if (!passwordOptions.isAllowed(password)) { + return false; + } else { + return true; + } +} + +interface MyPasswordInputProps { + password: string; + setPassword: (password: string) => void; + disableContainer?: boolean; +} + +export function MyPasswordInput(props: MyPasswordInputProps) { + const lang = useSelector( + (state: RootState) => state.appVariables.lang.commonInputs.password, + ); + + const [inputTouched, setInputTouched] = useState(false); + const [isPasswordVisible, setIsPasswordVisible] = useState(false); + + const togglePasswordVisibility = () => + setIsPasswordVisible(!isPasswordVisible); + + const errorText = () => { + if (props.password.length < passwordOptions.minLength) { + return reactStringReplace(lang.errorLength, '${minLength}', () => { + return passwordOptions.minLength.toString(); + }).join(''); + } else if (!passwordOptions.isAllowed(props.password)) { + return lang.errorPasswordInvalid; + } else { + return ''; + } + }; + + return ( + { + props.setPassword(text); + setInputTouched(true); + }} + rightComponent={ + + } + helper={ + inputTouched && + !passwordValid(props.password) && + } + disableContainer={props.disableContainer} + /> + ); +} + +export function accountNameValid(accountName: string) { + if (accountName.length < accountNameOptions.minLength) { + return false; + } else if (!accountNameOptions.isAllowed(accountName)) { + return false; + } else { + return true; + } +} + +export enum AccountNameAvailable { + Loading, + Available, + NotAvailable, +} + +interface MyAccountNameInputProps { + accountName: string; + setAccountName: (accountName: string) => void; + checkAccountNameAvailability?: boolean; + isAccountNameAvailable?: AccountNameAvailable; + setIsAccountNameAvailable?: ( + isAccountNameAvailable: AccountNameAvailable, + ) => void; +} + +export function MyAccountNameInput(props: MyAccountNameInputProps) { + const lang = useSelector( + (state: RootState) => state.appVariables.lang.commonInputs.accountName, + ); + const currentTheme = useSelector( + (state: RootState) => state.nonSaveVariables.theme.colors, + ); + + const [inputTouched, setInputTouched] = useState(false); + + const rightComponent = () => { + const closeIcon = ( + + ); + + if (!accountNameValid(props.accountName)) { + return closeIcon; + } else if (props.isAccountNameAvailable === AccountNameAvailable.Loading) { + return ; + } else if ( + props.isAccountNameAvailable === AccountNameAvailable.Available + ) { + return ; + } else { + return closeIcon; + } + }; + + const errorText = () => { + if (props.accountName.length < accountNameOptions.minLength) { + return reactStringReplace(lang.errorLength, '${minLength}', () => { + return accountNameOptions.minLength.toString(); + }).join(''); + } else if (!accountNameOptions.isAllowed(props.accountName)) { + return lang.errorAccountNameInvalid; + } + return ''; + }; + + if (props.checkAccountNameAvailability) { + useEffect(() => { + if (!accountNameValid(props.accountName)) return; + + const delay = 400; + const timeoutId = setTimeout(() => { + makeRequest({ + path: apiBackendRequest.CHECK_ACCOUNT_NAME, + requestGET: {':accountName': props.accountName}, + response: {}, + }) + .then(() => { + if (props.checkAccountNameAvailability) { + if (props.setIsAccountNameAvailable) { + props.setIsAccountNameAvailable(AccountNameAvailable.Available); + } + } + }) + .catch(() => { + if (props.setIsAccountNameAvailable) { + props.setIsAccountNameAvailable( + AccountNameAvailable.NotAvailable, + ); + } + }); + }, delay); + + // Cleanup the timeout on component unmount or when inputValue changes + return () => clearTimeout(timeoutId); + }, [props.accountName]); + } + + return ( + { + props.setAccountName(text); + setInputTouched(true); + + if (props.setIsAccountNameAvailable) { + props.setIsAccountNameAvailable(AccountNameAvailable.Loading); + } + }} + maxLength={accountNameOptions.maxLength} + rightComponent={props.checkAccountNameAvailability && rightComponent()} + helper={ + inputTouched && + !accountNameValid(props.accountName) && ( + + ) + } + /> + ); +} diff --git a/src/configs/appNonSaveVar.ts b/src/configs/appNonSaveVar.ts index b095b11..701c9e4 100644 --- a/src/configs/appNonSaveVar.ts +++ b/src/configs/appNonSaveVar.ts @@ -2,14 +2,12 @@ import {chatEntity, roomId} from '@configs/chat/types'; import {User} from '@user/types'; -import {AccountName} from './types'; +import {UserId} 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 {EventID, PAEvent} from '@event/types'; +import {PA_Point} from '@components/map/types'; export const APP_VERSION = getVersionByNum(1); export const AppVarMaxBackups: number = 10; @@ -33,7 +31,7 @@ export interface NON_SAVE_VARS { appStatus: appStatus; theme: ThemeTokensType; connectionStatus: connectionStatus; - cachedUsers: {[key: AccountName]: User}; + cachedUsers: {[key: UserId]: User}; cachedEvents: {[key: EventID]: PAEvent}; chats: {[key: roomId]: chatEntity}; chatActivity: roomId[]; diff --git a/src/configs/appNonSaveVarReducer.ts b/src/configs/appNonSaveVarReducer.ts index ac4aa70..40a7ba8 100644 --- a/src/configs/appNonSaveVarReducer.ts +++ b/src/configs/appNonSaveVarReducer.ts @@ -5,7 +5,7 @@ import {ThemeTokensType} from '@configs/colors'; import {chatEntity, roomId} from '@configs/chat/types'; import {SourceProp, User} from '@user/types'; -import {AccountName, EventId} from './types'; +import {EventId, UserId} from './types'; import {PA_Point} from '@components/map/types'; import {PAEvent, createEventProp} from '@event/types'; @@ -20,9 +20,9 @@ export const appNonSaveVariablesSlice = createSlice({ state.theme = action.payload; }, setCachedUser: (state, action: PayloadAction) => { - state.cachedUsers[action.payload.AccountName] = action.payload; + state.cachedUsers[action.payload.UserId] = action.payload; }, - removeCachedUser: (state, action: PayloadAction) => { + removeCachedUser: (state, action: PayloadAction) => { delete state.cachedUsers[action.payload]; }, setCachedEvent: (state, action: PayloadAction) => { @@ -31,10 +31,16 @@ export const appNonSaveVariablesSlice = createSlice({ removeCachedEvent: (state, action: PayloadAction) => { delete state.cachedEvents[action.payload]; }, - setJoinedEvent: (state, action: PayloadAction<{id: EventId, isJoined: number}>) => { - state.cachedEvents[action.payload.id].isJoined = createEventProp(SourceProp.cached, action.payload.isJoined); + setJoinedEvent: ( + state, + action: PayloadAction<{id: EventId; isJoined: number}>, + ) => { + state.cachedEvents[action.payload.id].isJoined = createEventProp( + SourceProp.cached, + action.payload.isJoined, + ); }, - + setSelectedChat: (state, action: PayloadAction) => { state.selectedChat = action.payload; }, diff --git a/src/configs/appVar.ts b/src/configs/appVar.ts index 7146503..820af3a 100644 --- a/src/configs/appVar.ts +++ b/src/configs/appVar.ts @@ -1,4 +1,4 @@ -import {XToken, AccountName, Username} from '@configs/types'; +import {AccountName, Username, UserId} from '@configs/types'; import {VersionType} from '@helper/version'; import {MyUserAccount} from '@user/types'; import {APP_VERSION} from './appNonSaveVar'; @@ -53,8 +53,8 @@ export interface PREFERENCES_VARS { version: VersionType; theme: ThemeMode; RegisterProcess: RegisterProcess; - selectedAccount: AccountName | 'none'; - accounts: {[key: AccountName]: MyUserAccount}; + selectedAccount: UserId | 'none'; + accounts: {[key: UserId]: MyUserAccount}; } export const preferences_vars_default: PREFERENCES_VARS = { diff --git a/src/configs/appVarReducer.ts b/src/configs/appVarReducer.ts index d01b357..fc52cb9 100644 --- a/src/configs/appVarReducer.ts +++ b/src/configs/appVarReducer.ts @@ -7,7 +7,7 @@ import { } from './appVar'; import LangFormat from '@lang/default'; import {lang as defaultLang} from '@lang/en'; -import {AccountName} from './types'; +import {UserId} from './types'; import {MyUserAccount} from '@user/types'; import {ThemeMode} from './colors'; @@ -21,6 +21,17 @@ const initialState: appVariablesState = { lang: defaultLang, }; +/* + store.dispatch( + appVarActions.setAccountName({ + name: response.response.accountName, + uuid: "", + }) +) + +to save changes: +helper/appData.ts +*/ export const appVariablesSlice = createSlice({ name: 'appVariables', initialState, @@ -37,11 +48,25 @@ export const appVariablesSlice = createSlice({ setRegisterProcess: (state, action: PayloadAction) => { state.preferences.RegisterProcess = action.payload; }, - setCurrentAccount: (state, action: PayloadAction) => { + setCurrentAccount: (state, action: PayloadAction) => { state.preferences.selectedAccount = action.payload; }, setAccount: (state, action: PayloadAction) => { - state.preferences.accounts[action.payload.AccountName] = action.payload; + state.preferences.accounts[action.payload.UserId] = action.payload; + }, + setAccountName: ( + state, + action: PayloadAction<{userId: string; name: string}>, + ) => { + state.preferences.accounts[action.payload.userId].AccountName.data = + action.payload.name; + }, + setUsername: ( + state, + action: PayloadAction<{userId: string; username: string}>, + ) => { + state.preferences.accounts[action.payload.userId].Username.data = + action.payload.username; }, setDBEK: (state, action: PayloadAction) => { state.preferences.dbek = action.payload; diff --git a/src/configs/types.ts b/src/configs/types.ts index 390f2eb..9db81b9 100644 --- a/src/configs/types.ts +++ b/src/configs/types.ts @@ -14,7 +14,7 @@ export type XToken = string; //export type verifyId = string; export type XAuthorization = string; -//export type UserId = string; +export type UserId = string; //export type WebSocketSessionId = string; export type EventId = string; @@ -32,7 +32,8 @@ export const userNameOptions = { minLength: 2, maxLength: 24, isAllowed: (text: string): boolean => { - return true; + // 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; }, }; diff --git a/src/helper/appData.ts b/src/helper/appData.ts index 42c0cf9..7c6e644 100644 --- a/src/helper/appData.ts +++ b/src/helper/appData.ts @@ -7,7 +7,6 @@ import {AppVarMaxBackups, APP_VERSION} from '@configs/appNonSaveVar'; import {appVarActions} from '@configs/appVarReducer'; import {store} from '@redux/store'; import {getData, setData} from './storage/appData'; -import {getVersionByType, stepUpVersionCalc} from './version'; const APP_CHANGE_BACKUP = 'appVerChangeBackup'; diff --git a/src/helper/request.ts b/src/helper/request.ts index 35de46a..aa17698 100644 --- a/src/helper/request.ts +++ b/src/helper/request.ts @@ -8,6 +8,7 @@ import { Password, XAuthorization, Username, + UserId, } from '../configs/types'; import MyUserManager from '@user/MyUserManager'; @@ -20,7 +21,7 @@ export const apiPath = { export enum apiBackendRequest { LOGIN = '/user/login', APP_START = '/user', - GET_USER_PROFILE = '/users/:accountName', + GET_USER_PROFILE = '/users/:userId', LOGOUT = '/user/logout', SIGN_UP = '/user/signup', CHECK_ACCOUNT_NAME = '/user/check/:accountName', @@ -112,6 +113,7 @@ interface LOGIN extends defaultRequest { }; response?: { XAuthorization: XAuthorization; + UserId: UserId; Username: Username; }; } @@ -125,6 +127,7 @@ interface SIGN_UP extends defaultRequest { }; response?: { XAuthorization: XAuthorization; + UserId: UserId; Username: Username; }; } @@ -133,7 +136,7 @@ interface GET_USER_PROFILE extends defaultRequest { path: apiBackendRequest.GET_USER_PROFILE; requestGET: { - ':accountName': AccountName; + ':userId': UserId; }; response: { //AccountName: AccountName; diff --git a/src/lang/default.ts b/src/lang/default.ts index 83d9f9a..542e7e9 100644 --- a/src/lang/default.ts +++ b/src/lang/default.ts @@ -13,6 +13,28 @@ export default interface LangFormat { join: string; quit: string; }; + eventCreateion: { + newEventTitle: string; + newPublicEventTitle: string; + newPrivateEventTitle: string; + name: string; + uploadImage: string; + description: string; + location: string; + create: string; + website: string; + datetime: string; + openingHoursFlag: string; + }; + days: { + monday: string; + tuesday: string; + wednesday: string; + thursday: string; + friday: string; + saturday: string; + sunday: string; + }; navigation: { home: { profile: { @@ -41,6 +63,23 @@ export default interface LangFormat { info: string; error: string; success: string; + commonInputs: { + username: { + label: string; + errorLength: string; + errorUsernameInvalid: string; + }; + accountName: { + label: string; + errorLength: string; + errorAccountNameInvalid: string; + }; + password: { + label: string; + errorLength: string; + errorPasswordInvalid: string; + }; + }; registration: { buttonLogin: string; buttonSignUp: string; @@ -71,12 +110,9 @@ export default interface LangFormat { signUpStepUsername: { title: string; description: string; - inputUsername: string; - error: string; }; signUpStepPhoneNumber: { title: string; - inputPhoneNumber: string; }; signUpStepVerifyPhoneNumber: { title: string; @@ -86,9 +122,6 @@ export default interface LangFormat { signUpStepPassword: { title: string; description: string; - inputPassword: string; - errorLength: string; - errorPasswordInvalid: string; }; signUpStepAccountName: { title: string; @@ -96,8 +129,6 @@ export default interface LangFormat { 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 ebf75b6..1460032 100644 --- a/src/lang/en.ts +++ b/src/lang/en.ts @@ -12,6 +12,28 @@ export const lang: LangFormat = { join: 'Join', quit: 'Quit', }, + eventCreateion: { + newEventTitle: 'New Event', + newPublicEventTitle: 'New Public Event', + newPrivateEventTitle: 'New Private Event', + name: 'Name', + uploadImage: 'Upload banner', + description: 'Description', + location: 'Location', + create: 'Create', + website: 'Website', + datetime: 'Date & Time', + openingHoursFlag: 'Opening hours / days', + }, + days: { + monday: 'Monday', + tuesday: 'Tuesday', + wednesday: 'Wednesday', + thursday: 'Thursday', + friday: 'Friday', + saturday: 'Saturday', + sunday: 'Sunday', + }, appName: 'Party App', navigation: { home: { @@ -41,6 +63,26 @@ export const lang: LangFormat = { info: 'Info', error: 'Error', success: 'Success', + commonInputs: { + username: { + label: 'USERNAME', + errorLength: 'At least ${minLength} characters are required', + errorUsernameInvalid: + 'Username can only contain a-z, 0-9, underscores and dots', + }, + accountName: { + label: 'ACCOUNT NAME', + errorLength: 'Account name must be at least ${minLength} characters long', + errorAccountNameInvalid: + 'Account name can only contain a-z, 0-9, underscores and dots', + }, + password: { + label: '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', + }, + }, registration: { buttonLogin: 'Login', buttonSignUp: 'Sign up', @@ -71,12 +113,9 @@ export const lang: LangFormat = { signUpStepUsername: { 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', - inputPhoneNumber: 'PHONE NUMBER', }, signUpStepVerifyPhoneNumber: { title: 'We sent you a code', @@ -86,10 +125,6 @@ export const lang: LangFormat = { signUpStepPassword: { title: "You'll need a password", 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', @@ -97,9 +132,6 @@ 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', diff --git a/src/navigation/navigation.tsx b/src/navigation/navigation.tsx index 1810bab..c92d125 100644 --- a/src/navigation/navigation.tsx +++ b/src/navigation/navigation.tsx @@ -2,6 +2,7 @@ import Stack from '@pages/globalStackManager'; import { NavigatorScreenParams, getFocusedRouteNameFromRoute, + useNavigation, } from '@react-navigation/native'; import { BottomTabBarProps, @@ -16,7 +17,7 @@ import ProfileTab, { ProfileStackNavigatorParamList, } from './tabs/main/ProfileTab'; import {FadeInView} from '@helper/animations'; -import {Animated, AppState, View} from 'react-native'; +import {Animated, View} from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; import { RegistrationScreenAnim, @@ -29,8 +30,9 @@ import {RootState, store as reduxStore} from '@redux/store'; import {Text} from '@gluestack-ui/themed'; import {MyTouchableOpacity} from '@components/MyTouchableOpacity'; import {useEffect, useRef} from 'react'; -import {animated, useSpring} from '@react-spring/native'; import {apiBackendRequest, makeRequest} from '@helper/request'; +import {appVarActions} from '@configs/appVarReducer'; +import MyUserManager from '@user/MyUserManager'; export type RootStackNavigatorParamList = { Home: NavigatorScreenParams; @@ -44,9 +46,13 @@ export default function Navigation() { const currentUser = reduxStore.getState().appVariables.preferences.selectedAccount; + const rootNavigation = useNavigation(); + useEffect(() => { console.log('APP NAVIGATION'); + if (currentUser === 'none') return; + makeRequest({ path: apiBackendRequest.APP_START, response: { @@ -65,9 +71,31 @@ export default function Navigation() { Events: {}, TokenValid: false, */ }, - }).then(response => { - console.log(response); - }); + }) + .then(resp => { + if (resp.response.accountName !== undefined) { + reduxStore.dispatch( + appVarActions.setAccountName({ + userId: currentUser, + name: resp.response.accountName, + }), + ); + } + + if (resp.response.username !== undefined) { + reduxStore.dispatch( + appVarActions.setUsername({ + userId: currentUser, + username: resp.response.username, + }), + ); + } + }) + .catch(err => { + if (err.status === 401) { + MyUserManager.logoutMyUser(rootNavigation); + } + }); }, []); return ( diff --git a/src/navigation/tabs/main/MapTab.tsx b/src/navigation/tabs/main/MapTab.tsx index b780440..1ef8af4 100644 --- a/src/navigation/tabs/main/MapTab.tsx +++ b/src/navigation/tabs/main/MapTab.tsx @@ -12,6 +12,10 @@ import {Map} from '@pages/map/map'; import {EventID} from '@components/map/types'; import EventPage from '@pages/event/EventPage'; +import CreateEventPage, { + EventCreationNavigatorParamList, +} from '@pages/event/CreateEventNavigation'; +import {NavigatorScreenParams} from '@react-navigation/native'; export const MapTabName = 'Map'; @@ -19,6 +23,7 @@ export type MapStackNavigatorParamList = { Overview: undefined; Event: {eventID: EventID}; EventStore: {eventID: EventID}; + CreateEvent: NavigatorScreenParams; }; const MapStack = createNativeStackNavigator(); @@ -27,9 +32,7 @@ export type MapScreenNavigationProp = NativeStackNavigationProp; function MapTab() { - const lang = useSelector( - (state: RootState) => state.appVariables.lang.navigation.home.map, - ); + const lang = useSelector((state: RootState) => state.appVariables.lang); const currentTheme = useSelector( (state: RootState) => state.nonSaveVariables.theme.colors, ); @@ -63,6 +66,13 @@ function MapTab() { options={{headerShown: true}} component={EventPage} /> + ); } diff --git a/src/pages/calendar/calendar.tsx b/src/pages/calendar/calendar.tsx index da40e5e..7707f10 100644 --- a/src/pages/calendar/calendar.tsx +++ b/src/pages/calendar/calendar.tsx @@ -18,8 +18,6 @@ const events: ArrayLike | null | undefined = []; for (let i = 1; i <= 100; i++) { const randomUrlIndex = Math.floor(Math.random() * baseUrls.length); - // hello world - events.push({ id: i, url: baseUrls[randomUrlIndex], diff --git a/src/pages/event/CreateEvent/Overview.tsx b/src/pages/event/CreateEvent/Overview.tsx new file mode 100644 index 0000000..d7092f3 --- /dev/null +++ b/src/pages/event/CreateEvent/Overview.tsx @@ -0,0 +1,57 @@ +import {MyIconButton} from '@components/MyButton'; +import {MyScreenContainer} from '@components/MyScreenContainer'; +import {ButtonText, VStack, HStack} from '@gluestack-ui/themed'; +import {RootState} from '@redux/store'; +import {View, Text, Button, Pressable} from 'react-native'; +import {useSelector} from 'react-redux'; + +import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; + +function Overview() { + const lang = useSelector( + (state: RootState) => state.appVariables.lang.eventCreateion, + ); + const currentTheme = useSelector( + (state: RootState) => state.nonSaveVariables.theme.colors, + ); + + return ( + + + + + + + ); +} + +function EventType({ + MyIconProps, +}: { + MyIconProps: {name: string; size: number; color?: string}; +}) { + const currentTheme = useSelector( + (state: RootState) => state.nonSaveVariables.theme.colors, + ); + + return ( + + + + Event Type + + + ); +} + +export default Overview; diff --git a/src/pages/event/CreateEventNavigation.tsx b/src/pages/event/CreateEventNavigation.tsx new file mode 100644 index 0000000..154ce97 --- /dev/null +++ b/src/pages/event/CreateEventNavigation.tsx @@ -0,0 +1,53 @@ +import {MapStackNavigatorParamList} from '@navigation/tabs/main/MapTab'; +import { + createNativeStackNavigator, + NativeStackNavigationProp, +} from '@react-navigation/native-stack'; +import {RootState} from '@redux/store'; +import {View, Text} from 'react-native'; +import {useSelector} from 'react-redux'; +import EventPage from './EventPage'; + +import Overview from './CreateEvent/Overview'; + +export type EventCreationNavigatorParamList = { + Overview: undefined; + PublicEvent: undefined; + PrivateEvent: undefined; +}; + +const EventCreationStack = + createNativeStackNavigator(); + +export type EventCreationScreenNavigationProp = + NativeStackNavigationProp; + +function CreateEventPage() { + const lang = useSelector((state: RootState) => state.appVariables.lang); + const currentTheme = useSelector( + (state: RootState) => state.nonSaveVariables.theme.colors, + ); + + const headerStyle = { + backgroundColor: currentTheme.backgroundDark400, + }; + + return ( + + + + ); +} + +export default CreateEventPage; diff --git a/src/pages/map/map.tsx b/src/pages/map/map.tsx index eb65dca..49a0e59 100644 --- a/src/pages/map/map.tsx +++ b/src/pages/map/map.tsx @@ -1,4 +1,4 @@ -import {StyleSheet, View, Text, Image} from 'react-native'; +import {StyleSheet, View, Text, Image, ViewStyle} from 'react-native'; import Mapbox, {MarkerView} from '@rnmapbox/maps'; import React, {useState} from 'react'; // Add useState import @@ -11,14 +11,17 @@ import {RootState, store} from '@redux/store'; import {appNonSaveVarActions} from '@configs/appNonSaveVarReducer'; import {Position} from '@rnmapbox/maps/src/types/Position'; -import {Dimensions} from 'react-native'; +import {Dimensions, Pressable} from 'react-native'; import {MyIconButton} from '@components/MyButton'; import {MyTouchableOpacity} from '@components/MyTouchableOpacity'; import {MyIcon} from '@components/MyIcon'; import {useSelector} from 'react-redux'; import {Button, ButtonIcon} from '@gluestack-ui/themed'; +import LinearGradient from 'react-native-linear-gradient'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; +import {MapScreenNavigationProp} from '@navigation/tabs/main/MapTab'; +import {useNavigation} from '@react-navigation/native'; Mapbox.setAccessToken( 'pk.eyJ1IjoidGl0YW5pdW1iYWNoIiwiYSI6ImNscGgzZGJxMDAwbHQyaXA2N3BtOWUxbWkifQ.x-f8JJxwQHWmPFI3P6Qn-w', @@ -27,6 +30,11 @@ Mapbox.setAccessToken( let lastCameraChange = 0; let isRerenderData = 0; +const IconButtonSize = 28; +const IconButtonCircleSize = IconButtonSize * 1.75; + +const IconMarginRight = 15; + export const Map = () => { const mapRef = React.useRef(null); //const [mapMarkers, setMapMarkers] = useState([]); // Add useState for visibleBounds @@ -36,6 +44,8 @@ export const Map = () => { (state: RootState) => state.nonSaveVariables.theme.colors, ); + const navigation = useNavigation(); + const getVisibleBounds = async () => { // return when lastDataRerender is 300ms ago const now = Date.now(); @@ -132,16 +142,63 @@ export const Map = () => { attributionPosition={{top: 10, left: 100}} logoPosition={{top: 10, left: 10}} styleURL="mapbox://styles/titaniumbach/clpij5uoo00o301pg2dj23j0m" - projection="globe"> + projection="globe" + compassPosition={{top: 10, right: IconMarginRight}} + compassImage={'mycompassimagekey'}> {}} + style={{ + top: 70, + right: IconMarginRight, + }} + /> + + {}} + style={{ + top: 70 + IconButtonCircleSize + 10, + right: IconMarginRight, + }} + /> + + { + navigation.navigate('CreateEvent', {screen: 'Overview'}); + }} + style={{ + bottom: 90 + 10 + IconButtonCircleSize, + right: IconMarginRight, + }} + type="primary" + /> + {}} + style={{ + bottom: 90, + right: IconMarginRight, + }} /> @@ -166,6 +223,8 @@ const styles = StyleSheet.create({ function MapIconButton({ onPress, MyIconProps, + style, + type, }: { onPress: () => void; MyIconProps: { @@ -174,18 +233,73 @@ function MapIconButton({ size: number; backgroundColor?: string; }; + style?: ViewStyle; + type?: 'primary' | 'secondary'; }) { + const currentTheme = useSelector( + (state: RootState) => state.nonSaveVariables.theme, + ); + + const viewStyle: ViewStyle = { + position: 'absolute', + + width: IconButtonCircleSize, + height: IconButtonCircleSize, + borderRadius: MyIconProps.size, // This will make the View circular + justifyContent: 'center', // Center the icon vertically + alignItems: 'center', // Center the icon horizontally + elevation: 5, + overflow: 'hidden', + ...style, + }; + + const pressableStyle: ViewStyle = { + width: '100%', + height: '100%', + justifyContent: 'center', // Center the icon vertically + alignItems: 'center', // Center the icon horizontally + }; + + if (type === 'primary') { + return ( + + + + + + ); + } + return ( - + + + ); } diff --git a/src/pages/profile/profile.tsx b/src/pages/profile/profile.tsx index dd97862..b94c0d4 100644 --- a/src/pages/profile/profile.tsx +++ b/src/pages/profile/profile.tsx @@ -7,7 +7,7 @@ import {MyVerticalDivider} from '@components/MyDivider'; import {MyIcon} from '@components/MyIcon'; import {useNavigation} from '@react-navigation/native'; import {ProfileScreenNavigationProp} from '@navigation/tabs/main/ProfileTab'; -import {MyIconInput} from '@components/MyInput'; +import {MyIconInput, MyInputError} from '@components/MyInput'; import {useEffect, useState} from 'react'; import {RootScreenNavigationProp} from '@navigation/navigation'; import {useSelector} from 'react-redux'; @@ -19,6 +19,12 @@ import MyUserManager from '@user/MyUserManager'; import {apiBackendRequest, makeRequest} from '@helper/request'; import reactStringReplace from 'react-string-replace'; import {passwordOptions, userNameOptions} from '@configs/types'; +import { + MyPasswordInput, + MyUsernameInput, + passwordValid, + usernameValid, +} from '@components/MyCommonInputs'; function UserAvatar() { return ( @@ -137,7 +143,9 @@ export function ProfileSettings() { @@ -256,9 +259,7 @@ function SettingsItem({icon, title, value, onPress}: SettingsItemProps) { } export function UpdateUsername() { - const lang = useSelector( - (state: RootState) => state.appVariables.lang.profile.settings, - ); + const lang = useSelector((state: RootState) => state.appVariables.lang); const currentTheme = useSelector( (state: RootState) => state.nonSaveVariables.theme.colors, ); @@ -273,7 +274,7 @@ export function UpdateUsername() { const [newUsername, setNewUsername] = useState(user.Username.data || ''); const info2Text = reactStringReplace( - lang.changeUsername.info2, + lang.profile.settings.changeUsername.info2, '${minLength}', () => userNameOptions.minLength.toString(), ); @@ -283,7 +284,7 @@ export function UpdateUsername() { navigation.setOptions({ headerRight: () => - changed ? ( + changed && usernameValid(newUsername) ? ( navigation.goBack()}> - { - setNewUsername(text); + { + setNewUsername(value); }} /> - {lang.changeUsername.info} + {lang.profile.settings.changeUsername.info} {info2Text} @@ -335,9 +334,9 @@ export function UpdatePassword() { useEffect(() => { const passwordChanged = - currentPassword.length > 0 && - newPassword.length > 0 && - repeatNewPassword.length > 0 && + passwordValid(currentPassword) && + passwordValid(newPassword) && + passwordValid(repeatNewPassword) && newPassword === repeatNewPassword; navigation.setOptions({ @@ -363,28 +362,19 @@ export function UpdatePassword() { backgroundColor: currentTheme.backgroundDark300, marginTop: 4, }}> - setCurrentPassword(text)} + setCurrentPassword(value)} disableContainer /> - setNewPassword(text)} + setNewPassword(value)} disableContainer /> - setRepeatNewPassword(text)} + setRepeatNewPassword(value)} disableContainer /> diff --git a/src/pages/welcome/login/login.tsx b/src/pages/welcome/login/login.tsx index d8a46eb..57d096d 100644 --- a/src/pages/welcome/login/login.tsx +++ b/src/pages/welcome/login/login.tsx @@ -1,5 +1,4 @@ import {MyButton} from '@components/MyButton'; -import {MyIconInput} from '@components/MyInput'; import {MyScreenContainer} from '@components/MyScreenContainer'; import {MyTitle} from '@components/MyTitle'; import {apiBackendRequest, makeRequest} from '@helper/request'; @@ -17,6 +16,12 @@ import {useSelector} from 'react-redux'; import {ToBase64} from '@helper/base64'; import showToast from '@components/MyToast'; import {useToast} from '@gluestack-ui/themed'; +import { + MyAccountNameInput, + MyPasswordInput, + accountNameValid, + passwordValid, +} from '@components/MyCommonInputs'; export function Login() { const lang = useSelector( @@ -29,7 +34,7 @@ export function Login() { const [accountName, setAccountName] = useState('anna'); const [password, setPassword] = useState('testtesttest1#S'); - const loginEnabled = accountName.length > 0 && password.length > 0; + const loginEnabled = accountNameValid(accountName) && passwordValid(password); return ( - setAccountName(text)} + setAccountName(value)} /> - setPassword(text)} + + setPassword(value)} /> { - return userNameOptions.minLength.toString(); - }, - ); return ( - { - setUsername(text); - setInputTouched(true); - }} - maxLength={userNameOptions.maxLength} - helper={ - inputTouched && - usernameValid && - } + setUsername(value)} /> { let rp = {...registerProcess}; @@ -202,23 +184,6 @@ 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, @@ -228,24 +193,6 @@ export function SignUpStepPassword() { }, ); - 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); - setInputTouched(true); - }} - rightComponent={ - - } - helper={ - inputTouched && - !passwordValid() && - } + setPassword(value)} /> { let rp = {...registerProcess}; @@ -306,19 +233,10 @@ 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(); @@ -329,72 +247,10 @@ 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); - setInputTouched(true); - setIsAccountNameAvailable(AccountNameAvailable.Loading); - }} - maxLength={accountNameOptions.maxLength} - rightComponent={rightComponent()} - helper={ - inputTouched && - !accountNameValid() && + setAccountName(value)} + isAccountNameAvailable={isAccountNameAvailable} + setIsAccountNameAvailable={value => + setIsAccountNameAvailable(value) } + checkAccountNameAvailability /> { @@ -450,11 +299,13 @@ export function SignUpStepAccountName() { method: 'POST', response: { XAuthorization: '', + UserId: '', Username: '', }, }) .then(resp => { MyUserManager.createNewMyUser( + resp.response.UserId, accountName, resp.response.Username, resp.response.XAuthorization, diff --git a/src/user/MyUserManager.ts b/src/user/MyUserManager.ts index f7d2c98..f0dcd59 100644 --- a/src/user/MyUserManager.ts +++ b/src/user/MyUserManager.ts @@ -1,20 +1,23 @@ import {appVarActions} from '@configs/appVarReducer'; -import {AccountName, XAuthorization, Username} from '@configs/types'; +import {AccountName, XAuthorization, Username, UserId} from '@configs/types'; import {saveVarChanges} from '@helper/appData'; import {apiBackendRequest, makeRequest} from '@helper/request'; import BigDataManager from '@helper/storage/BigDataManager'; import {RootState, store} from '@redux/store'; import {useSelector} from 'react-redux'; import {MyUserAccount, createUserProp, SourceProp} from './types'; +import {RootScreenNavigationProp} from '@navigation/navigation'; function createNewMyUser( + UserId: UserId, AccountName: AccountName, Username: Username, SessionId: XAuthorization, ): Promise { return new Promise((resolve, reject) => { let user: MyUserAccount = { - AccountName /*: createUserProp(SourceProp.offline, AccountName)*/, + UserId, + AccountName: createUserProp(SourceProp.offline, AccountName), Username: createUserProp(SourceProp.offline, Username), /* Description: createUserProp(SourceProp.online), FollowersCount: createUserProp(SourceProp.online), @@ -101,7 +104,7 @@ function createNewMyUser( function createMyUser(user: MyUserAccount) { store.dispatch(appVarActions.setAccount(user)); - store.dispatch(appVarActions.setCurrentAccount(user.AccountName)); + store.dispatch(appVarActions.setCurrentAccount(user.UserId)); saveVarChanges(); } @@ -114,9 +117,17 @@ function setMyUser(user: MyUserAccount) { saveVarChanges(); } -function logoutMyUser() { +function logoutMyUser(rootNavigation?: RootScreenNavigationProp) { store.dispatch(appVarActions.setCurrentAccount('none')); saveVarChanges(); + + if (rootNavigation === undefined) return; + + rootNavigation.navigate('Registration', {screen: 'LoginPreview'}); + rootNavigation.reset({ + index: 0, + routes: [{name: 'Registration'}], + }); } function getSelectedUserAccount(): AccountName { diff --git a/src/user/UserManager.ts b/src/user/UserManager.ts index 8d3c576..e073e78 100644 --- a/src/user/UserManager.ts +++ b/src/user/UserManager.ts @@ -1,26 +1,19 @@ import {maxCachedUsers} from '@configs/appNonSaveVar'; import {appNonSaveVarActions} from '@configs/appNonSaveVarReducer'; -import {AccountName, XAuthorization} from '@configs/types'; +import {AccountName, UserId} 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 { - BasicUserProp, - createUserProp, - ProfilePicture, - ProfilePictureType, - SourceProp, - User, -} from './types'; +import {createUserProp, ProfilePicture, SourceProp, User} from './types'; let cachedUserList: AccountName[] = []; async function getUser( - AccountName: AccountName, + UserId: UserId, save?: boolean, ): Promise { - if (AccountName === 'none') { + if (UserId === 'none') { return undefined; } @@ -30,18 +23,19 @@ async function getUser( let userIsInCache = false; { - const usr = state.nonSaveVariables.cachedUsers[AccountName]; + const usr = state.nonSaveVariables.cachedUsers[UserId]; if (usr !== undefined) { user = usr; userIsInCache = true; } } - if (AccountName === state.appVariables.preferences.selectedAccount) { - const usr = state.appVariables.preferences.accounts[AccountName]; + if (UserId === state.appVariables.preferences.selectedAccount) { + const usr = state.appVariables.preferences.accounts[UserId]; if (usr !== undefined) { user = { + UserId, AccountName: usr.AccountName, /*Description: usr.Description, FollowersCount: usr.FollowersCount, @@ -57,7 +51,7 @@ async function getUser( if (user === undefined) { const usrDBKeys = BigDataManager.databases.users.keys; - const usr = await BigDataManager.databases.users.getEntry(AccountName); + const usr = await BigDataManager.databases.users.getEntry(UserId); if (usr !== undefined && usr !== null) { let ProfilePicture = { @@ -88,7 +82,11 @@ async function getUser( }; user = { - AccountName, + UserId, + AccountName: createUserProp( + SourceProp.offline, + usr[usrDBKeys.AccountName], + ), Description: createUserProp( SourceProp.offline, usr[usrDBKeys.Description], @@ -114,7 +112,7 @@ async function getUser( try { const resp = await makeRequest({ path: apiBackendRequest.GET_USER_PROFILE, - requestGET: {':accountName': AccountName}, + requestGET: {':userId': UserId}, response: { Description: '', FollowersCount: 0, @@ -127,7 +125,11 @@ async function getUser( }); user = { - AccountName: AccountName, + UserId, + AccountName: createUserProp( + SourceProp.cached, + resp.response.AccountName, + ), Description: createUserProp( SourceProp.cached, resp.response.Description, @@ -162,7 +164,7 @@ async function getUser( if (userIsInCache === false && user !== undefined) { console.log('save in cache'); store.dispatch(appNonSaveVarActions.setCachedUser(user)); - cachedUserList.push(user.AccountName); + cachedUserList.push(user.UserId); if (cachedUserList.length > maxCachedUsers) { let usrId = cachedUserList[0]; @@ -181,47 +183,47 @@ enum GetParam { SAVE, } -let getUserList: {[key: AccountName]: GetParam} = {}; +let getUserList: {[key: UserId]: GetParam} = {}; async function refreshUsers() { - for (let AccountName in getUserList) { - const param = getUserList[AccountName]; - delete getUserList[AccountName]; + for (let UserId in getUserList) { + const param = getUserList[UserId]; + delete getUserList[UserId]; - await getUser(AccountName); + await getUser(UserId); } } setInterval(refreshUsers, 500); -function addUserToGetQueue(AccountName: AccountName, param: GetParam) { - if (getUserList[AccountName] === undefined) { - getUserList[AccountName] = param; - } else if (getUserList[AccountName] < param) { - getUserList[AccountName] = param; +function addUserToGetQueue(UserId: UserId, param: GetParam) { + if (getUserList[UserId] === undefined) { + getUserList[UserId] = param; + } else if (getUserList[UserId] < param) { + getUserList[UserId] = param; } } -function getUserSelector(AccountName: AccountName) { - addUserToGetQueue(AccountName, GetParam.CACHE); +function getUserSelector(UserId: UserId) { + addUserToGetQueue(UserId, GetParam.CACHE); const myUser = useSelector( - (state: RootState) => state.nonSaveVariables.cachedUsers[AccountName], + (state: RootState) => state.nonSaveVariables.cachedUsers[UserId], ); if (myUser === undefined) { - return initUndefinedUser(AccountName); + return initUndefinedUser(UserId); } return myUser; } -function getUserSelectorPicture(AccountName: AccountName): ProfilePicture { - addUserToGetQueue(AccountName, GetParam.CACHE); +function getUserSelectorPicture(UserId: UserId): ProfilePicture { + addUserToGetQueue(UserId, GetParam.CACHE); const myUser = useSelector( (state: RootState) => - state.nonSaveVariables.cachedUsers[AccountName]?.ProfilePicture, + state.nonSaveVariables.cachedUsers[UserId]?.ProfilePicture, ); if (myUser === undefined) { @@ -251,9 +253,10 @@ function getUserSelectorAccountName( return myUser; } */ -function initUndefinedUser(AccountName: AccountName): User { +function initUndefinedUser(UserId: UserId): User { return { - AccountName: AccountName, + UserId, + AccountName: createUserProp(SourceProp.online), /* Description: createUserProp(SourceProp.online), FollowersCount: createUserProp(SourceProp.online), FollowingCount: createUserProp(SourceProp.online), diff --git a/src/user/types.ts b/src/user/types.ts index 82a5b15..065a949 100644 --- a/src/user/types.ts +++ b/src/user/types.ts @@ -3,8 +3,8 @@ import { AccountName, langCode, XAuthorization, - timestamp, Username, + UserId, } from '@configs/types'; export enum SourceProp { @@ -27,8 +27,8 @@ export interface ProfilePicture { } export interface User { - //UserId: UserId; - AccountName: AccountName; + UserId: UserId; + AccountName: BasicUserProp; /* ProfilePicture: ProfilePicture; lastUpdateTimestamp: timestamp; */