Compare commits

...

38 Commits

Author SHA1 Message Date
Antoine PEREDERII f11ca02ec4 Merge branch 'master' of codefirst.iut.uca.fr:antoine.perederii/TpReactNative
continuous-integration/drone/push Build is passing Details
1 year ago
Antoine PEREDERII a043f05832 🧪 Add some tests
1 year ago
Antoine PEREDERII 0eee3563de Update '.drone.yml'
continuous-integration/drone/push Build is passing Details
1 year ago
Antoine PEREDERII d5786ea613 Update '.drone.yml'
continuous-integration/drone/push Build is passing Details
1 year ago
Antoine PEREDERII cde37fcdd8 👷 Sonar deployement tests
1 year ago
Antoine PEREDERII 22d856df67 Merge pull request '🔥 Tests not concluent of Favorites local storage' (#11) from part8 into master
1 year ago
Antoine PEREDERII f6210d7825 🔥 Tests not concluent of Favorites local storage
1 year ago
Antoine PEREDERII b49beeb1e0 Update 'README.md'
1 year ago
Antoine PEREDERII 1c2518a02a Merge pull request 'part7' (#10) from part7 into master
1 year ago
Antoine PEREDERII d2cfa7b992 🐛 Update some nope imports
1 year ago
Antoine PEREDERII f6a01ff7db 🚀 Finish themes
1 year ago
Antoine PEREDERII 6bd58278a0 Merge pull request ' Add first part of theme' (#9) from part7 into master
1 year ago
Antoine PEREDERII 57a846972c Add first part of theme
1 year ago
Antoine PEREDERII 5bd99267bd Merge pull request '🔥 Remove code' (#8) from part6 into master
1 year ago
Antoine PEREDERII a4a79361f8 🔥 Remove code
1 year ago
Antoine PEREDERII e69ed4beeb Merge pull request ' Finish tp6' (#7) from part6 into master
1 year ago
Antoine PEREDERII 476ecb9162 Finish tp6
1 year ago
Antoine PEREDERII 3cc455b3a5 Merge pull request 'part6' (#6) from part6 into master
1 year ago
Antoine PEREDERII ba870e4320 Introduce new TP6
1 year ago
Antoine PEREDERII f8e11adc9f Merge branch 'part5' of codefirst.iut.uca.fr:antoine.perederii/TpReactNative into part5
1 year ago
Antoine PEREDERII bf20bc3306 🔥 Update code
1 year ago
Antoine PEREDERII 723c07cc2c Update 'src/components/JokeDetail.tsx'
1 year ago
Antoine PEREDERII 7820822622 Merge pull request ' Add joke details page' (#5) from part5 into master
1 year ago
Antoine PEREDERII 0a3c15d468 🔀 Merge part5 on master
1 year ago
Antoine PEREDERII ac58bf5302 Add joke details page
1 year ago
Antoine PEREDERII d2d7449d2f Merge pull request 'part4' (#4) from part4 into master
1 year ago
Antoine PEREDERII 79e58e2c80 Update 'src/redux/thunk/RecentsJokesThunk.ts'
1 year ago
Antoine PEREDERII 56558a43bc 🏗️ Add API link
1 year ago
Antoine PEREDERII 1910234f06 Merge pull request 'part4' (#3) from part4 into master
1 year ago
Antoine PEREDERII b2a19dcd44 Add API requests
1 year ago
Antoine PEREDERII 086ea9fd3d ♻️ Update categ filter id to type
1 year ago
Antoine PEREDERII 37f093fc75 💄 Update UI code, remove somes un-used import
1 year ago
Antoine PEREDERII 6195b1a95e 🎨 Update folders project and add a first part of tp1
1 year ago
Antoine PEREDERII dab1695919 Update UI of the part2
1 year ago
Antoine PEREDERII 8def998432 update UI
1 year ago
Antoine PEREDERII b23e3b703a Merge pull request 'part2 merge on master' (#1) from part2 into master
1 year ago
Antoine PEREDERII 6265dd9c58 comment some code and update readme
1 year ago
Antoine PEREDERII 5d9ada3edd update tp
1 year ago

@ -0,0 +1,17 @@
kind: pipeline
type: docker
name: JokesApp
steps:
- name: sonar-analyses
image: hub.codefirst.iut.uca.fr/camille.petitalot/drone-sonarplugin-reactnative:latest
commands:
- cd src
- npm install
- sonar-scanner -Dsonar.projectKey=JokesAppReact_A-PE -Dsonar.sources=. -Dsonar.host.url=$${PLUGIN_SONAR_HOST}
-Dsonar.login=$${PLUGIN_SONAR_TOKEN}
secrets: [ SECRET_SONAR_LOGIN ]
settings:
sonar_host: https://codefirst.iut.uca.fr/sonar/
sonar_token:
from_secret: SECRET_SONAR_LOGIN

@ -59,16 +59,16 @@ Le projet de tp utilise un modèle de flux de travail Git pour organiser le dév
## Avancés du projet ## Avancés du projet
* [x] Partie 0 - Project creation * [x] Partie 0 - Project creation
* [x] Partie 1 - Project organisation * [x] Partie 1 - Project organisation
* [ ] Partie 2 - Typescript types * [X] Partie 2 - Typescript types
* [ ] Partie 3 - Screens * [X] Partie 3 - Screens
* [ ] Partie 4 - Components * [X] Partie 4 - Components
* [ ] Partie 5 - FlatList * [X] Partie 5 - FlatList
* [ ] Partie 6 - Safe Area * [X] Partie 6 - Safe Area
* [ ] Partie 7 - Navigation * [X] Partie 7 - Navigation
* [ ] Partie 8 - Hooks * [X] Partie 8 - Hooks
* [ ] Partie 9 - Redux Store * [X] Partie 9 - Redux Store
* [ ] Partie 10 - Async Storage * [X] Partie 10 - Async Storage
* [ ] Partie 11 - Theming * [X] Partie 11 - Theming
* [ ] Partie 12 - Unit testing * [ ] Partie 12 - Unit testing
* [ ] Partie 13 - Resources * [ ] Partie 13 - Resources

@ -0,0 +1,8 @@
> Why do I have a folder named ".expo" in my project?
The ".expo" folder is created when an Expo project is started using "expo start" command.
> What do the files contain?
- "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds.
- "settings.json": contains the server configuration that is used to serve the application manifest.
> Should I commit the ".expo" folder?
No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
Upon project creation, the ".expo" folder is already added to your ".gitignore" file.

@ -0,0 +1,3 @@
{
"devices": []
}

@ -0,0 +1 @@
module.exports = $$require_external('node:assert');

@ -0,0 +1 @@
module.exports = $$require_external('node:async_hooks');

@ -0,0 +1 @@
module.exports = $$require_external('node:buffer');

@ -0,0 +1 @@
module.exports = $$require_external('node:child_process');

@ -0,0 +1 @@
module.exports = $$require_external('node:cluster');

@ -0,0 +1 @@
module.exports = $$require_external('node:console');

@ -0,0 +1 @@
module.exports = $$require_external('node:constants');

@ -0,0 +1 @@
module.exports = $$require_external('node:crypto');

@ -0,0 +1 @@
module.exports = $$require_external('node:dgram');

@ -0,0 +1 @@
module.exports = $$require_external('node:diagnostics_channel');

@ -0,0 +1 @@
module.exports = $$require_external('node:dns');

@ -0,0 +1 @@
module.exports = $$require_external('node:domain');

@ -0,0 +1 @@
module.exports = $$require_external('node:events');

@ -0,0 +1 @@
module.exports = $$require_external('node:fs');

@ -0,0 +1 @@
module.exports = $$require_external('node:fs/promises');

@ -0,0 +1 @@
module.exports = $$require_external('node:http');

@ -0,0 +1 @@
module.exports = $$require_external('node:http2');

@ -0,0 +1 @@
module.exports = $$require_external('node:https');

@ -0,0 +1 @@
module.exports = $$require_external('node:inspector');

@ -0,0 +1 @@
module.exports = $$require_external('node:module');

@ -0,0 +1 @@
module.exports = $$require_external('node:net');

@ -0,0 +1 @@
module.exports = $$require_external('node:os');

@ -0,0 +1 @@
module.exports = $$require_external('node:path');

@ -0,0 +1 @@
module.exports = $$require_external('node:perf_hooks');

@ -0,0 +1 @@
module.exports = $$require_external('node:process');

@ -0,0 +1 @@
module.exports = $$require_external('node:punycode');

@ -0,0 +1 @@
module.exports = $$require_external('node:querystring');

@ -0,0 +1 @@
module.exports = $$require_external('node:readline');

@ -0,0 +1 @@
module.exports = $$require_external('node:repl');

@ -0,0 +1 @@
module.exports = $$require_external('node:stream');

@ -0,0 +1 @@
module.exports = $$require_external('node:string_decoder');

@ -0,0 +1 @@
module.exports = $$require_external('node:timers');

@ -0,0 +1 @@
module.exports = $$require_external('node:tls');

@ -0,0 +1 @@
module.exports = $$require_external('node:trace_events');

@ -0,0 +1 @@
module.exports = $$require_external('node:tty');

@ -0,0 +1 @@
module.exports = $$require_external('node:url');

@ -0,0 +1 @@
module.exports = $$require_external('node:util');

@ -0,0 +1 @@
module.exports = $$require_external('node:v8');

@ -0,0 +1 @@
module.exports = $$require_external('node:vm');

@ -0,0 +1 @@
module.exports = $$require_external('node:wasi');

@ -0,0 +1 @@
module.exports = $$require_external('node:worker_threads');

@ -0,0 +1 @@
module.exports = $$require_external('node:zlib');

@ -0,0 +1 @@
global.$$require_external = typeof window === "undefined" ? require : () => null;

@ -0,0 +1 @@
global.$$require_external = (moduleId) => {throw new Error(`Node.js standard library module ${moduleId} is not available in this JavaScript environment`);}

@ -0,0 +1,25 @@
import { SafeAreaView } from 'react-native'
import {StyleSheet} from 'react-native';
import NavigationBar from "./navigation/NavigationBar";
import {indigoColor} from "./assets/Theme";
import store from "./redux/store";
import React from "react";
import {Provider} from "react-redux";
export default function App() {
return (
<Provider store={store}>
<SafeAreaView style={{flex: 0, backgroundColor: 'darksalmon'}}/>
<SafeAreaView style={styles.container}>
<NavigationBar />
</SafeAreaView>
</Provider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: indigoColor
},
});

@ -0,0 +1,32 @@
import {Theme} from "@react-navigation/native";
export const indigoColor = "rgba(14, 14, 44, 1)";
export const purpleColor = "rgba(74, 74, 104, 1)";
export const darksalmonColor = "rgba(233, 150, 122, 1)";
export const greyColor = "rgba(140, 140, 161, 1)";
export const whiteColor = "rgba(239, 239, 253, 1)";
export const LightTheme: Theme = {
dark: false,
colors: {
primary: darksalmonColor,
background: whiteColor,
card: greyColor,
text: "black",
border: whiteColor,
notification: 'rgb(255, 59, 48)',
},
};
export const DarkTheme: Theme = {
dark: true,
colors: {
primary: darksalmonColor,
background: purpleColor,
card: indigoColor,
text: whiteColor,
border: greyColor,
notification: 'rgb(255, 69, 58)',
},
};

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

@ -0,0 +1,27 @@
import {StyleSheet, Text, View} from 'react-native';
import {greyColor} from "../assets/Theme";
import {Category} from "../model/Category";
type CategItemProps = {
category: Category;
};
export default function Categ(prop: CategItemProps) {
return (
<View style={styles.bottomContainer}>
<Text style={{color:'white'}}>{prop.category.name}</Text>
</View>
);
}
const styles = StyleSheet.create({
bottomContainer: {
backgroundColor: greyColor,
paddingVertical: 5,
paddingHorizontal: 10,
margin: 10,
borderRadius: 20,
width : 120,
alignItems : 'center'
}
});

@ -0,0 +1,26 @@
import React from 'react';
import { shallow } from 'enzyme';
import Categ from './Categ';
import { Text, View } from 'react-native';
import {describe, expect, it} from "@jest/globals";
describe('Categ Component', () => {
it('renders correctly', () => {
const category = { name: 'Test Category' };
const wrapper = shallow(<Categ category={category} />);
expect(wrapper).toMatchSnapshot();
});
it('displays category name', () => {
const category = { name: 'Test Category' };
const wrapper = shallow(<Categ category={category} />);
expect(wrapper.find(Text).prop('children')).toEqual(category.name);
});
it('applies correct styles', () => {
const category = { name: 'Test Category' };
const wrapper = shallow(<Categ category={category} />);
const containerStyle = wrapper.find(View).prop('style');
expect(containerStyle).toEqual(expect.objectContaining(styles.bottomContainer));
});
});

@ -0,0 +1,21 @@
import {FlatList} from 'react-native';
import Categ from "./Categ";
import {Category} from "../model/Category";
type CategListItemProps = {
categories: Category[];
};
export default function Categs(props: CategListItemProps) {
return (
<FlatList showsHorizontalScrollIndicator={false} horizontal={true}
data={props.categories.sort((a, b) => b.number - a.number)}
keyExtractor={(item) => item.name}
renderItem={
({ item }: { item: Category }) => (
<Categ category={item}/>
)
}
/>
);
}

@ -0,0 +1,36 @@
import React from 'react';
import { shallow } from 'enzyme';
import Categs from './Categs';
import { FlatList } from 'react-native';
import Categ from './Categ';
import { Category } from '../model/Category';
import {describe, expect, it} from "@jest/globals";
describe('Categs Component', () => {
const categories: Category[] = [
new Category('Category 1', 10),
new Category('Category 2', 5),
new Category('Category 3', 15),
];
it('renders correctly', () => {
const wrapper = shallow(<Categs categories={categories} />);
expect(wrapper).toMatchSnapshot();
});
it('renders FlatList with correct props', () => {
const wrapper = shallow(<Categs categories={categories} />);
const flatList = wrapper.find(FlatList);
expect(flatList.exists()).toBeTruthy();
expect(flatList.prop('showsHorizontalScrollIndicator')).toBe(false);
expect(flatList.prop('horizontal')).toBe(true);
expect(flatList.prop('data')).toEqual(categories.sort((a, b) => b.number - a.number));
expect(flatList.prop('keyExtractor')).toBeInstanceOf(Function);
expect(flatList.prop('renderItem')).toBeInstanceOf(Function);
});
it('renders correct number of Categ components', () => {
const wrapper = shallow(<Categs categories={categories} />);
expect(wrapper.find(Categ)).toHaveLength(categories.length);
});
});

@ -0,0 +1,178 @@
import {StyleSheet, Text, View, Image, Button, TouchableOpacity} from 'react-native';
import {SampleJoke} from "../model/SampleJoke";
import {darksalmonColor, whiteColor, greyColor, indigoColor, purpleColor} from "../assets/Theme";
import React, {useEffect, useState} from "react";
import {CustomJoke} from "../model/CustomJoke";
import {useDispatch, useSelector} from "react-redux";
import {getCustomJokeById, getJokeById} from "../redux/thunk/GetByThunk";
import {getCustomJokesList} from "../redux/thunk/GetThunk";
import {deleteCustomJoke} from "../redux/thunk/DeleteThunk";
import {useNavigation} from "@react-navigation/native";
import {AppDispatch} from "../redux/store";
type JokeItemProps = {
joke: SampleJoke | CustomJoke;
};
const eye = require("../assets/eye_icon.png")
const hideEye = require("../assets/eye_off_icon.png")
const heart = require("../assets/favorite_icon.png")
const bin = require("../assets/bin.png")
const heartPlain = require("../assets/plain_favorite_icon.png")
export default function JokeDetail(props: JokeItemProps) {
const [showDescription, setShowDescription] = useState(false);
const [showFavorite, setShowFavorite] = useState(false);
const isCustom = props.joke instanceof CustomJoke;
const dispatch = useDispatch<AppDispatch>();
const navigation = useNavigation();
const toggleDescription = () => {
setShowDescription(!showDescription);
};
const toggleFavorite = () => {
setShowFavorite(!showFavorite);
};
const deleteCustomJokes = async () => {
await dispatch(deleteCustomJoke(props.joke.id));
await dispatch(getCustomJokesList());
navigation.goBack();
};
return(
<View style={styles.container}>
<Image source={{ uri: props.joke.image }} style={styles.image} />
<View style={styles.bottomContainer}>
<Text style={{color: indigoColor}}>{props.joke.type}</Text>
</View>
<Text style={styles.text}>Résumé de la blague</Text>
<View style={styles.row}>
{isCustom && (<TouchableOpacity style={styles.favContainer} onPress={ deleteCustomJokes }>
<View>
<Image
source={bin}
style={styles.imageButton}
/>
</View>
</TouchableOpacity>)
}
<TouchableOpacity style={styles.favContainer} onPress={toggleFavorite}>
<View>
<Image
source={showFavorite ? heartPlain : heart}
style={styles.imageButton}
/>
</View>
</TouchableOpacity>
<TouchableOpacity style={styles.Button} onPress={toggleDescription}>
<View style={styles.chuteContainer}>
<Image
source={showDescription ? hideEye : eye}
style={styles.imageButton}
/>
<Text style={styles.TextButton} >LA CHUTE</Text>
</View>
</TouchableOpacity>
</View>
{showDescription && <Text style={styles.text}>{props.joke.description()}</Text>}
</View>
);
}
const styles = StyleSheet.create({
image : {
margin : 5,
width: '90%',
height:200,
top : 5,
alignSelf : "center",
backgroundColor: "white",
borderRadius: 5,
},
Button:{
borderRadius : 5,
backgroundColor : darksalmonColor,
height:50,
width : 160,
flexDirection : "row"
},
imageButton : {
margin : 10,
width: 50,
height:50,
top : 5,
alignSelf : "center",
backgroundColor: "none",
tintColor : whiteColor
},
favContainer : {
margin : 20,
borderWidth : 3,
borderRadius : 15,
borderColor : whiteColor,
borderStyle : "solid"
},
TextButton : {
margin: 10,
textAlign : "center",
fontWeight: "700",
color : whiteColor,
},
chuteContainer :{
display : "flex",
flex : 1,
flexDirection: "row",
alignItems : "center"
},
container: {
marginHorizontal: "5%",
display: "flex",
marginBottom:7,
marginTop:7,
paddingVertical: 10,
backgroundColor: indigoColor,
justifyContent: 'space-between',
borderRadius: 20,
height: "auto",
borderColor: whiteColor,
borderStyle: "solid",
borderWidth: 1
},
row: {
display: "flex",
flexDirection:"row",
alignSelf: "flex-end",
},
color: {
flex: 0,
backgroundColor: darksalmonColor,
height: 150,
width:15,
},
columnContainer: {
flexDirection: "column",
marginLeft: 20,
marginRight: 20,
width: '60%',
flex: 2,
justifyContent: 'space-between',
},
text: {
color:greyColor,
paddingBottom: 7,
paddingTop: 7,
marginLeft: 19,
fontSize: 16,
},
bottomContainer: {
backgroundColor: whiteColor,
paddingVertical: 5,
paddingHorizontal: 10,
margin: 10,
marginLeft: 19,
marginTop: 20,
borderRadius: 20,
width : 150,
alignItems : 'center'
}
})

@ -0,0 +1,54 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import JokeDetail from '../path/to/JokeDetail';
import {describe} from "@jest/globals"; // Remplacez le chemin par le bon chemin
describe('JokeDetail Component', () => {
const sampleJoke = {
id: '1',
type: 'Sample',
image: 'sample-image-url',
description: () => 'Sample Joke Description'
};
const customJoke = {
id: '2',
type: 'Custom',
image: 'custom-image-url',
description: () => 'Custom Joke Description'
};
test('renders joke image', () => {
const { getByTestId } = render(<JokeDetail joke={sampleJoke} />);
const jokeImage = getByTestId('joke-image');
expect(jokeImage).toBeTruthy();
});
test('renders joke description when toggled', () => {
const { getByText } = render(<JokeDetail joke={sampleJoke} />);
const toggleButton = getByText('LA CHUTE');
fireEvent.press(toggleButton);
const jokeDescription = getByText('Sample Joke Description');
expect(jokeDescription).toBeTruthy();
});
test('toggles favorite icon when pressed', () => {
const { getByTestId } = render(<JokeDetail joke={sampleJoke} />);
const favoriteButton = getByTestId('favorite-button');
fireEvent.press(favoriteButton);
expect(favoriteButton.props.source).toEqual(require('../assets/plain_favorite_icon.png'));
});
test('calls deleteCustomJokes and updates list when delete button is pressed for custom joke', () => {
const deleteMock = jest.fn();
const dispatchMock = jest.fn();
jest.mock('../redux/thunk/DeleteThunk', () => ({ deleteCustomJoke: deleteMock }));
jest.mock('react-redux', () => ({ useDispatch: () => dispatchMock }));
const { getByTestId } = render(<JokeDetail joke={customJoke} />);
const deleteButton = getByTestId('delete-button');
fireEvent.press(deleteButton);
expect(deleteMock).toHaveBeenCalledWith(customJoke.id);
expect(dispatchMock).toHaveBeenCalledTimes(2); // Assuming one dispatch for delete and one for update list
});
});

@ -0,0 +1,67 @@
import {StyleSheet, Text, View, Image} from 'react-native';
import {SampleJoke} from "../model/SampleJoke";
import {CustomJoke} from "../model/CustomJoke";
import {darksalmonColor, whiteColor, indigoColor} from "../assets/Theme";
type JokeListItemProps = {
joke: (CustomJoke | SampleJoke);
};
export default function JokeHomeSquare(prop: JokeListItemProps) {
return (
<View style={styles.container}>
<View style={styles.topBackgroundColor}>
<View style={{width: 200, height: 40, backgroundColor: darksalmonColor}}/>
</View>
<View style={styles.bottomBackgroundColor}>
<View style={{width: 200, height: 120, backgroundColor: indigoColor}}/>
</View>
<Image source={{ uri: prop.joke.image }} style={styles.image} />
<Text style={[styles.text, styles.textTitle]}>Résumé de la blague</Text>
<Text style={[styles.text, styles.textSimple]}>{prop.joke.description()}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
marginTop: 10,
marginLeft: 20,
position: "relative",
},
topBackgroundColor: {
borderTopLeftRadius: 5,
borderTopRightRadius: 5,
overflow: 'hidden',
},
bottomBackgroundColor: {
borderBottomLeftRadius: 5,
borderBottomRightRadius: 5,
overflow: 'hidden',
},
image: {
width: 120,
height: 75,
position: "absolute",
top: 5,
left: "50%",
marginLeft: -60
},
text: {
position: 'absolute',
textAlign: "center",
color:whiteColor,
paddingBottom: 7,
paddingTop: 7,
width: "100%"
},
textTitle: {
fontSize: 17,
fontWeight: "bold",
top: 90
},
textSimple: {
fontSize: 15,
top: 115
}
});

@ -0,0 +1,74 @@
import {StyleSheet, Text, View, Image} from 'react-native';
import {SampleJoke} from "../model/SampleJoke";
import {CustomJoke} from "../model/CustomJoke";
import {darksalmonColor, whiteColor, greyColor, indigoColor} from "../assets/Theme";
import Categ from "./Categ";
type JokeListItemProps = {
joke: (CustomJoke | SampleJoke);
};
export default function JokeItem(prop: JokeListItemProps) {
return (
<View style={styles.rowContainer}>
<View style={styles.color}/>
<Image source={{ uri: prop.joke.image }} style={styles.image} />
<View style={styles.columnContainer}>
<Text style={styles.text}>Résumé de la blague</Text>
<Text style={styles.text}>{prop.joke.description()}</Text>
<View style={styles.bottomContainer}>
<Text style={{color:'white'}}>{prop.joke.type}</Text>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
rowContainer: {
flexDirection: "row",
marginHorizontal: "5%",
marginBottom:7,
marginTop:7,
paddingVertical: 10,
backgroundColor: indigoColor,
width:'90%',
height:150,
justifyContent: 'space-between',
alignItems: 'center',
},
color: {
flex: 0,
backgroundColor: darksalmonColor,
height: 150,
width:15,
},
image: {
width: '40%',
height: 150,
flex: 1
},
columnContainer: {
flexDirection: "column",
marginLeft: 20,
marginRight: 20,
width: '60%',
flex: 2,
justifyContent: 'space-between',
},
text: {
color:whiteColor,
paddingBottom: 7,
paddingTop: 7,
fontSize: 16,
},
bottomContainer: {
backgroundColor: greyColor,
paddingVertical: 5,
paddingHorizontal: 10,
margin: 10,
borderRadius: 20,
width : 120,
alignItems : 'center'
}
});

@ -0,0 +1,35 @@
import {FlatList, Image, StyleSheet, Text, TouchableHighlight, TouchableOpacity, View} from 'react-native';
import {SampleJoke} from "../model/SampleJoke";
import {CustomJoke} from "../model/CustomJoke";
import JokeItem from "./JokeItem";
import React, {useState} from "react";
import {useNavigation} from "@react-navigation/native";
import {darksalmonColor, greyColor, indigoColor, whiteColor} from "../assets/Theme";
type JokeListItemProps = {
jokes: (CustomJoke | SampleJoke)[];
};
export default function JokeItems(props: JokeListItemProps) {
const navigation = useNavigation()
return (
<FlatList
data={props.jokes}
keyExtractor={(item) => item.id.toString()}
renderItem={
({ item }: { item: CustomJoke | SampleJoke }) => (
// @ts-ignore
<TouchableHighlight onPress={() => navigation.navigate("JokeDetails", {"idJoke": item.id})}>
<JokeItem joke={item}/>
</TouchableHighlight>
)
}
/>
);
}
const styles = StyleSheet.create({
})

@ -0,0 +1,22 @@
import {FlatList} from 'react-native';
import {SampleJoke} from "../model/SampleJoke";
import {CustomJoke} from "../model/CustomJoke";
import JokeHomeSquare from "./JokeHomeSquare";
type JokeListItemProps = {
jokes: (CustomJoke | SampleJoke)[];
};
export default function JokesHomeSquare(props: JokeListItemProps) {
return (
<FlatList showsHorizontalScrollIndicator={false} horizontal={true}
data={props.jokes}
renderItem={
({ item }: { item: CustomJoke | SampleJoke }) => (
<JokeHomeSquare joke={item}/>
)
}
keyExtractor={(item) => item.id.toString()}
/>
);
}

@ -0,0 +1,13 @@
/**
* @file CustomJokeStub.ts
* @brief Exemple d'utilisation de la classe JokeFactory pour créer des blagues personnalisées.
*/
import { JokeFactory } from '../../model/JokeFactory';
/**
* @brief Stub de blagues personnalisées créées à l'aide de la classe JokeFactory.
* @constant
* @type {CustomJoke[]}
*/
export const customJokeStub = JokeFactory.createCustomJokes('[{"id":"premier", "type":"custom", "setup":"one", "punchline":"y\'en a pas", "image":"https://placekitten.com/200/300"},{"id":"deuxieme", "type":"custom", "setup":"two","punchline":"y\'en a pas", "image":"https://placekitten.com/200/300"},{"id":"troisieme", "type":"Default", "setup":"three","punchline":"y\'en toujours a pas ;)", "image":"https://placekitten.com/200/300"},{"id":"quatrieme", "type":"custom bro", "setup":"four","punchline":"y\'en toujours toujours ap", "image":"https://placekitten.com/200/300"}]');

@ -0,0 +1,13 @@
/**
* @file SampleJokeStub.ts
* @brief Exemple d'utilisation de la classe JokeFactory pour créer des blagues simples.
*/
import { JokeFactory } from '../../model/JokeFactory';
/**
* @brief Stub de blagues simples créées à l'aide de la classe JokeFactory.
* @constant
* @type {SampleJoke[]}
*/
export const sampleJokeStub = JokeFactory.createSampleJokes('[{"id":1, "type":"custom", "setup":"one", "punchline":"y\'en a pas", "image":"https://placekitten.com/200/300"},{"id":2, "type":"custom", "setup":"two","punchline":"y\'en a pas", "image":"https://placekitten.com/200/300"}]');

@ -0,0 +1,55 @@
/**
* @file Category.ts
* @brief Définition de la classe Catégory.
*/
/**
* @class
* @brief Représente une catégorie nom.
*/
export class Category {
private _name: string;
private _number: number;
/**
* @brief Constructeur de la classe Category.
* @param {string} name - Le nom de la catégorie.
* @param {number} number - Le nombre de la catégorie.
*/
constructor(name: string, number: number) {
this._name = name;
this._number = number;
}
/**
* @brief Obtient le nom de la catégorie.
* @return {string} Le nom de la catégorie.
*/
get name(): string {
return this._name;
}
/**
* @brief Obtient le nombre de la catégorie.
* @return {number} Le nombre de la catégorie.
*/
get number(): number {
return this._number;
}
/**
* @brief Modifie le nom de la catégorie.
* @param {string} name - Le nom de la categorie.
*/
set name(name: string) {
this._name = name;
}
/**
* @brief Modifie le nombre de la catégorie.
* @param {number} number - Le nombre de la catégorie.
*/
set number(number: number) {
this._number = number;
}
}

@ -0,0 +1,25 @@
import { Category } from './Category';
import {describe, expect, it} from "@jest/globals";
describe('Category Class Constructor', () => {
it('should create a new Category object', () => {
const category = new Category('name', 5);
expect(category).toBeDefined();
expect(category.name).toBe('name');
expect(category.number).toBe(5);
});
});
describe('Category Class Accessors', () => {
it('should set and get the name correctly', () => {
const category = new Category('name', 5);
category.name = 'newName';
expect(category.name).toBe('newName');
});
it('should set and get the number correctly', () => {
const category = new Category('name', 5);
category.number = 10;
expect(category.number).toBe(10);
});
});

@ -0,0 +1,44 @@
/**
* @file CustomJoke.ts
* @brief Définition de la classe CustomJoke.
*/
import { Joke } from './Joke';
/**
* @class
* @brief Représente une blague personnalisée avec un identifiant unique.
* @extends Joke
*/
export class CustomJoke extends Joke {
private _id: string;
/**
* @brief Constructeur de la classe CustomJoke.
* @param {string} id - L'identifiant unique de la blague.
* @param {string} type - Le type de la blague.
* @param {string} setup - La partie préliminaire de la blague.
* @param {string} punchline - La chute de la blague.
* @param {string} image - L'URL de l'image associée à la blague.
*/
constructor(id: string, type: string, setup: string, image: string, punchline: string = "") {
super(type, setup, punchline, image); // Assuming Joke class has these properties
this._id = id;
}
/**
* @brief Obtient l'identifiant de la blague.
* @return {string} L'identifiant de la blague.
*/
get id(): string {
return this._id;
}
/**
* @brief Modifie l'identifiant de la blague.
* @param {string} id - Le nouvel identifiant de la blague.
*/
set id(id: string) {
this._id = id;
}
}

@ -0,0 +1,35 @@
import { CustomJoke } from './CustomJoke';
import {describe, expect, test} from "@jest/globals"; // Remplacez le chemin par le bon chemin
describe('CustomJoke Class', () => {
const id = '1';
const type = 'Custom';
const setup = 'Why did the developer go broke?';
const image = 'custom-image-url';
const punchline = 'Because he used up all his cache';
test('creates a new instance of CustomJoke', () => {
const customJoke = new CustomJoke(id, type, setup, image, punchline);
expect(customJoke).toBeInstanceOf(CustomJoke);
expect(customJoke.id).toEqual(id);
expect(customJoke.type).toEqual(type);
expect(customJoke.setup).toEqual(setup);
expect(customJoke.punchline).toEqual(punchline);
expect(customJoke.image).toEqual(image);
});
test('updates CustomJoke properties', () => {
const newSetup = 'Why do programmers prefer dark mode?';
const newImage = 'new-custom-image-url';
const newPunchline = 'Because light attracts bugs';
const customJoke = new CustomJoke(id, type, setup, image, punchline);
customJoke.setup = newSetup;
customJoke.image = newImage;
customJoke.punchline = newPunchline;
expect(customJoke.setup).toEqual(newSetup);
expect(customJoke.image).toEqual(newImage);
expect(customJoke.punchline).toEqual(newPunchline);
});
});

@ -0,0 +1,112 @@
/**
* @file Joke.ts
* @brief Définition de la classe abstraite Joke.
*/
/**
* @class
* @brief Classe abstraite représentant une blague.
*/
export abstract class Joke {
private _type: string;
private _setup: string;
private _punchline: string;
private _image: string;
/**
* @brief Constructeur de la classe Joke.
* @param {string} type - Le type de la blague.
* @param {string} setup - La partie préliminaire de la blague.
* @param {string} punchline - La chute de la blague.
* @param {string} image - L'URL de l'image associée à la blague.
*/
constructor(type: string, setup: string, punchline: string, image: string) {
this._type = type;
this._setup = setup;
this._punchline = punchline;
this._image = image;
}
/**
* @brief Obtient le type de la blague.
* @return {string} Le type de la blague.
*/
get type() {
return this._type;
}
/**
* @brief Obtient la partie préliminaire de la blague.
* @return {string} La partie préliminaire de la blague.
*/
get setup(): string {
return this._setup;
}
/**
* @brief Obtient la chute de la blague.
* @return {string} La chute de la blague.
*/
get punchline(): string {
return this._punchline;
}
/**
* @brief Obtient l'URL de l'image associée à la blague.
* @return {string} L'URL de l'image associée à la blague.
*/
get image(): string {
return this._image;
}
/**
* @brief Modifie le type de la blague.
* @param {string} theType - Le nouveau type de la blague.
*/
set type(theType: string) {
this._type = theType;
}
/**
* @brief Modifie la partie préliminaire de la blague.
* @param {string} theSetup - La nouvelle partie préliminaire de la blague.
*/
public set setup(theSetup: string) {
this._setup = theSetup;
}
/**
* @brief Modifie la chute de la blague.
* @param {string} thePunchline - La nouvelle chute de la blague.
*/
public set punchline(thePunchline: string) {
this._punchline = thePunchline;
}
/**
* @brief Modifie l'URL de l'image associée à la blague.
* @param {string} theImage - Le nouvel URL de l'image associée à la blague.
*/
public set image(theImage: string) {
this._image = theImage;
}
/**
* @brief Obtient un résumé de la blague.
* @return {string} Un résumé de la blague.
*/
public summary(): string {
if(this.punchline.length <= 25){
return this.punchline;
}
return this.punchline.substring(0, 24) + "...";
}
/**
* @brief Obtient une description textuelle de la blague.
* @return {string} Une description textuelle de la blague.
*/
public description(): string {
return this.type + ", " + this.summary();
}
}

@ -1,7 +1,21 @@
/**
* @file JokeFactory.ts
* @brief Définition de la classe JokeFactory.
*/
import { CustomJoke } from "./CustomJoke"; import { CustomJoke } from "./CustomJoke";
import { SampleJoke } from "./SampleJoke"; import { SampleJoke } from "./SampleJoke";
/**
* @class
* @brief Fabrique de blagues permettant de créer des instances de blagues à partir de données JSON.
*/
export class JokeFactory { export class JokeFactory {
/**
* @brief Crée des instances de blagues personnalisées à partir d'un tableau JSON.
* @param {string} jsonArray - Tableau JSON représentant les blagues personnalisées.
* @return {CustomJoke[]} Tableau d'instances de blagues personnalisées.
*/
public static createCustomJokes(jsonArray: string): CustomJoke[] { public static createCustomJokes(jsonArray: string): CustomJoke[] {
const jsonObjects: any[] = JSON.parse(jsonArray); const jsonObjects: any[] = JSON.parse(jsonArray);
const customJokes: CustomJoke[] = []; const customJokes: CustomJoke[] = [];
@ -20,6 +34,11 @@ export class JokeFactory {
return customJokes; return customJokes;
} }
/**
* @brief Crée des instances de blagues d'échantillon à partir d'un tableau JSON.
* @param {string} jsonArray - Tableau JSON représentant les blagues d'échantillon.
* @return {SampleJoke[]} Tableau d'instances de blagues d'échantillon.
*/
public static createSampleJokes(jsonArray: string): SampleJoke[] { public static createSampleJokes(jsonArray: string): SampleJoke[] {
const jsonObjects: any[] = JSON.parse(jsonArray); const jsonObjects: any[] = JSON.parse(jsonArray);
const sampleJokes: SampleJoke[] = []; const sampleJokes: SampleJoke[] = [];

@ -0,0 +1,61 @@
const { Joke } = require('./Joke');
const {expect, it, beforeEach, describe} = require("@jest/globals");
// Mock class extending the abstract Joke class
class MockJoke extends Joke {
constructor(type, setup, punchline, image) {
super(type, setup, punchline, image);
}
}
// Test the Joke class
describe('Joke Class', () => {
let joke;
beforeEach(() => {
joke = new MockJoke('type', 'setup', 'punchline', 'image');
});
// Test the constructor
it('should create a new Joke object', () => {
expect(joke).toBeDefined();
expect(joke.type).toBe('type');
expect(joke.setup).toBe('setup');
expect(joke.punchline).toBe('punchline');
expect(joke.image).toBe('image');
});
// Test the summary() method
it('should return a summary of the joke', () => {
expect(joke.summary()).toBe('punchline');
});
// Test the description() method
it('should return a textual description of the joke', () => {
expect(joke.description()).toBe('type, punchline');
});
// Test setting and getting the type
it('should set and get the type correctly', () => {
joke.type = 'newType';
expect(joke.type).toBe('newType');
});
// Test setting and getting the setup
it('should set and get the setup correctly', () => {
joke.setup = 'newSetup';
expect(joke.setup).toBe('newSetup');
});
// Test setting and getting the punchline
it('should set and get the punchline correctly', () => {
joke.punchline = 'newPunchline';
expect(joke.punchline).toBe('newPunchline');
});
// Test setting and getting the image
it('should set and get the image correctly', () => {
joke.image = 'newImage';
expect(joke.image).toBe('newImage');
});
});

@ -0,0 +1,45 @@
/**
* @file SampleJoke.ts
* @brief Définition de la classe SampleJoke.
*/
import { Joke } from "./Joke";
/**
* @class
* @brief Classe représentant une blague d'échantillon.
* @extends Joke
*/
export class SampleJoke extends Joke {
private _id: number;
/**
* @brief Constructeur de la classe SampleJoke.
* @param {number} id - L'identifiant de la blague d'échantillon.
* @param {string} type - Le type de la blague.
* @param {string} setup - La partie préliminaire de la blague.
* @param {string} punchline - La chute de la blague.
* @param {string} image - L'URL de l'image associée à la blague.
*/
constructor(id: number, type: string, setup: string, image: string, punchline: string = "") {
super(type, setup, punchline, image); // Assuming Joke class has these properties
this._id = id;
}
/**
* @brief Obtient l'identifiant de la blague d'échantillon.
* @return {number} L'identifiant de la blague d'échantillon.
*/
get id(): number {
return this._id;
}
/**
* @brief Modifie l'identifiant de la blague d'échantillon.
* @param {number} id - Le nouvel identifiant de la blague d'échantillon.
*/
set id(id: number) {
this._id = id;
}
}

@ -0,0 +1,126 @@
import {DarkTheme, DefaultTheme, NavigationContainer, Theme} from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import {Image, View} from 'react-native';
import HomeScreen from "../screens/HomeScreen";
import Favorites from "../screens/Favorites";
import Add from "../screens/AddScreen";
import Settings from "../screens/Settings";
import {darksalmonColor, greyColor, indigoColor} from "../assets/Theme";
import StackNavigation from "./StackNavigation";
import {useEffect, useState} from "react";
import {getTheme} from "../redux/thunk/ThemeThunk";
export default function NavigationBar() {
const BottomTabNavigator = createBottomTabNavigator();
const homeIcon = require("../assets/home_icon.png");
const listIcon = require("../assets/list_icon.png");
const addIcon = require("../assets/add_icon.png");
const favoriteIcon = require("../assets/favorite_icon.png");
const settingsIcon = require("../assets/settings_icon.png");
const [themes, setTheme] = useState<Theme>(DefaultTheme);
useEffect(() => {
const fetchTheme = async () => {
const theme = await getTheme();
setTheme(theme);
};
fetchTheme();
});
if (themes == null) {
return null;
}
return (
<NavigationContainer theme={ themes.dark === false ? DefaultTheme : DarkTheme}>
<BottomTabNavigator.Navigator initialRouteName="Accueil"
screenOptions={{
headerStyle: {
backgroundColor: indigoColor,
},
headerTitleStyle: {
color:darksalmonColor,
fontSize:24,
textAlign: "center",
paddingBottom:20,
},
headerTitleAlign: 'center',
tabBarShowLabel: false,
tabBarStyle: {
backgroundColor: indigoColor,
}
}}
>
<BottomTabNavigator.Screen name="Accueil" component={HomeScreen}
options={{
tabBarIcon: ({focused}) => (
<Image
source={homeIcon}
style={{
width: 30, height: 30,
tintColor: focused ? darksalmonColor : greyColor,
}}
/>
)
}}/>
<BottomTabNavigator.Screen name="Catalogue" component={StackNavigation}
options={{
headerShown: false,
tabBarIcon: ({focused}) => (
<Image
source={listIcon}
style={{
width: 30, height: 30,
tintColor: focused ? darksalmonColor : greyColor,
}}
/>
)
}}/>
<BottomTabNavigator.Screen name="Ajout d'une blague" component={Add}
options={{
tabBarIcon: ({focused}) => (
<View style={{backgroundColor: greyColor, borderRadius: 5, padding: 10}}>
<Image
source={addIcon}
style={{
width: 20, height: 20,
tintColor: focused ? darksalmonColor : "black",
}}
/>
</View>
)
}}/>
<BottomTabNavigator.Screen name="Favoris" component={Favorites}
options={{
tabBarIcon: ({focused}) => (
<Image
source={favoriteIcon}
style={{
width: 30, height: 30,
tintColor: focused ? darksalmonColor : greyColor,
}}
/>
)
}}/>
<BottomTabNavigator.Screen name="Parametres" component={Settings}
options={{
tabBarIcon: ({focused}) => (
<Image
source={settingsIcon}
style={{
width: 30, height: 30,
tintColor: focused ? darksalmonColor : greyColor,
}}
/>
)
}}/>
</BottomTabNavigator.Navigator>
</NavigationContainer>
)
}

@ -0,0 +1,26 @@
import {createStackNavigator} from "@react-navigation/stack";
import Catalogue from "../screens/Catalogue";
import JokeDetailsScreen from "../screens/JokeDetailsScreen";
import {darksalmonColor, indigoColor} from "../assets/Theme";
export default function StackNavigation() {
const Stack = createStackNavigator();
return (
<Stack.Navigator initialRouteName="catalogue" screenOptions={{
headerStyle: {
backgroundColor: indigoColor,
},
headerTitleStyle: {
marginTop: 10,
color:darksalmonColor,
fontSize:24,
textAlign: "center",
paddingBottom:30,
},
headerTitleAlign: 'center'
}}>
<Stack.Screen name="catalogue" component={Catalogue} />
<Stack.Screen name="JokeDetails" component={JokeDetailsScreen}/>
</Stack.Navigator>
)
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,63 @@
{
"name": "tp-react-native",
"version": "1.0.0",
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"ts:check": "tsc",
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"test": "jest"
},
"dependencies": {
"@jest/globals": "^29.7.0",
"@react-native-async-storage/async-storage": "^1.23.1",
"@react-navigation/bottom-tabs": "^6.5.11",
"@react-navigation/native": "^6.1.10",
"@react-navigation/stack": "^6.3.21",
"@reduxjs/toolkit": "^2.2.1",
"@testing-library/jest-native": "^5.4.3",
"@testing-library/react-native": "^12.4.5",
"@types/react": "~18.2.45",
"enzyme": "^3.11.0",
"expo": "~50.0.3",
"expo-status-bar": "~1.11.1",
"jest": "^29.7.0",
"jest-expo": "^50.0.4",
"react": "18.2.0",
"react-native": "0.73.2",
"react-native-gesture-handler": "^2.15.0",
"react-native-safe-area-context": "^4.9.0",
"react-redux": "^9.1.0",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"typescript": "^5.3.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@types/fetch-mock": "^7.3.8",
"@types/redux-mock-store": "^1.0.6",
"fetch-mock": "^9.11.0",
"redux-mock-store": "^1.5.4"
},
"jest": {
"preset": "jest-expo",
"verbose": true,
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)"
],
"testMatch": [
"**.test.js"
],
"testEnvironment": "node",
"testEnvironmentOptions": {
"browsers": [
"chrome",
"firefox",
"safari"
]
}
},
"private": true
}

@ -0,0 +1,19 @@
import {Category} from "../../model/Category";
export enum ActionType {
FETCH_CATEGORIES = 'FETCH_CATEGORIES',
}
type actionFetch = {
type: ActionType.FETCH_CATEGORIES;
payload: Category[];
}
export type Action = actionFetch;
export const setCategoriesList = (categoriesList: Category[]) => {
return {
type: ActionType.FETCH_CATEGORIES,
payload: categoriesList,
};
}

@ -0,0 +1,23 @@
import { ActionType, Action, setCategoriesList } from './CategoryAction';
import { Category } from '../../model/Category';
import {describe, expect, it} from "@jest/globals";
describe('Actions', () => {
describe('setCategoriesList', () => {
it('should create an action to set categories list', () => {
const categoriesList: Category[] = [
new Category('Category 1', 1),
new Category('Category 2', 2)
];
const expectedAction: Action = {
type: ActionType.FETCH_CATEGORIES,
payload: categoriesList,
};
const action = setCategoriesList(categoriesList);
expect(action).toEqual(expectedAction);
});
});
});

@ -0,0 +1,53 @@
import {CustomJoke} from "../../model/CustomJoke";
import {SampleJoke} from "../../model/SampleJoke";
export enum ActionType {
FETCH_CUSTOM_JOKES = 'FETCH_CUSTOM_JOKES',
POST_CUSTOM_JOKE = "POST_CUSTOM_JOKE",
FETCH_JOKES_BY_ID = "FETCH_JOKES_BY_ID",
DELETE_CUSTOM_JOKE = "DELETE_CUSTOM_JOKE"
}
type actionPostFetch = {
type: ActionType.POST_CUSTOM_JOKE;
payload: CustomJoke;
}
type actionGetFetch = {
type: ActionType.FETCH_CUSTOM_JOKES;
payload: CustomJoke[];
}
type actionGetByFetch = {
type: ActionType.FETCH_JOKES_BY_ID;
payload: CustomJoke;
}
type actionDeleteFetch = {
type: ActionType.DELETE_CUSTOM_JOKE;
payload: string;
}
export type Action = actionPostFetch | actionGetFetch | actionGetByFetch | actionDeleteFetch;
export const setPostJoke = (customJoke: CustomJoke) => {
return {
type: ActionType.POST_CUSTOM_JOKE,
payload: customJoke
}
}
export const setCustomJokesList = (customJokesList: CustomJoke[]) => {
return {
type: ActionType.FETCH_CUSTOM_JOKES,
payload: customJokesList
};
}
export const setCustomJokeById = (customJoke: CustomJoke) => {
return {
type: ActionType.FETCH_JOKES_BY_ID,
payload: customJoke,
};
}
export const setDeleteJoke = (jokeId: string) => {
return {
type: ActionType.DELETE_CUSTOM_JOKE,
payload: jokeId
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save