diff --git a/components/AlertModal.tsx b/components/AlertModal.tsx
new file mode 100644
index 0000000..d0cca41
--- /dev/null
+++ b/components/AlertModal.tsx
@@ -0,0 +1,61 @@
+// components/AlertModal.tsx
+import { Button, Modal, StyleSheet, Text, View } from 'react-native';
+import React from 'react';
+
+type AlertModalProps = {
+ visible: boolean;
+ message: string;
+ onClose: () => void;
+};
+
+const AlertModal = ({ visible, message, onClose }: AlertModalProps) => {
+ return (
+
+
+
+ {message}
+
+
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ centeredView: {
+ flex: 1,
+ justifyContent: "center",
+ alignItems: "center",
+ marginTop: 16
+ },
+ modalView: {
+ margin: 16,
+ backgroundColor: "white",
+ borderRadius: 16,
+ padding: 32,
+ alignItems: "center",
+ shadowColor: "#000",
+ shadowOffset: {
+ width: 0,
+ height: 2
+ },
+ shadowOpacity: 0.25,
+ shadowRadius: 4,
+ elevation: 5
+ },
+ modalText: {
+ marginBottom: 16,
+ textAlign: "center"
+ }
+});
+
+export default AlertModal;
diff --git a/components/MoveListItem.tsx b/components/MoveListItem.tsx
index e30e050..874ce42 100644
--- a/components/MoveListItem.tsx
+++ b/components/MoveListItem.tsx
@@ -20,10 +20,8 @@ const MoveListItem: React.FC = ({ move, onPress, style }) =>
const styles = StyleSheet.create({
listItem: {
backgroundColor: '#DDD',
- padding: 20,
- marginVertical: 8,
- marginHorizontal: 16,
- borderRadius: 10,
+ padding: 8,
+ borderRadius: 8,
},
listItemText: {
color: '#333',
diff --git a/redux/actions/moveActions.ts b/redux/actions/moveActions.ts
index 051a510..267155c 100644
--- a/redux/actions/moveActions.ts
+++ b/redux/actions/moveActions.ts
@@ -1,71 +1,105 @@
// redux/actions/moveAction.ts
-import { CREATE_MOVE, DELETE_MOVE, GET_MOVES, UPDATE_MOVE } from '../constants';
-import { Move } from "../../entities/Move";
-import { Dispatch } from "redux";
-import { API_BASE_URL } from "../../config";
+import { CREATE_MOVE, DELETE, DELETE_MOVE, GET, GET_MOVES, MOVE_ERROR, POST, PUT, UPDATE_MOVE } from '../constants';
+import {
+ Move
+} from "../../entities/Move";
+import { Dispatch } from "redux";
+import { API_BASE_URL } from "../../config";
export const createMove = (move: Move) => {
+ const verb = POST
return async (dispatch: Dispatch) => {
try {
const response = await fetch(`${API_BASE_URL}/move`, {
- method: 'POST',
+ method: verb,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(move),
});
+
+ if (!response.ok) {
+ throw new Error(`Failed to ${verb}: ${response.statusText}`);
+ }
+
const data = await response.json();
dispatch({ type: CREATE_MOVE, payload: data });
}
catch (error) {
console.error(error);
+ // @ts-ignore
+ dispatch({ type: MOVE_ERROR, payload: error.message });
}
}
}
export const getMoves = () => {
+ const verb = GET
return async (dispatch: Dispatch) => {
try {
const response = await fetch(`${API_BASE_URL}/move`);
+
+ if (!response.ok) {
+ throw new Error(`Failed to ${verb}: ${response.statusText}`);
+ }
+
const data = await response.json();
dispatch({ type: GET_MOVES, payload: data });
}
catch (error) {
console.error(error);
+ // @ts-ignore
+ dispatch({ type: MOVE_ERROR, payload: error.message });
}
}
}
export const updateMove = (id: string, move: Move) => {
+ const verb = PUT
return async (dispatch: Dispatch) => {
try {
const response = await fetch(`${API_BASE_URL}/move/${id}`, {
- method: 'PUT',
+ method: verb,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(move),
});
+
+ if (!response.ok) {
+ throw new Error(`Failed to ${verb}: ${response.statusText}`);
+ }
+
const updatedMove = await response.json();
dispatch({ type: UPDATE_MOVE, payload: updatedMove });
}
catch (error) {
console.error(error);
+ // @ts-ignore
+ dispatch({ type: MOVE_ERROR, payload: error.message });
}
}
}
export const deleteMove = (id: string) => {
+ const verb = DELETE
return async (dispatch: Dispatch) => {
try {
- await fetch(`${API_BASE_URL}/move/${id}`, {
- method: 'DELETE',
+ const response = await fetch(`${API_BASE_URL}/move/${id}`, {
+ method: verb,
});
+
+ if (!response.ok) {
+ throw new Error(`Failed to ${verb}: ${response.statusText}`);
+ }
+
dispatch({ type: DELETE_MOVE, payload: id });
}
catch (error) {
console.error(error);
+ // @ts-ignore
+ dispatch({ type: MOVE_ERROR, payload: error.message });
}
}
}
diff --git a/redux/constants.ts b/redux/constants.ts
index ca67dca..64fd849 100644
--- a/redux/constants.ts
+++ b/redux/constants.ts
@@ -1,6 +1,12 @@
// redux/constants.ts
+export const GET = 'GET';
+export const PUT = 'PUT';
+export const POST = 'POST';
+export const DELETE = 'DELETE';
+
export const GET_MOVES = 'GET_MOVES';
export const CREATE_MOVE = 'CREATE_MOVE';
export const UPDATE_MOVE = 'UPDATE_MOVE';
export const DELETE_MOVE = 'DELETE_MOVE';
+export const MOVE_ERROR = 'MOVE_ERROR';
diff --git a/redux/reducers/moveReducer.ts b/redux/reducers/moveReducer.ts
index b858c7c..0339c30 100644
--- a/redux/reducers/moveReducer.ts
+++ b/redux/reducers/moveReducer.ts
@@ -1,9 +1,10 @@
// redux/reducers/moveReducer.ts
-import { CREATE_MOVE, DELETE_MOVE, GET_MOVES, UPDATE_MOVE } from '../constants';
-import { Move } from "../../entities/Move";
+import { CREATE_MOVE, DELETE_MOVE, GET_MOVES, MOVE_ERROR, UPDATE_MOVE } from '../constants';
+import { Move } from "../../entities/Move";
export type MoveState = {
moves: Move[];
+ error: string | null;
};
type MoveAction = {
@@ -13,27 +14,37 @@ type MoveAction = {
const initialState: MoveState = {
moves: [],
+ error: null
}
export default function moveReducer(state = initialState, action: MoveAction): MoveState {
switch (action.type) {
case GET_MOVES:
return {
- ...state, moves: action.payload as Move[] || []
+ ...state, moves: action.payload as Move[] || [],
+ error: null,
};
case CREATE_MOVE:
return {
- ...state, moves: [...state.moves, action.payload as Move]
+ ...state, moves: [...state.moves, action.payload as Move],
+ error: null,
};
case UPDATE_MOVE:
return {
...state,
- moves: state.moves.map(move => move.id === (action.payload as Move).id ? action.payload as Move : move)
+ moves: state.moves.map(move => move.id === (action.payload as Move).id ? action.payload as Move : move),
+ error: null,
};
case DELETE_MOVE:
return {
...state,
- moves: state.moves.filter(move => move.id !== action.payload)
+ moves: state.moves.filter(move => move.id !== action.payload),
+ error: null,
+ };
+ case MOVE_ERROR:
+ return {
+ ...state,
+ error: action.payload as string
};
default:
return state;
diff --git a/screens/moves/MoveFormScreen.tsx b/screens/moves/MoveFormScreen.tsx
index e2c963f..6a9e570 100644
--- a/screens/moves/MoveFormScreen.tsx
+++ b/screens/moves/MoveFormScreen.tsx
@@ -4,9 +4,9 @@ import React, { useState } from 'react';
import { Button, StyleSheet, Text, TextInput } from 'react-native';
import { StackNavigationProp } from '@react-navigation/stack';
import { RootStackParamList } from "../../navigation/navigationTypes";
-import { useDispatch } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import { createMove, updateMove } from '../../redux/actions/moveActions';
-import { AppDispatch } from "../../redux/store";
+import { AppDispatch, RootState } from "../../redux/store";
import { Move } from "../../entities/Move";
import { RouteProp } from "@react-navigation/native";
import { MOVE_FORM } from "../../navigation/constants";
@@ -16,6 +16,8 @@ import { MoveCategoryName } from "../../entities/MoveCategory
import { TypeName } from "../../entities/TypeName";
import MultiSelect from "react-native-multiple-select";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
+import AlertModal from "../../components/AlertModal";
+import { MOVE_ERROR } from "../../redux/constants";
type MoveFormScreenNavigationProp = StackNavigationProp;
type MoveFormScreenRouteProp = RouteProp;
@@ -26,6 +28,7 @@ type Props = {
};
const MoveFormScreen = ({ navigation, route }: Props) => {
+ const error = useSelector((state: RootState) => state.move.error);
const dispatch = useDispatch();
const [move, setMove] = useState(route.params?.move || {
id: null,
@@ -41,7 +44,6 @@ const MoveFormScreen = ({ navigation, route }: Props) => {
schemaVersion: 2
});
-
const [selectedWeakAgainst, setSelectedWeakAgainst] = useState(move.type.weakAgainst);
const [selectedEffectiveAgainst, setSelectedEffectiveAgainst] = useState(move.type.effectiveAgainst);
@@ -69,6 +71,11 @@ const MoveFormScreen = ({ navigation, route }: Props) => {
return (
+ dispatch({ type: MOVE_ERROR, payload: null })}
+ />
Name:
;
type MoveListScreenRouteProp = RouteProp;
@@ -24,6 +26,7 @@ type RootState = {
};
const MoveListScreen = ({ navigation }: Props) => {
+ const error = useSelector((state: RootState) => state.move.error);
const dispatch: AppDispatch = useDispatch();
const moves = useSelector((state: RootState) => state.move.moves);
@@ -37,29 +40,36 @@ const MoveListScreen = ({ navigation }: Props) => {
);
return (
- navigation.navigate(MOVE_FORM, { move: undefined })}/>
- }
- renderItem={({ item }) => (
-
- navigation.navigate(MOVE_DETAIL, { move: item })}
- />
-
- )}
- keyExtractor={(item) => item.name}
- />
+ <>
+ dispatch({ type: MOVE_ERROR, payload: null })}
+ />
+ navigation.navigate(MOVE_FORM, { move: undefined })}/>
+ }
+ renderItem={({ item }) => (
+
+ navigation.navigate(MOVE_DETAIL, { move: item })}
+ />
+
+ )}
+ keyExtractor={(item) => item.name}
+ />
+ >
);
};
@@ -71,10 +81,12 @@ const styles = StyleSheet.create({
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
+ marginHorizontal: 16,
+ marginTop: 8,
},
moveListItem: {
flex: 1,
- marginRight: 10
+ marginRight: 8
}
});